]> xenbits.xensource.com Git - xentesttools/busybox.git/commitdiff
Initial Busybox commit. master
authorKonrad Rzeszutek Wilk <kliw@darnok.org>
Fri, 14 Aug 2009 22:23:22 +0000 (18:23 -0400)
committerKonrad Rzeszutek Wilk <kliw@darnok.org>
Fri, 14 Aug 2009 22:23:22 +0000 (18:23 -0400)
1464 files changed:
.indent.pro [new file with mode: 0644]
AUTHORS [new file with mode: 0644]
Config.in [new file with mode: 0644]
INSTALL [new file with mode: 0644]
LICENSE [new file with mode: 0644]
Makefile [new file with mode: 0644]
Makefile.custom [new file with mode: 0644]
Makefile.flags [new file with mode: 0644]
Makefile.help [new file with mode: 0644]
README [new file with mode: 0644]
TODO [new file with mode: 0644]
TODO_config_nommu [new file with mode: 0644]
applets/Kbuild [new file with mode: 0644]
applets/applet_tables.c [new file with mode: 0644]
applets/applets.c [new file with mode: 0644]
applets/busybox.mkll [new file with mode: 0755]
applets/individual.c [new file with mode: 0644]
applets/install.sh [new file with mode: 0755]
applets/usage.c [new file with mode: 0644]
applets/usage_compressed [new file with mode: 0755]
arch/i386/Makefile [new file with mode: 0644]
archival/Config.in [new file with mode: 0644]
archival/Kbuild [new file with mode: 0644]
archival/ar.c [new file with mode: 0644]
archival/bbunzip.c [new file with mode: 0644]
archival/bbunzip_test.sh [new file with mode: 0644]
archival/bbunzip_test2.sh [new file with mode: 0644]
archival/bbunzip_test3.sh [new file with mode: 0644]
archival/bz/LICENSE [new file with mode: 0644]
archival/bz/README [new file with mode: 0644]
archival/bz/blocksort.c [new file with mode: 0644]
archival/bz/bzlib.c [new file with mode: 0644]
archival/bz/bzlib.h [new file with mode: 0644]
archival/bz/bzlib_private.h [new file with mode: 0644]
archival/bz/compress.c [new file with mode: 0644]
archival/bz/huffman.c [new file with mode: 0644]
archival/bzip2.c [new file with mode: 0644]
archival/cpio.c [new file with mode: 0644]
archival/dpkg.c [new file with mode: 0644]
archival/dpkg_deb.c [new file with mode: 0644]
archival/gzip.c [new file with mode: 0644]
archival/libunarchive/Kbuild [new file with mode: 0644]
archival/libunarchive/data_align.c [new file with mode: 0644]
archival/libunarchive/data_extract_all.c [new file with mode: 0644]
archival/libunarchive/data_extract_to_buffer.c [new file with mode: 0644]
archival/libunarchive/data_extract_to_stdout.c [new file with mode: 0644]
archival/libunarchive/data_skip.c [new file with mode: 0644]
archival/libunarchive/decompress_bunzip2.c [new file with mode: 0644]
archival/libunarchive/decompress_uncompress.c [new file with mode: 0644]
archival/libunarchive/decompress_unlzma.c [new file with mode: 0644]
archival/libunarchive/decompress_unzip.c [new file with mode: 0644]
archival/libunarchive/filter_accept_all.c [new file with mode: 0644]
archival/libunarchive/filter_accept_list.c [new file with mode: 0644]
archival/libunarchive/filter_accept_list_reassign.c [new file with mode: 0644]
archival/libunarchive/filter_accept_reject_list.c [new file with mode: 0644]
archival/libunarchive/find_list_entry.c [new file with mode: 0644]
archival/libunarchive/get_header_ar.c [new file with mode: 0644]
archival/libunarchive/get_header_cpio.c [new file with mode: 0644]
archival/libunarchive/get_header_tar.c [new file with mode: 0644]
archival/libunarchive/get_header_tar_bz2.c [new file with mode: 0644]
archival/libunarchive/get_header_tar_gz.c [new file with mode: 0644]
archival/libunarchive/get_header_tar_lzma.c [new file with mode: 0644]
archival/libunarchive/header_list.c [new file with mode: 0644]
archival/libunarchive/header_skip.c [new file with mode: 0644]
archival/libunarchive/header_verbose_list.c [new file with mode: 0644]
archival/libunarchive/init_handle.c [new file with mode: 0644]
archival/libunarchive/open_transformer.c [new file with mode: 0644]
archival/libunarchive/seek_by_jump.c [new file with mode: 0644]
archival/libunarchive/seek_by_read.c [new file with mode: 0644]
archival/libunarchive/unpack_ar_archive.c [new file with mode: 0644]
archival/rpm.c [new file with mode: 0644]
archival/rpm2cpio.c [new file with mode: 0644]
archival/tar.c [new file with mode: 0644]
archival/unzip.c [new file with mode: 0644]
archival/unzip_doc.txt.bz2 [new file with mode: 0644]
busybox-1.14.3.tar.bz2 [new file with mode: 0644]
console-tools/Config.in [new file with mode: 0644]
console-tools/Kbuild [new file with mode: 0644]
console-tools/chvt.c [new file with mode: 0644]
console-tools/clear.c [new file with mode: 0644]
console-tools/deallocvt.c [new file with mode: 0644]
console-tools/dumpkmap.c [new file with mode: 0644]
console-tools/kbd_mode.c [new file with mode: 0644]
console-tools/loadfont.c [new file with mode: 0644]
console-tools/loadkmap.c [new file with mode: 0644]
console-tools/openvt.c [new file with mode: 0644]
console-tools/reset.c [new file with mode: 0644]
console-tools/resize.c [new file with mode: 0644]
console-tools/setconsole.c [new file with mode: 0644]
console-tools/setkeycodes.c [new file with mode: 0644]
console-tools/setlogcons.c [new file with mode: 0644]
console-tools/showkey.c [new file with mode: 0644]
coreutils/Config.in [new file with mode: 0644]
coreutils/Kbuild [new file with mode: 0644]
coreutils/basename.c [new file with mode: 0644]
coreutils/cal.c [new file with mode: 0644]
coreutils/cat.c [new file with mode: 0644]
coreutils/catv.c [new file with mode: 0644]
coreutils/chgrp.c [new file with mode: 0644]
coreutils/chmod.c [new file with mode: 0644]
coreutils/chown.c [new file with mode: 0644]
coreutils/chroot.c [new file with mode: 0644]
coreutils/cksum.c [new file with mode: 0644]
coreutils/comm.c [new file with mode: 0644]
coreutils/cp.c [new file with mode: 0644]
coreutils/cut.c [new file with mode: 0644]
coreutils/date.c [new file with mode: 0644]
coreutils/dd.c [new file with mode: 0644]
coreutils/df.c [new file with mode: 0644]
coreutils/dirname.c [new file with mode: 0644]
coreutils/dos2unix.c [new file with mode: 0644]
coreutils/du.c [new file with mode: 0644]
coreutils/echo.c [new file with mode: 0644]
coreutils/env.c [new file with mode: 0644]
coreutils/expand.c [new file with mode: 0644]
coreutils/expr.c [new file with mode: 0644]
coreutils/false.c [new file with mode: 0644]
coreutils/fold.c [new file with mode: 0644]
coreutils/head.c [new file with mode: 0644]
coreutils/hostid.c [new file with mode: 0644]
coreutils/id.c [new file with mode: 0644]
coreutils/id_test.sh [new file with mode: 0755]
coreutils/install.c [new file with mode: 0644]
coreutils/length.c [new file with mode: 0644]
coreutils/libcoreutils/Kbuild [new file with mode: 0644]
coreutils/libcoreutils/coreutils.h [new file with mode: 0644]
coreutils/libcoreutils/cp_mv_stat.c [new file with mode: 0644]
coreutils/libcoreutils/getopt_mk_fifo_nod.c [new file with mode: 0644]
coreutils/ln.c [new file with mode: 0644]
coreutils/logname.c [new file with mode: 0644]
coreutils/ls.c [new file with mode: 0644]
coreutils/md5_sha1_sum.c [new file with mode: 0644]
coreutils/mkdir.c [new file with mode: 0644]
coreutils/mkfifo.c [new file with mode: 0644]
coreutils/mknod.c [new file with mode: 0644]
coreutils/mv.c [new file with mode: 0644]
coreutils/nice.c [new file with mode: 0644]
coreutils/nohup.c [new file with mode: 0644]
coreutils/od.c [new file with mode: 0644]
coreutils/od_bloaty.c [new file with mode: 0644]
coreutils/printenv.c [new file with mode: 0644]
coreutils/printf.c [new file with mode: 0644]
coreutils/pwd.c [new file with mode: 0644]
coreutils/readlink.c [new file with mode: 0644]
coreutils/realpath.c [new file with mode: 0644]
coreutils/rm.c [new file with mode: 0644]
coreutils/rmdir.c [new file with mode: 0644]
coreutils/seq.c [new file with mode: 0644]
coreutils/sleep.c [new file with mode: 0644]
coreutils/sort.c [new file with mode: 0644]
coreutils/split.c [new file with mode: 0644]
coreutils/stat.c [new file with mode: 0644]
coreutils/stty.c [new file with mode: 0644]
coreutils/sum.c [new file with mode: 0644]
coreutils/sync.c [new file with mode: 0644]
coreutils/tac.c [new file with mode: 0644]
coreutils/tail.c [new file with mode: 0644]
coreutils/tee.c [new file with mode: 0644]
coreutils/test.c [new file with mode: 0644]
coreutils/test_ptr_hack.c [new file with mode: 0644]
coreutils/touch.c [new file with mode: 0644]
coreutils/tr.c [new file with mode: 0644]
coreutils/true.c [new file with mode: 0644]
coreutils/tty.c [new file with mode: 0644]
coreutils/uname.c [new file with mode: 0644]
coreutils/uniq.c [new file with mode: 0644]
coreutils/usleep.c [new file with mode: 0644]
coreutils/uudecode.c [new file with mode: 0644]
coreutils/uuencode.c [new file with mode: 0644]
coreutils/wc.c [new file with mode: 0644]
coreutils/who.c [new file with mode: 0644]
coreutils/whoami.c [new file with mode: 0644]
coreutils/yes.c [new file with mode: 0644]
debianutils/Config.in [new file with mode: 0644]
debianutils/Kbuild [new file with mode: 0644]
debianutils/mktemp.c [new file with mode: 0644]
debianutils/pipe_progress.c [new file with mode: 0644]
debianutils/run_parts.c [new file with mode: 0644]
debianutils/start_stop_daemon.c [new file with mode: 0644]
debianutils/which.c [new file with mode: 0644]
docs/Serial-Programming-HOWTO.txt [new file with mode: 0644]
docs/autodocifier.pl [new file with mode: 0755]
docs/busybox.net/FAQ.html [new file with mode: 0644]
docs/busybox.net/about.html [new file with mode: 0644]
docs/busybox.net/busybox-growth.ps [new file with mode: 0644]
docs/busybox.net/copyright.txt [new file with mode: 0644]
docs/busybox.net/developer.html [new file with mode: 0644]
docs/busybox.net/download.html [new file with mode: 0644]
docs/busybox.net/fix.html [new file with mode: 0644]
docs/busybox.net/footer.html [new file with mode: 0644]
docs/busybox.net/header.html [new file with mode: 0644]
docs/busybox.net/images/back.png [new file with mode: 0644]
docs/busybox.net/images/busybox.jpeg [new file with mode: 0644]
docs/busybox.net/images/busybox.png [new file with mode: 0644]
docs/busybox.net/images/busybox1.png [new file with mode: 0644]
docs/busybox.net/images/busybox2.jpg [new file with mode: 0644]
docs/busybox.net/images/busybox2.png [new file with mode: 0644]
docs/busybox.net/images/busybox3.jpg [new file with mode: 0644]
docs/busybox.net/images/dir.png [new file with mode: 0644]
docs/busybox.net/images/donate.png [new file with mode: 0644]
docs/busybox.net/images/fm.mini.png [new file with mode: 0644]
docs/busybox.net/images/gfx_by_gimp.png [new file with mode: 0644]
docs/busybox.net/images/ltbutton2.png [new file with mode: 0644]
docs/busybox.net/images/osuosl.png [new file with mode: 0644]
docs/busybox.net/images/sdsmall.png [new file with mode: 0644]
docs/busybox.net/images/text.png [new file with mode: 0644]
docs/busybox.net/images/valid-html401.png [new file with mode: 0644]
docs/busybox.net/images/vh40.gif [new file with mode: 0644]
docs/busybox.net/images/written.in.vi.png [new file with mode: 0644]
docs/busybox.net/index.html [new file with mode: 0644]
docs/busybox.net/license.html [new file with mode: 0644]
docs/busybox.net/links.html [new file with mode: 0644]
docs/busybox.net/lists.html [new file with mode: 0644]
docs/busybox.net/news.html [new file with mode: 0644]
docs/busybox.net/oldnews.html [new file with mode: 0644]
docs/busybox.net/products.html [new file with mode: 0644]
docs/busybox.net/screenshot.html [new file with mode: 0644]
docs/busybox.net/shame.html [new file with mode: 0644]
docs/busybox.net/sponsors.html [new file with mode: 0644]
docs/busybox.net/subversion.html [new file with mode: 0644]
docs/busybox.net/svnindex.css [new file with mode: 0644]
docs/busybox.net/svnindex.xsl [new file with mode: 0644]
docs/busybox.net/tinyutils.html [new file with mode: 0644]
docs/busybox_footer.pod [new file with mode: 0644]
docs/busybox_header.pod [new file with mode: 0644]
docs/cgi/cl.html [new file with mode: 0644]
docs/cgi/env.html [new file with mode: 0644]
docs/cgi/in.html [new file with mode: 0644]
docs/cgi/interface.html [new file with mode: 0644]
docs/cgi/out.html [new file with mode: 0644]
docs/contributing.txt [new file with mode: 0644]
docs/ctty.htm [new file with mode: 0644]
docs/draft-coar-cgi-v11-03-clean.html [new file with mode: 0644]
docs/ifupdown_design.txt [new file with mode: 0644]
docs/keep_data_small.txt [new file with mode: 0644]
docs/logging_and_backgrounding.txt [new file with mode: 0644]
docs/mdev.txt [new file with mode: 0644]
docs/new-applet-HOWTO.txt [new file with mode: 0644]
docs/nofork_noexec.txt [new file with mode: 0644]
docs/sigint.htm [new file with mode: 0644]
docs/style-guide.txt [new file with mode: 0644]
docs/tar_pax.txt [new file with mode: 0644]
e2fsprogs/Config.in [new file with mode: 0644]
e2fsprogs/Kbuild [new file with mode: 0644]
e2fsprogs/README [new file with mode: 0644]
e2fsprogs/chattr.c [new file with mode: 0644]
e2fsprogs/e2fs_defs.h [new file with mode: 0644]
e2fsprogs/e2fs_lib.c [new file with mode: 0644]
e2fsprogs/e2fs_lib.h [new file with mode: 0644]
e2fsprogs/fsck.c [new file with mode: 0644]
e2fsprogs/lsattr.c [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/Config.in [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/Kbuild [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/README [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/blkid/Kbuild [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/blkid/blkid.h [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/blkid/blkidP.h [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/blkid/blkid_getsize.c [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/blkid/cache.c [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/blkid/dev.c [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/blkid/devname.c [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/blkid/devno.c [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/blkid/list.c [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/blkid/list.h [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/blkid/probe.c [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/blkid/probe.h [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/blkid/read.c [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/blkid/resolve.c [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/blkid/save.c [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/blkid/tag.c [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/chattr.c [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/e2fsbb.h [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/e2fsck.c [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/e2fsck.h [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/e2p/Kbuild [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/e2p/e2p.h [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/e2p/feature.c [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/e2p/fgetsetflags.c [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/e2p/fgetsetversion.c [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/e2p/hashstr.c [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/e2p/iod.c [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/e2p/ls.c [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/e2p/mntopts.c [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/e2p/ostype.c [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/e2p/parse_num.c [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/e2p/pe.c [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/e2p/pf.c [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/e2p/ps.c [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/e2p/uuid.c [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/ext2fs/Kbuild [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/ext2fs/alloc.c [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/ext2fs/alloc_sb.c [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/ext2fs/alloc_stats.c [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/ext2fs/alloc_tables.c [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/ext2fs/badblocks.c [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/ext2fs/bb_compat.c [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/ext2fs/bb_inode.c [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/ext2fs/bitmaps.c [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/ext2fs/bitops.c [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/ext2fs/bitops.h [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/ext2fs/block.c [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/ext2fs/bmap.c [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/ext2fs/bmove.c [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/ext2fs/brel.h [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/ext2fs/brel_ma.c [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/ext2fs/check_desc.c [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/ext2fs/closefs.c [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/ext2fs/cmp_bitmaps.c [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/ext2fs/dblist.c [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/ext2fs/dblist_dir.c [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/ext2fs/dir_iterate.c [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/ext2fs/dirblock.c [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/ext2fs/dirhash.c [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/ext2fs/dupfs.c [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/ext2fs/e2image.h [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/ext2fs/expanddir.c [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/ext2fs/ext2_err.h [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/ext2fs/ext2_ext_attr.h [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/ext2fs/ext2_fs.h [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/ext2fs/ext2_io.h [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/ext2fs/ext2_types.h [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/ext2fs/ext2fs.h [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/ext2fs/ext2fsP.h [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/ext2fs/ext2fs_inline.c [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/ext2fs/ext_attr.c [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/ext2fs/fileio.c [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/ext2fs/finddev.c [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/ext2fs/flushb.c [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/ext2fs/freefs.c [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/ext2fs/gen_bitmap.c [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/ext2fs/get_pathname.c [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/ext2fs/getsectsize.c [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/ext2fs/getsize.c [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/ext2fs/icount.c [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/ext2fs/imager.c [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/ext2fs/ind_block.c [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/ext2fs/initialize.c [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/ext2fs/inline.c [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/ext2fs/inode.c [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/ext2fs/inode_io.c [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/ext2fs/io_manager.c [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/ext2fs/irel.h [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/ext2fs/irel_ma.c [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/ext2fs/ismounted.c [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/ext2fs/jfs_dat.h [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/ext2fs/kernel-jbd.h [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/ext2fs/kernel-list.h [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/ext2fs/link.c [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/ext2fs/lookup.c [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/ext2fs/mkdir.c [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/ext2fs/mkjournal.c [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/ext2fs/namei.c [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/ext2fs/newdir.c [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/ext2fs/openfs.c [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/ext2fs/read_bb.c [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/ext2fs/read_bb_file.c [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/ext2fs/res_gdt.c [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/ext2fs/rs_bitmap.c [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/ext2fs/rw_bitmaps.c [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/ext2fs/sparse.c [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/ext2fs/swapfs.c [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/ext2fs/test_io.c [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/ext2fs/unix_io.c [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/ext2fs/unlink.c [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/ext2fs/valid_blk.c [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/ext2fs/version.c [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/ext2fs/write_bb_file.c [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/fsck.c [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/fsck.h [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/lsattr.c [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/mke2fs.c [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/tune2fs.c [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/util.c [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/util.h [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/uuid/Kbuild [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/uuid/compare.c [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/uuid/gen_uuid.c [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/uuid/pack.c [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/uuid/parse.c [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/uuid/unpack.c [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/uuid/unparse.c [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/uuid/uuid.h [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/uuid/uuidP.h [new file with mode: 0644]
e2fsprogs/old_e2fsprogs/uuid/uuid_time.c [new file with mode: 0644]
editors/Config.in [new file with mode: 0644]
editors/Kbuild [new file with mode: 0644]
editors/awk.c [new file with mode: 0644]
editors/cmp.c [new file with mode: 0644]
editors/diff.c [new file with mode: 0644]
editors/ed.c [new file with mode: 0644]
editors/patch.c [new file with mode: 0644]
editors/sed.c [new file with mode: 0644]
editors/sed1line.txt [new file with mode: 0644]
editors/sed_summary.htm [new file with mode: 0644]
editors/vi.c [new file with mode: 0644]
examples/bootfloppy/bootfloppy.txt [new file with mode: 0644]
examples/bootfloppy/display.txt [new file with mode: 0644]
examples/bootfloppy/etc/fstab [new file with mode: 0644]
examples/bootfloppy/etc/init.d/rcS [new file with mode: 0755]
examples/bootfloppy/etc/inittab [new file with mode: 0644]
examples/bootfloppy/etc/profile [new file with mode: 0644]
examples/bootfloppy/mkdevs.sh [new file with mode: 0755]
examples/bootfloppy/mkrootfs.sh [new file with mode: 0755]
examples/bootfloppy/mksyslinux.sh [new file with mode: 0755]
examples/bootfloppy/quickstart.txt [new file with mode: 0644]
examples/bootfloppy/syslinux.cfg [new file with mode: 0644]
examples/busybox.spec [new file with mode: 0644]
examples/depmod [new file with mode: 0644]
examples/depmod.pl [new file with mode: 0755]
examples/devfsd.conf [new file with mode: 0644]
examples/dnsd.conf [new file with mode: 0644]
examples/inetd.conf [new file with mode: 0644]
examples/inittab [new file with mode: 0644]
examples/mk2knr.pl [new file with mode: 0755]
examples/udhcp/sample.bound [new file with mode: 0755]
examples/udhcp/sample.deconfig [new file with mode: 0755]
examples/udhcp/sample.nak [new file with mode: 0755]
examples/udhcp/sample.renew [new file with mode: 0755]
examples/udhcp/sample.script [new file with mode: 0644]
examples/udhcp/simple.script [new file with mode: 0644]
examples/udhcp/udhcpd.conf [new file with mode: 0644]
examples/undeb [new file with mode: 0644]
examples/unrpm [new file with mode: 0644]
examples/zcip.script [new file with mode: 0644]
findutils/Config.in [new file with mode: 0644]
findutils/Kbuild [new file with mode: 0644]
findutils/find.c [new file with mode: 0644]
findutils/grep.c [new file with mode: 0644]
findutils/xargs.c [new file with mode: 0644]
include/applets.h [new file with mode: 0644]
include/busybox.h [new file with mode: 0644]
include/dump.h [new file with mode: 0644]
include/grp_.h [new file with mode: 0644]
include/inet_common.h [new file with mode: 0644]
include/libbb.h [new file with mode: 0644]
include/platform.h [new file with mode: 0644]
include/pwd_.h [new file with mode: 0644]
include/rtc_.h [new file with mode: 0644]
include/shadow_.h [new file with mode: 0644]
include/unarchive.h [new file with mode: 0644]
include/usage.h [new file with mode: 0644]
include/volume_id.h [new file with mode: 0644]
include/xatonum.h [new file with mode: 0644]
include/xregex.h [new file with mode: 0644]
init/Config.in [new file with mode: 0644]
init/Kbuild [new file with mode: 0644]
init/halt.c [new file with mode: 0644]
init/init.c [new file with mode: 0644]
init/mesg.c [new file with mode: 0644]
libbb/Config.in [new file with mode: 0644]
libbb/Kbuild [new file with mode: 0644]
libbb/README [new file with mode: 0644]
libbb/appletlib.c [new file with mode: 0644]
libbb/ask_confirmation.c [new file with mode: 0644]
libbb/bb_askpass.c [new file with mode: 0644]
libbb/bb_basename.c [new file with mode: 0644]
libbb/bb_do_delay.c [new file with mode: 0644]
libbb/bb_pwd.c [new file with mode: 0644]
libbb/bb_qsort.c [new file with mode: 0644]
libbb/bb_strtod.c [new file with mode: 0644]
libbb/bb_strtonum.c [new file with mode: 0644]
libbb/change_identity.c [new file with mode: 0644]
libbb/chomp.c [new file with mode: 0644]
libbb/compare_string_array.c [new file with mode: 0644]
libbb/concat_path_file.c [new file with mode: 0644]
libbb/concat_subpath_file.c [new file with mode: 0644]
libbb/copy_file.c [new file with mode: 0644]
libbb/copyfd.c [new file with mode: 0644]
libbb/correct_password.c [new file with mode: 0644]
libbb/crc32.c [new file with mode: 0644]
libbb/create_icmp6_socket.c [new file with mode: 0644]
libbb/create_icmp_socket.c [new file with mode: 0644]
libbb/default_error_retval.c [new file with mode: 0644]
libbb/device_open.c [new file with mode: 0644]
libbb/die_if_bad_username.c [new file with mode: 0644]
libbb/dump.c [new file with mode: 0644]
libbb/error_msg.c [new file with mode: 0644]
libbb/error_msg_and_die.c [new file with mode: 0644]
libbb/execable.c [new file with mode: 0644]
libbb/fclose_nonstdin.c [new file with mode: 0644]
libbb/fflush_stdout_and_exit.c [new file with mode: 0644]
libbb/fgets_str.c [new file with mode: 0644]
libbb/find_mount_point.c [new file with mode: 0644]
libbb/find_pid_by_name.c [new file with mode: 0644]
libbb/find_root_device.c [new file with mode: 0644]
libbb/full_write.c [new file with mode: 0644]
libbb/get_console.c [new file with mode: 0644]
libbb/get_last_path_component.c [new file with mode: 0644]
libbb/get_line_from_file.c [new file with mode: 0644]
libbb/getopt32.c [new file with mode: 0644]
libbb/getpty.c [new file with mode: 0644]
libbb/herror_msg.c [new file with mode: 0644]
libbb/herror_msg_and_die.c [new file with mode: 0644]
libbb/human_readable.c [new file with mode: 0644]
libbb/inet_common.c [new file with mode: 0644]
libbb/info_msg.c [new file with mode: 0644]
libbb/inode_hash.c [new file with mode: 0644]
libbb/isdirectory.c [new file with mode: 0644]
libbb/kernel_version.c [new file with mode: 0644]
libbb/last_char_is.c [new file with mode: 0644]
libbb/lineedit.c [new file with mode: 0644]
libbb/lineedit_ptr_hack.c [new file with mode: 0644]
libbb/llist.c [new file with mode: 0644]
libbb/login.c [new file with mode: 0644]
libbb/loop.c [new file with mode: 0644]
libbb/make_directory.c [new file with mode: 0644]
libbb/makedev.c [new file with mode: 0644]
libbb/match_fstype.c [new file with mode: 0644]
libbb/md5.c [new file with mode: 0644]
libbb/md5prime.c [new file with mode: 0644]
libbb/messages.c [new file with mode: 0644]
libbb/mode_string.c [new file with mode: 0644]
libbb/mtab.c [new file with mode: 0644]
libbb/mtab_file.c [new file with mode: 0644]
libbb/obscure.c [new file with mode: 0644]
libbb/parse_config.c [new file with mode: 0644]
libbb/parse_mode.c [new file with mode: 0644]
libbb/perror_msg.c [new file with mode: 0644]
libbb/perror_msg_and_die.c [new file with mode: 0644]
libbb/perror_nomsg.c [new file with mode: 0644]
libbb/perror_nomsg_and_die.c [new file with mode: 0644]
libbb/pidfile.c [new file with mode: 0644]
libbb/print_flags.c [new file with mode: 0644]
libbb/printable.c [new file with mode: 0644]
libbb/process_escape_sequence.c [new file with mode: 0644]
libbb/procps.c [new file with mode: 0644]
libbb/ptr_to_globals.c [new file with mode: 0644]
libbb/pw_encrypt.c [new file with mode: 0644]
libbb/pw_encrypt_des.c [new file with mode: 0644]
libbb/pw_encrypt_md5.c [new file with mode: 0644]
libbb/pw_encrypt_sha.c [new file with mode: 0644]
libbb/read.c [new file with mode: 0644]
libbb/read_key.c [new file with mode: 0644]
libbb/recursive_action.c [new file with mode: 0644]
libbb/remove_file.c [new file with mode: 0644]
libbb/restricted_shell.c [new file with mode: 0644]
libbb/rtc.c [new file with mode: 0644]
libbb/run_shell.c [new file with mode: 0644]
libbb/safe_gethostname.c [new file with mode: 0644]
libbb/safe_poll.c [new file with mode: 0644]
libbb/safe_strncpy.c [new file with mode: 0644]
libbb/safe_write.c [new file with mode: 0644]
libbb/selinux_common.c [new file with mode: 0644]
libbb/setup_environment.c [new file with mode: 0644]
libbb/sha1.c [new file with mode: 0644]
libbb/signals.c [new file with mode: 0644]
libbb/simplify_path.c [new file with mode: 0644]
libbb/skip_whitespace.c [new file with mode: 0644]
libbb/speed_table.c [new file with mode: 0644]
libbb/str_tolower.c [new file with mode: 0644]
libbb/strrstr.c [new file with mode: 0644]
libbb/time.c [new file with mode: 0644]
libbb/trim.c [new file with mode: 0644]
libbb/u_signal_names.c [new file with mode: 0644]
libbb/udp_io.c [new file with mode: 0644]
libbb/update_passwd.c [new file with mode: 0644]
libbb/uuencode.c [new file with mode: 0644]
libbb/vdprintf.c [new file with mode: 0644]
libbb/verror_msg.c [new file with mode: 0644]
libbb/vfork_daemon_rexec.c [new file with mode: 0644]
libbb/warn_ignoring_args.c [new file with mode: 0644]
libbb/wfopen.c [new file with mode: 0644]
libbb/wfopen_input.c [new file with mode: 0644]
libbb/write.c [new file with mode: 0644]
libbb/xatonum.c [new file with mode: 0644]
libbb/xatonum_template.c [new file with mode: 0644]
libbb/xconnect.c [new file with mode: 0644]
libbb/xfunc_die.c [new file with mode: 0644]
libbb/xfuncs.c [new file with mode: 0644]
libbb/xfuncs_printf.c [new file with mode: 0644]
libbb/xgetcwd.c [new file with mode: 0644]
libbb/xgethostbyname.c [new file with mode: 0644]
libbb/xreadlink.c [new file with mode: 0644]
libbb/xrealloc_vector.c [new file with mode: 0644]
libbb/xregcomp.c [new file with mode: 0644]
libpwdgrp/Kbuild [new file with mode: 0644]
libpwdgrp/pwd_grp.c [new file with mode: 0644]
libpwdgrp/pwd_grp_internal.c [new file with mode: 0644]
libpwdgrp/uidgid_get.c [new file with mode: 0644]
loginutils/Config.in [new file with mode: 0644]
loginutils/Kbuild [new file with mode: 0644]
loginutils/addgroup.c [new file with mode: 0644]
loginutils/adduser.c [new file with mode: 0644]
loginutils/chpasswd.c [new file with mode: 0644]
loginutils/cryptpw.c [new file with mode: 0644]
loginutils/deluser.c [new file with mode: 0644]
loginutils/getty.c [new file with mode: 0644]
loginutils/login.c [new file with mode: 0644]
loginutils/passwd.c [new file with mode: 0644]
loginutils/su.c [new file with mode: 0644]
loginutils/sulogin.c [new file with mode: 0644]
loginutils/vlock.c [new file with mode: 0644]
mailutils/Config.in [new file with mode: 0644]
mailutils/Kbuild [new file with mode: 0644]
mailutils/mail.c [new file with mode: 0644]
mailutils/mail.h [new file with mode: 0644]
mailutils/mime.c [new file with mode: 0644]
mailutils/popmaildir.c [new file with mode: 0644]
mailutils/sendmail.c [new file with mode: 0644]
miscutils/Config.in [new file with mode: 0644]
miscutils/Kbuild [new file with mode: 0644]
miscutils/adjtimex.c [new file with mode: 0644]
miscutils/bbconfig.c [new file with mode: 0644]
miscutils/chat.c [new file with mode: 0644]
miscutils/chrt.c [new file with mode: 0644]
miscutils/crond.c [new file with mode: 0644]
miscutils/crontab.c [new file with mode: 0644]
miscutils/dc.c [new file with mode: 0644]
miscutils/devfsd.c [new file with mode: 0644]
miscutils/devmem.c [new file with mode: 0644]
miscutils/eject.c [new file with mode: 0644]
miscutils/fbsplash.c [new file with mode: 0644]
miscutils/fbsplash.cfg [new file with mode: 0644]
miscutils/flash_eraseall.c [new file with mode: 0644]
miscutils/hdparm.c [new file with mode: 0644]
miscutils/inotifyd.c [new file with mode: 0644]
miscutils/ionice.c [new file with mode: 0644]
miscutils/last.c [new file with mode: 0644]
miscutils/last_fancy.c [new file with mode: 0644]
miscutils/less.c [new file with mode: 0644]
miscutils/makedevs.c [new file with mode: 0644]
miscutils/man.c [new file with mode: 0644]
miscutils/microcom.c [new file with mode: 0644]
miscutils/mountpoint.c [new file with mode: 0644]
miscutils/mt.c [new file with mode: 0644]
miscutils/raidautorun.c [new file with mode: 0644]
miscutils/readahead.c [new file with mode: 0644]
miscutils/runlevel.c [new file with mode: 0644]
miscutils/rx.c [new file with mode: 0644]
miscutils/setsid.c [new file with mode: 0644]
miscutils/strings.c [new file with mode: 0644]
miscutils/taskset.c [new file with mode: 0644]
miscutils/time.c [new file with mode: 0644]
miscutils/timeout.c [new file with mode: 0644]
miscutils/ttysize.c [new file with mode: 0644]
miscutils/watchdog.c [new file with mode: 0644]
modutils/Config.in [new file with mode: 0644]
modutils/Kbuild [new file with mode: 0644]
modutils/depmod.c [new file with mode: 0644]
modutils/insmod.c [new file with mode: 0644]
modutils/lsmod.c [new file with mode: 0644]
modutils/modprobe-small.c [new file with mode: 0644]
modutils/modprobe.c [new file with mode: 0644]
modutils/modutils-24.c [new file with mode: 0644]
modutils/modutils.c [new file with mode: 0644]
modutils/modutils.h [new file with mode: 0644]
modutils/rmmod.c [new file with mode: 0644]
networking/Config.in [new file with mode: 0644]
networking/Kbuild [new file with mode: 0644]
networking/arp.c [new file with mode: 0644]
networking/arping.c [new file with mode: 0644]
networking/brctl.c [new file with mode: 0644]
networking/dnsd.c [new file with mode: 0644]
networking/ether-wake.c [new file with mode: 0644]
networking/ftpd.c [new file with mode: 0644]
networking/ftpgetput.c [new file with mode: 0644]
networking/hostname.c [new file with mode: 0644]
networking/httpd.c [new file with mode: 0644]
networking/httpd_indexcgi.c [new file with mode: 0644]
networking/httpd_post_upload.txt [new file with mode: 0644]
networking/ifconfig.c [new file with mode: 0644]
networking/ifenslave.c [new file with mode: 0644]
networking/ifupdown.c [new file with mode: 0644]
networking/inetd.c [new file with mode: 0644]
networking/interface.c [new file with mode: 0644]
networking/ip.c [new file with mode: 0644]
networking/ipcalc.c [new file with mode: 0644]
networking/isrv.c [new file with mode: 0644]
networking/isrv.h [new file with mode: 0644]
networking/isrv_identd.c [new file with mode: 0644]
networking/libiproute/Kbuild [new file with mode: 0644]
networking/libiproute/ip_common.h [new file with mode: 0644]
networking/libiproute/ip_parse_common_args.c [new file with mode: 0644]
networking/libiproute/ipaddress.c [new file with mode: 0644]
networking/libiproute/iplink.c [new file with mode: 0644]
networking/libiproute/iproute.c [new file with mode: 0644]
networking/libiproute/iprule.c [new file with mode: 0644]
networking/libiproute/iptunnel.c [new file with mode: 0644]
networking/libiproute/libnetlink.c [new file with mode: 0644]
networking/libiproute/libnetlink.h [new file with mode: 0644]
networking/libiproute/ll_addr.c [new file with mode: 0644]
networking/libiproute/ll_map.c [new file with mode: 0644]
networking/libiproute/ll_map.h [new file with mode: 0644]
networking/libiproute/ll_proto.c [new file with mode: 0644]
networking/libiproute/ll_types.c [new file with mode: 0644]
networking/libiproute/rt_names.c [new file with mode: 0644]
networking/libiproute/rt_names.h [new file with mode: 0644]
networking/libiproute/rtm_map.c [new file with mode: 0644]
networking/libiproute/rtm_map.h [new file with mode: 0644]
networking/libiproute/utils.c [new file with mode: 0644]
networking/libiproute/utils.h [new file with mode: 0644]
networking/nameif.c [new file with mode: 0644]
networking/nc.c [new file with mode: 0644]
networking/nc_bloaty.c [new file with mode: 0644]
networking/netstat.c [new file with mode: 0644]
networking/nslookup.c [new file with mode: 0644]
networking/ping.c [new file with mode: 0644]
networking/pscan.c [new file with mode: 0644]
networking/route.c [new file with mode: 0644]
networking/slattach.c [new file with mode: 0644]
networking/tc.c [new file with mode: 0644]
networking/tcpudp.c [new file with mode: 0644]
networking/tcpudp_perhost.c [new file with mode: 0644]
networking/tcpudp_perhost.h [new file with mode: 0644]
networking/telnet.c [new file with mode: 0644]
networking/telnetd.c [new file with mode: 0644]
networking/telnetd.ctrlSQ.patch [new file with mode: 0644]
networking/tftp.c [new file with mode: 0644]
networking/traceroute.c [new file with mode: 0644]
networking/tunctl.c [new file with mode: 0644]
networking/udhcp/Config.in [new file with mode: 0644]
networking/udhcp/Kbuild [new file with mode: 0644]
networking/udhcp/arpping.c [new file with mode: 0644]
networking/udhcp/clientpacket.c [new file with mode: 0644]
networking/udhcp/clientsocket.c [new file with mode: 0644]
networking/udhcp/common.c [new file with mode: 0644]
networking/udhcp/common.h [new file with mode: 0644]
networking/udhcp/dhcpc.c [new file with mode: 0644]
networking/udhcp/dhcpc.h [new file with mode: 0644]
networking/udhcp/dhcpd.c [new file with mode: 0644]
networking/udhcp/dhcpd.h [new file with mode: 0644]
networking/udhcp/dhcprelay.c [new file with mode: 0644]
networking/udhcp/domain_codec.c [new file with mode: 0644]
networking/udhcp/dumpleases.c [new file with mode: 0644]
networking/udhcp/files.c [new file with mode: 0644]
networking/udhcp/leases.c [new file with mode: 0644]
networking/udhcp/options.c [new file with mode: 0644]
networking/udhcp/options.h [new file with mode: 0644]
networking/udhcp/packet.c [new file with mode: 0644]
networking/udhcp/script.c [new file with mode: 0644]
networking/udhcp/serverpacket.c [new file with mode: 0644]
networking/udhcp/signalpipe.c [new file with mode: 0644]
networking/udhcp/socket.c [new file with mode: 0644]
networking/udhcp/static_leases.c [new file with mode: 0644]
networking/vconfig.c [new file with mode: 0644]
networking/wget.c [new file with mode: 0644]
networking/zcip.c [new file with mode: 0644]
printutils/Config.in [new file with mode: 0644]
printutils/Kbuild [new file with mode: 0644]
printutils/lpd.c [new file with mode: 0644]
printutils/lpr.c [new file with mode: 0644]
procps/Config.in [new file with mode: 0644]
procps/Kbuild [new file with mode: 0644]
procps/free.c [new file with mode: 0644]
procps/fuser.c [new file with mode: 0644]
procps/kill.c [new file with mode: 0644]
procps/nmeter.c [new file with mode: 0644]
procps/pgrep.c [new file with mode: 0644]
procps/pidof.c [new file with mode: 0644]
procps/ps.c [new file with mode: 0644]
procps/ps.posix [new file with mode: 0644]
procps/renice.c [new file with mode: 0644]
procps/sysctl.c [new file with mode: 0644]
procps/top.c [new file with mode: 0644]
procps/uptime.c [new file with mode: 0644]
procps/watch.c [new file with mode: 0644]
runit/Config.in [new file with mode: 0644]
runit/Kbuild [new file with mode: 0644]
runit/chpst.c [new file with mode: 0644]
runit/runit_lib.c [new file with mode: 0644]
runit/runit_lib.h [new file with mode: 0644]
runit/runsv.c [new file with mode: 0644]
runit/runsvdir.c [new file with mode: 0644]
runit/sv.c [new file with mode: 0644]
runit/svlogd.c [new file with mode: 0644]
scripts/Kbuild [new file with mode: 0644]
scripts/Kbuild.include [new file with mode: 0644]
scripts/Makefile.IMA [new file with mode: 0644]
scripts/Makefile.build [new file with mode: 0644]
scripts/Makefile.clean [new file with mode: 0644]
scripts/Makefile.host [new file with mode: 0644]
scripts/Makefile.lib [new file with mode: 0644]
scripts/basic/Makefile [new file with mode: 0644]
scripts/basic/docproc.c [new file with mode: 0644]
scripts/basic/fixdep.c [new file with mode: 0644]
scripts/basic/split-include.c [new file with mode: 0644]
scripts/bb_release [new file with mode: 0755]
scripts/bloat-o-meter [new file with mode: 0755]
scripts/checkhelp.awk [new file with mode: 0755]
scripts/checkstack.pl [new file with mode: 0755]
scripts/cleanup_printf2puts [new file with mode: 0755]
scripts/defconfig [new file with mode: 0644]
scripts/echo.c [new file with mode: 0644]
scripts/find_bad_common_bufsiz [new file with mode: 0755]
scripts/find_stray_common_vars [new file with mode: 0755]
scripts/fix_ws.sh [new file with mode: 0755]
scripts/gcc-version.sh [new file with mode: 0755]
scripts/individual [new file with mode: 0755]
scripts/kconfig/Makefile [new file with mode: 0644]
scripts/kconfig/POTFILES.in [new file with mode: 0644]
scripts/kconfig/check.sh [new file with mode: 0755]
scripts/kconfig/conf.c [new file with mode: 0644]
scripts/kconfig/confdata.c [new file with mode: 0644]
scripts/kconfig/expr.c [new file with mode: 0644]
scripts/kconfig/expr.h [new file with mode: 0644]
scripts/kconfig/gconf.c [new file with mode: 0644]
scripts/kconfig/gconf.glade [new file with mode: 0644]
scripts/kconfig/images.c [new file with mode: 0644]
scripts/kconfig/kconfig_load.c [new file with mode: 0644]
scripts/kconfig/kxgettext.c [new file with mode: 0644]
scripts/kconfig/lex.zconf.c_shipped [new file with mode: 0644]
scripts/kconfig/lkc.h [new file with mode: 0644]
scripts/kconfig/lkc_proto.h [new file with mode: 0644]
scripts/kconfig/lxdialog/BIG.FAT.WARNING [new file with mode: 0644]
scripts/kconfig/lxdialog/Makefile [new file with mode: 0644]
scripts/kconfig/lxdialog/check-lxdialog.sh [new file with mode: 0644]
scripts/kconfig/lxdialog/checklist.c [new file with mode: 0644]
scripts/kconfig/lxdialog/colors.h [new file with mode: 0644]
scripts/kconfig/lxdialog/dialog.h [new file with mode: 0644]
scripts/kconfig/lxdialog/inputbox.c [new file with mode: 0644]
scripts/kconfig/lxdialog/lxdialog.c [new file with mode: 0644]
scripts/kconfig/lxdialog/menubox.c [new file with mode: 0644]
scripts/kconfig/lxdialog/msgbox.c [new file with mode: 0644]
scripts/kconfig/lxdialog/textbox.c [new file with mode: 0644]
scripts/kconfig/lxdialog/util.c [new file with mode: 0644]
scripts/kconfig/lxdialog/yesno.c [new file with mode: 0644]
scripts/kconfig/mconf.c [new file with mode: 0644]
scripts/kconfig/menu.c [new file with mode: 0644]
scripts/kconfig/qconf.cc [new file with mode: 0644]
scripts/kconfig/qconf.h [new file with mode: 0644]
scripts/kconfig/symbol.c [new file with mode: 0644]
scripts/kconfig/util.c [new file with mode: 0644]
scripts/kconfig/zconf.gperf [new file with mode: 0644]
scripts/kconfig/zconf.hash.c_shipped [new file with mode: 0644]
scripts/kconfig/zconf.l [new file with mode: 0644]
scripts/kconfig/zconf.tab.c_shipped [new file with mode: 0644]
scripts/kconfig/zconf.y [new file with mode: 0644]
scripts/memusage [new file with mode: 0755]
scripts/mkconfigs [new file with mode: 0755]
scripts/mkmakefile [new file with mode: 0755]
scripts/objsizes [new file with mode: 0755]
scripts/randomtest [new file with mode: 0755]
scripts/randomtest.loop [new file with mode: 0755]
scripts/sample_pmap [new file with mode: 0755]
scripts/showasm [new file with mode: 0755]
scripts/trylink [new file with mode: 0755]
selinux/Config.in [new file with mode: 0644]
selinux/Kbuild [new file with mode: 0644]
selinux/chcon.c [new file with mode: 0644]
selinux/getenforce.c [new file with mode: 0644]
selinux/getsebool.c [new file with mode: 0644]
selinux/load_policy.c [new file with mode: 0644]
selinux/matchpathcon.c [new file with mode: 0644]
selinux/runcon.c [new file with mode: 0644]
selinux/selinuxenabled.c [new file with mode: 0644]
selinux/sestatus.c [new file with mode: 0644]
selinux/setenforce.c [new file with mode: 0644]
selinux/setfiles.c [new file with mode: 0644]
selinux/setsebool.c [new file with mode: 0644]
shell/Config.in [new file with mode: 0644]
shell/Kbuild [new file with mode: 0644]
shell/README [new file with mode: 0644]
shell/README.job [new file with mode: 0644]
shell/ash.c [new file with mode: 0644]
shell/ash_doc.txt [new file with mode: 0644]
shell/ash_ptr_hack.c [new file with mode: 0644]
shell/ash_test/ash-alias/alias.right [new file with mode: 0644]
shell/ash_test/ash-alias/alias.tests [new file with mode: 0755]
shell/ash_test/ash-arith/README.ash [new file with mode: 0644]
shell/ash_test/ash-arith/arith-bash1.right [new file with mode: 0644]
shell/ash_test/ash-arith/arith-bash1.tests [new file with mode: 0755]
shell/ash_test/ash-arith/arith-for.right [new file with mode: 0644]
shell/ash_test/ash-arith/arith-for.testsx [new file with mode: 0755]
shell/ash_test/ash-arith/arith.right [new file with mode: 0644]
shell/ash_test/ash-arith/arith.tests [new file with mode: 0755]
shell/ash_test/ash-arith/arith1.sub [new file with mode: 0755]
shell/ash_test/ash-arith/arith2.sub [new file with mode: 0755]
shell/ash_test/ash-heredoc/heredoc.right [new file with mode: 0644]
shell/ash_test/ash-heredoc/heredoc.tests [new file with mode: 0755]
shell/ash_test/ash-invert/invert.right [new file with mode: 0644]
shell/ash_test/ash-invert/invert.tests [new file with mode: 0755]
shell/ash_test/ash-misc/last_amp.right [new file with mode: 0644]
shell/ash_test/ash-misc/last_amp.tests [new file with mode: 0755]
shell/ash_test/ash-misc/shift1.right [new file with mode: 0644]
shell/ash_test/ash-misc/shift1.tests [new file with mode: 0755]
shell/ash_test/ash-quoting/dollar_squote_bash1.right [new file with mode: 0644]
shell/ash_test/ash-quoting/dollar_squote_bash1.tests [new file with mode: 0755]
shell/ash_test/ash-read/read_ifs.right [new file with mode: 0644]
shell/ash_test/ash-read/read_ifs.tests [new file with mode: 0755]
shell/ash_test/ash-read/read_n.right [new file with mode: 0644]
shell/ash_test/ash-read/read_n.tests [new file with mode: 0755]
shell/ash_test/ash-read/read_r.right [new file with mode: 0644]
shell/ash_test/ash-read/read_r.tests [new file with mode: 0755]
shell/ash_test/ash-read/read_t.right [new file with mode: 0644]
shell/ash_test/ash-read/read_t.tests [new file with mode: 0755]
shell/ash_test/ash-redir/redir.right [new file with mode: 0644]
shell/ash_test/ash-redir/redir.tests [new file with mode: 0755]
shell/ash_test/ash-redir/redir2.right [new file with mode: 0644]
shell/ash_test/ash-redir/redir2.tests [new file with mode: 0755]
shell/ash_test/ash-redir/redir3.right [new file with mode: 0644]
shell/ash_test/ash-redir/redir3.tests [new file with mode: 0755]
shell/ash_test/ash-redir/redir4.right [new file with mode: 0644]
shell/ash_test/ash-redir/redir4.tests [new file with mode: 0755]
shell/ash_test/ash-redir/redir5.right [new file with mode: 0644]
shell/ash_test/ash-redir/redir5.tests [new file with mode: 0755]
shell/ash_test/ash-redir/redir6.right [new file with mode: 0644]
shell/ash_test/ash-redir/redir6.tests [new file with mode: 0755]
shell/ash_test/ash-signals/reap1.right [new file with mode: 0644]
shell/ash_test/ash-signals/reap1.tests [new file with mode: 0755]
shell/ash_test/ash-signals/signal1.right [new file with mode: 0644]
shell/ash_test/ash-signals/signal1.tests [new file with mode: 0755]
shell/ash_test/ash-signals/signal2.right [new file with mode: 0644]
shell/ash_test/ash-signals/signal2.tests [new file with mode: 0755]
shell/ash_test/ash-signals/signal3.right [new file with mode: 0644]
shell/ash_test/ash-signals/signal3.tests [new file with mode: 0755]
shell/ash_test/ash-standalone/noexec_gets_no_env.right [new file with mode: 0644]
shell/ash_test/ash-standalone/noexec_gets_no_env.tests [new file with mode: 0755]
shell/ash_test/ash-standalone/nofork_trashes_getopt.right [new file with mode: 0644]
shell/ash_test/ash-standalone/nofork_trashes_getopt.tests [new file with mode: 0755]
shell/ash_test/ash-vars/var1.right [new file with mode: 0644]
shell/ash_test/ash-vars/var1.tests [new file with mode: 0755]
shell/ash_test/ash-vars/var2.right [new file with mode: 0644]
shell/ash_test/ash-vars/var2.tests [new file with mode: 0755]
shell/ash_test/ash-vars/var_bash1.right [new file with mode: 0644]
shell/ash_test/ash-vars/var_bash1.tests [new file with mode: 0755]
shell/ash_test/ash-vars/var_bash2.right [new file with mode: 0644]
shell/ash_test/ash-vars/var_bash2.tests [new file with mode: 0755]
shell/ash_test/ash-vars/var_bash3.right [new file with mode: 0644]
shell/ash_test/ash-vars/var_bash3.tests [new file with mode: 0755]
shell/ash_test/ash-vars/var_leak.right [new file with mode: 0644]
shell/ash_test/ash-vars/var_leak.tests [new file with mode: 0755]
shell/ash_test/ash-vars/var_posix1.right [new file with mode: 0644]
shell/ash_test/ash-vars/var_posix1.tests [new file with mode: 0755]
shell/ash_test/printenv.c [new file with mode: 0644]
shell/ash_test/recho.c [new file with mode: 0644]
shell/ash_test/run-all [new file with mode: 0755]
shell/ash_test/zecho.c [new file with mode: 0644]
shell/bbsh.c [new file with mode: 0644]
shell/cttyhack.c [new file with mode: 0644]
shell/hush.c [new file with mode: 0644]
shell/hush_doc.txt [new file with mode: 0644]
shell/hush_leaktool.sh [new file with mode: 0755]
shell/hush_test/hush-arith/arith.right [new file with mode: 0644]
shell/hush_test/hush-arith/arith.tests [new file with mode: 0755]
shell/hush_test/hush-arith/arith1.sub [new file with mode: 0755]
shell/hush_test/hush-arith/arith2.sub [new file with mode: 0755]
shell/hush_test/hush-bugs/and_or_and_backgrounding.right [new file with mode: 0644]
shell/hush_test/hush-bugs/and_or_and_backgrounding.tests [new file with mode: 0755]
shell/hush_test/hush-glob/glob1.right [new file with mode: 0644]
shell/hush_test/hush-glob/glob1.tests [new file with mode: 0755]
shell/hush_test/hush-glob/glob_and_assign.right [new file with mode: 0644]
shell/hush_test/hush-glob/glob_and_assign.tests [new file with mode: 0755]
shell/hush_test/hush-glob/glob_redir.right [new file with mode: 0644]
shell/hush_test/hush-glob/glob_redir.tests [new file with mode: 0755]
shell/hush_test/hush-leak/leak_argv1.right [new file with mode: 0644]
shell/hush_test/hush-leak/leak_argv1.tests [new file with mode: 0755]
shell/hush_test/hush-misc/and-or.right [new file with mode: 0644]
shell/hush_test/hush-misc/and-or.tests [new file with mode: 0755]
shell/hush_test/hush-misc/assignment1.right [new file with mode: 0644]
shell/hush_test/hush-misc/assignment1.tests [new file with mode: 0755]
shell/hush_test/hush-misc/assignment2.rigth [new file with mode: 0644]
shell/hush_test/hush-misc/assignment2.tests [new file with mode: 0755]
shell/hush_test/hush-misc/break1.right [new file with mode: 0644]
shell/hush_test/hush-misc/break1.tests [new file with mode: 0755]
shell/hush_test/hush-misc/break2.right [new file with mode: 0644]
shell/hush_test/hush-misc/break2.tests [new file with mode: 0755]
shell/hush_test/hush-misc/break3.right [new file with mode: 0644]
shell/hush_test/hush-misc/break3.tests [new file with mode: 0755]
shell/hush_test/hush-misc/break4.right [new file with mode: 0644]
shell/hush_test/hush-misc/break4.tests [new file with mode: 0755]
shell/hush_test/hush-misc/break5.right [new file with mode: 0644]
shell/hush_test/hush-misc/break5.tests [new file with mode: 0755]
shell/hush_test/hush-misc/builtin1.right [new file with mode: 0644]
shell/hush_test/hush-misc/builtin1.tests [new file with mode: 0755]
shell/hush_test/hush-misc/case1.right [new file with mode: 0644]
shell/hush_test/hush-misc/case1.tests [new file with mode: 0755]
shell/hush_test/hush-misc/colon.right [new file with mode: 0644]
shell/hush_test/hush-misc/colon.tests [new file with mode: 0755]
shell/hush_test/hush-misc/continue1.right [new file with mode: 0644]
shell/hush_test/hush-misc/continue1.tests [new file with mode: 0755]
shell/hush_test/hush-misc/continue2.right [new file with mode: 0644]
shell/hush_test/hush-misc/continue2.tests [new file with mode: 0755]
shell/hush_test/hush-misc/continue3.right [new file with mode: 0644]
shell/hush_test/hush-misc/continue3.tests [new file with mode: 0755]
shell/hush_test/hush-misc/empty_for.right [new file with mode: 0644]
shell/hush_test/hush-misc/empty_for.tests [new file with mode: 0755]
shell/hush_test/hush-misc/empty_for2.right [new file with mode: 0644]
shell/hush_test/hush-misc/empty_for2.tests [new file with mode: 0755]
shell/hush_test/hush-misc/exec.right [new file with mode: 0644]
shell/hush_test/hush-misc/exec.tests [new file with mode: 0755]
shell/hush_test/hush-misc/exit1.right [new file with mode: 0644]
shell/hush_test/hush-misc/exit1.tests [new file with mode: 0755]
shell/hush_test/hush-misc/export.right [new file with mode: 0644]
shell/hush_test/hush-misc/export.tests [new file with mode: 0755]
shell/hush_test/hush-misc/for_with_bslashes.right [new file with mode: 0644]
shell/hush_test/hush-misc/for_with_bslashes.tests [new file with mode: 0755]
shell/hush_test/hush-misc/for_with_keywords.right [new file with mode: 0644]
shell/hush_test/hush-misc/for_with_keywords.tests [new file with mode: 0755]
shell/hush_test/hush-misc/func1.right [new file with mode: 0644]
shell/hush_test/hush-misc/func1.tests [new file with mode: 0755]
shell/hush_test/hush-misc/func2.right [new file with mode: 0644]
shell/hush_test/hush-misc/func2.tests [new file with mode: 0755]
shell/hush_test/hush-misc/heredoc1.right [new file with mode: 0644]
shell/hush_test/hush-misc/heredoc1.tests [new file with mode: 0755]
shell/hush_test/hush-misc/heredoc2.right [new file with mode: 0644]
shell/hush_test/hush-misc/heredoc2.tests [new file with mode: 0755]
shell/hush_test/hush-misc/heredoc3.right [new file with mode: 0644]
shell/hush_test/hush-misc/heredoc3.tests [new file with mode: 0755]
shell/hush_test/hush-misc/heredoc_huge.right [new file with mode: 0644]
shell/hush_test/hush-misc/heredoc_huge.tests [new file with mode: 0755]
shell/hush_test/hush-misc/if_false_exitcode.right [new file with mode: 0644]
shell/hush_test/hush-misc/if_false_exitcode.tests [new file with mode: 0755]
shell/hush_test/hush-misc/pid.right [new file with mode: 0644]
shell/hush_test/hush-misc/pid.tests [new file with mode: 0755]
shell/hush_test/hush-misc/read.right [new file with mode: 0644]
shell/hush_test/hush-misc/read.tests [new file with mode: 0755]
shell/hush_test/hush-misc/redir1.right [new file with mode: 0644]
shell/hush_test/hush-misc/redir1.tests [new file with mode: 0755]
shell/hush_test/hush-misc/redir2.right [new file with mode: 0644]
shell/hush_test/hush-misc/redir2.tests [new file with mode: 0755]
shell/hush_test/hush-misc/redir3.right [new file with mode: 0644]
shell/hush_test/hush-misc/redir3.tests [new file with mode: 0755]
shell/hush_test/hush-misc/redir4.right [new file with mode: 0644]
shell/hush_test/hush-misc/redir4.tests [new file with mode: 0755]
shell/hush_test/hush-misc/shift.right [new file with mode: 0644]
shell/hush_test/hush-misc/shift.tests [new file with mode: 0755]
shell/hush_test/hush-misc/syntax_err.right [new file with mode: 0644]
shell/hush_test/hush-misc/syntax_err.tests [new file with mode: 0755]
shell/hush_test/hush-misc/syntax_err_negate.right [new file with mode: 0644]
shell/hush_test/hush-misc/syntax_err_negate.tests [new file with mode: 0755]
shell/hush_test/hush-misc/until1.right [new file with mode: 0644]
shell/hush_test/hush-misc/until1.tests [new file with mode: 0755]
shell/hush_test/hush-misc/while1.right [new file with mode: 0644]
shell/hush_test/hush-misc/while1.tests [new file with mode: 0755]
shell/hush_test/hush-misc/while2.right [new file with mode: 0644]
shell/hush_test/hush-misc/while2.tests [new file with mode: 0755]
shell/hush_test/hush-misc/while_in_subshell.right [new file with mode: 0644]
shell/hush_test/hush-misc/while_in_subshell.tests [new file with mode: 0755]
shell/hush_test/hush-parsing/argv0.right [new file with mode: 0644]
shell/hush_test/hush-parsing/argv0.tests [new file with mode: 0755]
shell/hush_test/hush-parsing/escape1.right [new file with mode: 0644]
shell/hush_test/hush-parsing/escape1.tests [new file with mode: 0755]
shell/hush_test/hush-parsing/escape2.right [new file with mode: 0644]
shell/hush_test/hush-parsing/escape2.tests [new file with mode: 0755]
shell/hush_test/hush-parsing/escape3.right [new file with mode: 0644]
shell/hush_test/hush-parsing/escape3.tests [new file with mode: 0755]
shell/hush_test/hush-parsing/escape5.right [new file with mode: 0644]
shell/hush_test/hush-parsing/escape5.tests [new file with mode: 0755]
shell/hush_test/hush-parsing/negate.right [new file with mode: 0644]
shell/hush_test/hush-parsing/negate.tests [new file with mode: 0755]
shell/hush_test/hush-parsing/noeol.right [new file with mode: 0644]
shell/hush_test/hush-parsing/noeol.tests [new file with mode: 0755]
shell/hush_test/hush-parsing/noeol2.right [new file with mode: 0644]
shell/hush_test/hush-parsing/noeol2.tests [new file with mode: 0755]
shell/hush_test/hush-parsing/noeol3.right [new file with mode: 0644]
shell/hush_test/hush-parsing/noeol3.tests [new file with mode: 0755]
shell/hush_test/hush-parsing/process_subst.right [new file with mode: 0644]
shell/hush_test/hush-parsing/process_subst.tests [new file with mode: 0755]
shell/hush_test/hush-parsing/quote1.right [new file with mode: 0644]
shell/hush_test/hush-parsing/quote1.tests [new file with mode: 0755]
shell/hush_test/hush-parsing/quote2.right [new file with mode: 0644]
shell/hush_test/hush-parsing/quote2.tests [new file with mode: 0755]
shell/hush_test/hush-parsing/quote3.right [new file with mode: 0644]
shell/hush_test/hush-parsing/quote3.tests [new file with mode: 0755]
shell/hush_test/hush-parsing/quote4.right [new file with mode: 0644]
shell/hush_test/hush-parsing/quote4.tests [new file with mode: 0755]
shell/hush_test/hush-parsing/redir_space.right [new file with mode: 0644]
shell/hush_test/hush-parsing/redir_space.tests [new file with mode: 0755]
shell/hush_test/hush-parsing/starquoted.right [new file with mode: 0644]
shell/hush_test/hush-parsing/starquoted.tests [new file with mode: 0755]
shell/hush_test/hush-parsing/starquoted2.right [new file with mode: 0644]
shell/hush_test/hush-parsing/starquoted2.tests [new file with mode: 0755]
shell/hush_test/hush-psubst/tick.right [new file with mode: 0644]
shell/hush_test/hush-psubst/tick.tests [new file with mode: 0755]
shell/hush_test/hush-psubst/tick2.right [new file with mode: 0644]
shell/hush_test/hush-psubst/tick2.tests [new file with mode: 0755]
shell/hush_test/hush-psubst/tick3.right [new file with mode: 0644]
shell/hush_test/hush-psubst/tick3.tests [new file with mode: 0755]
shell/hush_test/hush-psubst/tick4.right [new file with mode: 0644]
shell/hush_test/hush-psubst/tick4.tests [new file with mode: 0755]
shell/hush_test/hush-psubst/tick_huge.right [new file with mode: 0644]
shell/hush_test/hush-psubst/tick_huge.tests [new file with mode: 0755]
shell/hush_test/hush-trap/catch.right [new file with mode: 0644]
shell/hush_test/hush-trap/catch.tests [new file with mode: 0755]
shell/hush_test/hush-trap/exit.right [new file with mode: 0644]
shell/hush_test/hush-trap/exit.tests [new file with mode: 0755]
shell/hush_test/hush-trap/save-ret.right [new file with mode: 0644]
shell/hush_test/hush-trap/save-ret.tests [new file with mode: 0755]
shell/hush_test/hush-trap/usage.right [new file with mode: 0644]
shell/hush_test/hush-trap/usage.tests [new file with mode: 0755]
shell/hush_test/hush-vars/empty.right [new file with mode: 0644]
shell/hush_test/hush-vars/empty.tests [new file with mode: 0755]
shell/hush_test/hush-vars/glob_and_vars.right [new file with mode: 0644]
shell/hush_test/hush-vars/glob_and_vars.tests [new file with mode: 0755]
shell/hush_test/hush-vars/param_expand_alt.right [new file with mode: 0644]
shell/hush_test/hush-vars/param_expand_alt.tests [new file with mode: 0755]
shell/hush_test/hush-vars/param_expand_assign.right [new file with mode: 0644]
shell/hush_test/hush-vars/param_expand_assign.tests [new file with mode: 0755]
shell/hush_test/hush-vars/param_expand_default.right [new file with mode: 0644]
shell/hush_test/hush-vars/param_expand_default.tests [new file with mode: 0755]
shell/hush_test/hush-vars/param_expand_indicate_error.right [new file with mode: 0644]
shell/hush_test/hush-vars/param_expand_indicate_error.tests [new file with mode: 0755]
shell/hush_test/hush-vars/param_expand_len.right [new file with mode: 0644]
shell/hush_test/hush-vars/param_expand_len.tests [new file with mode: 0755]
shell/hush_test/hush-vars/param_glob.right [new file with mode: 0644]
shell/hush_test/hush-vars/param_glob.tests [new file with mode: 0755]
shell/hush_test/hush-vars/param_subshell.right [new file with mode: 0644]
shell/hush_test/hush-vars/param_subshell.tests [new file with mode: 0755]
shell/hush_test/hush-vars/star.right [new file with mode: 0644]
shell/hush_test/hush-vars/star.tests [new file with mode: 0755]
shell/hush_test/hush-vars/unset.right [new file with mode: 0644]
shell/hush_test/hush-vars/unset.tests [new file with mode: 0755]
shell/hush_test/hush-vars/var1.right [new file with mode: 0644]
shell/hush_test/hush-vars/var1.tests [new file with mode: 0755]
shell/hush_test/hush-vars/var2.right [new file with mode: 0644]
shell/hush_test/hush-vars/var2.tests [new file with mode: 0755]
shell/hush_test/hush-vars/var3.right [new file with mode: 0644]
shell/hush_test/hush-vars/var3.tests [new file with mode: 0755]
shell/hush_test/hush-vars/var_expand_in_assign.right [new file with mode: 0644]
shell/hush_test/hush-vars/var_expand_in_assign.tests [new file with mode: 0755]
shell/hush_test/hush-vars/var_expand_in_redir.right [new file with mode: 0644]
shell/hush_test/hush-vars/var_expand_in_redir.tests [new file with mode: 0755]
shell/hush_test/hush-vars/var_in_pipes.right [new file with mode: 0644]
shell/hush_test/hush-vars/var_in_pipes.tests [new file with mode: 0755]
shell/hush_test/hush-vars/var_leaks.right [new file with mode: 0644]
shell/hush_test/hush-vars/var_leaks.tests [new file with mode: 0755]
shell/hush_test/hush-vars/var_posix1.right [new file with mode: 0644]
shell/hush_test/hush-vars/var_posix1.tests [new file with mode: 0755]
shell/hush_test/hush-vars/var_preserved.right [new file with mode: 0644]
shell/hush_test/hush-vars/var_preserved.tests [new file with mode: 0755]
shell/hush_test/hush-vars/var_subst_in_for.right [new file with mode: 0644]
shell/hush_test/hush-vars/var_subst_in_for.tests [new file with mode: 0755]
shell/hush_test/hush-z_slow/leak_all1.right [new file with mode: 0644]
shell/hush_test/hush-z_slow/leak_all1.tests [new file with mode: 0755]
shell/hush_test/hush-z_slow/leak_all2.right [new file with mode: 0644]
shell/hush_test/hush-z_slow/leak_all2.tests [new file with mode: 0755]
shell/hush_test/hush-z_slow/leak_heredoc1.right [new file with mode: 0644]
shell/hush_test/hush-z_slow/leak_heredoc1.tests [new file with mode: 0755]
shell/hush_test/hush-z_slow/leak_var.right [new file with mode: 0644]
shell/hush_test/hush-z_slow/leak_var.tests [new file with mode: 0755]
shell/hush_test/hush-z_slow/leak_var2.right [new file with mode: 0644]
shell/hush_test/hush-z_slow/leak_var2.tests [new file with mode: 0755]
shell/hush_test/hush-z_slow/leak_var3.right [new file with mode: 0644]
shell/hush_test/hush-z_slow/leak_var3.tests [new file with mode: 0755]
shell/hush_test/run-all [new file with mode: 0755]
shell/lash_unused.c [new file with mode: 0644]
shell/match.c [new file with mode: 0644]
shell/match.h [new file with mode: 0644]
shell/math.c [new file with mode: 0644]
shell/math.h [new file with mode: 0644]
shell/msh.c [new file with mode: 0644]
shell/msh_function.patch [new file with mode: 0644]
shell/msh_test/msh-bugs/noeol3.right [new file with mode: 0644]
shell/msh_test/msh-bugs/noeol3.tests [new file with mode: 0755]
shell/msh_test/msh-bugs/process_subst.right [new file with mode: 0644]
shell/msh_test/msh-bugs/process_subst.tests [new file with mode: 0755]
shell/msh_test/msh-bugs/read.right [new file with mode: 0644]
shell/msh_test/msh-bugs/read.tests [new file with mode: 0755]
shell/msh_test/msh-bugs/shift.right [new file with mode: 0644]
shell/msh_test/msh-bugs/shift.tests [new file with mode: 0755]
shell/msh_test/msh-bugs/starquoted.right [new file with mode: 0644]
shell/msh_test/msh-bugs/starquoted.tests [new file with mode: 0755]
shell/msh_test/msh-bugs/syntax_err.right [new file with mode: 0644]
shell/msh_test/msh-bugs/syntax_err.tests [new file with mode: 0755]
shell/msh_test/msh-bugs/var_expand_in_assign.right [new file with mode: 0644]
shell/msh_test/msh-bugs/var_expand_in_assign.tests [new file with mode: 0755]
shell/msh_test/msh-bugs/var_expand_in_redir.right [new file with mode: 0644]
shell/msh_test/msh-bugs/var_expand_in_redir.tests [new file with mode: 0755]
shell/msh_test/msh-execution/exitcode_EACCES.right [new file with mode: 0644]
shell/msh_test/msh-execution/exitcode_EACCES.tests [new file with mode: 0755]
shell/msh_test/msh-execution/exitcode_ENOENT.right [new file with mode: 0644]
shell/msh_test/msh-execution/exitcode_ENOENT.tests [new file with mode: 0755]
shell/msh_test/msh-execution/many_continues.right [new file with mode: 0644]
shell/msh_test/msh-execution/many_continues.tests [new file with mode: 0755]
shell/msh_test/msh-execution/nested_break.right [new file with mode: 0644]
shell/msh_test/msh-execution/nested_break.tests [new file with mode: 0755]
shell/msh_test/msh-misc/tick.right [new file with mode: 0644]
shell/msh_test/msh-misc/tick.tests [new file with mode: 0755]
shell/msh_test/msh-parsing/argv0.right [new file with mode: 0644]
shell/msh_test/msh-parsing/argv0.tests [new file with mode: 0755]
shell/msh_test/msh-parsing/noeol.right [new file with mode: 0644]
shell/msh_test/msh-parsing/noeol.tests [new file with mode: 0755]
shell/msh_test/msh-parsing/noeol2.right [new file with mode: 0644]
shell/msh_test/msh-parsing/noeol2.tests [new file with mode: 0755]
shell/msh_test/msh-parsing/quote1.right [new file with mode: 0644]
shell/msh_test/msh-parsing/quote1.tests [new file with mode: 0755]
shell/msh_test/msh-parsing/quote2.right [new file with mode: 0644]
shell/msh_test/msh-parsing/quote2.tests [new file with mode: 0755]
shell/msh_test/msh-parsing/quote3.right [new file with mode: 0644]
shell/msh_test/msh-parsing/quote3.tests [new file with mode: 0755]
shell/msh_test/msh-parsing/quote4.right [new file with mode: 0644]
shell/msh_test/msh-parsing/quote4.tests [new file with mode: 0755]
shell/msh_test/msh-vars/star.right [new file with mode: 0644]
shell/msh_test/msh-vars/star.tests [new file with mode: 0755]
shell/msh_test/msh-vars/var.right [new file with mode: 0644]
shell/msh_test/msh-vars/var.tests [new file with mode: 0755]
shell/msh_test/msh-vars/var_subst_in_for.right [new file with mode: 0644]
shell/msh_test/msh-vars/var_subst_in_for.tests [new file with mode: 0755]
shell/msh_test/run-all [new file with mode: 0755]
shell/susv3_doc.tar.bz2 [new file with mode: 0644]
sysklogd/Config.in [new file with mode: 0644]
sysklogd/Kbuild [new file with mode: 0644]
sysklogd/klogd.c [new file with mode: 0644]
sysklogd/logger.c [new file with mode: 0644]
sysklogd/logread.c [new file with mode: 0644]
sysklogd/syslogd.c [new file with mode: 0644]
sysklogd/syslogd_and_logger.c [new file with mode: 0644]
testsuite/README [new file with mode: 0644]
testsuite/TODO [new file with mode: 0644]
testsuite/all_sourcecode.tests [new file with mode: 0755]
testsuite/awk.tests [new file with mode: 0755]
testsuite/awk_t1.tar.bz2 [new file with mode: 0644]
testsuite/basename/basename-does-not-remove-identical-extension [new file with mode: 0644]
testsuite/basename/basename-works [new file with mode: 0644]
testsuite/bunzip2.tests [new file with mode: 0755]
testsuite/bunzip2/bunzip2-reads-from-standard-input [new file with mode: 0644]
testsuite/bunzip2/bunzip2-removes-compressed-file [new file with mode: 0644]
testsuite/bunzip2/bzcat-does-not-remove-compressed-file [new file with mode: 0644]
testsuite/busybox.tests [new file with mode: 0755]
testsuite/bzcat.tests [new file with mode: 0755]
testsuite/cat/cat-prints-a-file [new file with mode: 0644]
testsuite/cat/cat-prints-a-file-and-standard-input [new file with mode: 0644]
testsuite/cmp/cmp-detects-difference [new file with mode: 0644]
testsuite/comm.tests [new file with mode: 0755]
testsuite/cp/cp-RHL-does_not_preserve-links [new file with mode: 0644]
testsuite/cp/cp-a-files-to-dir [new file with mode: 0644]
testsuite/cp/cp-a-preserves-links [new file with mode: 0644]
testsuite/cp/cp-copies-empty-file [new file with mode: 0644]
testsuite/cp/cp-copies-large-file [new file with mode: 0644]
testsuite/cp/cp-copies-small-file [new file with mode: 0644]
testsuite/cp/cp-d-files-to-dir [new file with mode: 0644]
testsuite/cp/cp-dev-file [new file with mode: 0644]
testsuite/cp/cp-dir-create-dir [new file with mode: 0644]
testsuite/cp/cp-dir-existing-dir [new file with mode: 0644]
testsuite/cp/cp-does-not-copy-unreadable-file [new file with mode: 0644]
testsuite/cp/cp-files-to-dir [new file with mode: 0644]
testsuite/cp/cp-follows-links [new file with mode: 0644]
testsuite/cp/cp-preserves-hard-links [new file with mode: 0644]
testsuite/cp/cp-preserves-links [new file with mode: 0644]
testsuite/cp/cp-preserves-source-file [new file with mode: 0644]
testsuite/cpio.tests [new file with mode: 0755]
testsuite/cut.tests [new file with mode: 0755]
testsuite/cut/cut-cuts-a-character [new file with mode: 0644]
testsuite/cut/cut-cuts-a-closed-range [new file with mode: 0644]
testsuite/cut/cut-cuts-a-field [new file with mode: 0644]
testsuite/cut/cut-cuts-an-open-range [new file with mode: 0644]
testsuite/cut/cut-cuts-an-unclosed-range [new file with mode: 0644]
testsuite/date/date-R-works [new file with mode: 0644]
testsuite/date/date-format-works [new file with mode: 0644]
testsuite/date/date-u-works [new file with mode: 0644]
testsuite/date/date-works [new file with mode: 0644]
testsuite/date/date-works-1 [new file with mode: 0644]
testsuite/dd/dd-accepts-if [new file with mode: 0644]
testsuite/dd/dd-accepts-of [new file with mode: 0644]
testsuite/dd/dd-copies-from-standard-input-to-standard-output [new file with mode: 0644]
testsuite/dd/dd-prints-count-to-standard-error [new file with mode: 0644]
testsuite/dd/dd-reports-write-errors [new file with mode: 0644]
testsuite/diff.tests [new file with mode: 0755]
testsuite/dirname/dirname-handles-absolute-path [new file with mode: 0644]
testsuite/dirname/dirname-handles-empty-path [new file with mode: 0644]
testsuite/dirname/dirname-handles-multiple-slashes [new file with mode: 0644]
testsuite/dirname/dirname-handles-relative-path [new file with mode: 0644]
testsuite/dirname/dirname-handles-root [new file with mode: 0644]
testsuite/dirname/dirname-handles-single-component [new file with mode: 0644]
testsuite/dirname/dirname-works [new file with mode: 0644]
testsuite/du/du-h-works [new file with mode: 0644]
testsuite/du/du-k-works [new file with mode: 0644]
testsuite/du/du-l-works [new file with mode: 0644]
testsuite/du/du-m-works [new file with mode: 0644]
testsuite/du/du-s-works [new file with mode: 0644]
testsuite/du/du-works [new file with mode: 0644]
testsuite/echo/echo-does-not-print-newline [new file with mode: 0644]
testsuite/echo/echo-prints-argument [new file with mode: 0644]
testsuite/echo/echo-prints-arguments [new file with mode: 0644]
testsuite/echo/echo-prints-newline [new file with mode: 0644]
testsuite/echo/echo-prints-slash-zero [new file with mode: 0644]
testsuite/expand.tests [new file with mode: 0755]
testsuite/expand/expand-works-like-GNU [new file with mode: 0644]
testsuite/expr/expr-big [new file with mode: 0644]
testsuite/expr/expr-works [new file with mode: 0644]
testsuite/false/false-is-silent [new file with mode: 0644]
testsuite/false/false-returns-failure [new file with mode: 0644]
testsuite/find/find-supports-minus-xdev [new file with mode: 0644]
testsuite/grep.tests [new file with mode: 0755]
testsuite/gunzip.tests [new file with mode: 0755]
testsuite/gunzip/gunzip-reads-from-standard-input [new file with mode: 0644]
testsuite/gzip/gzip-accepts-multiple-files [new file with mode: 0644]
testsuite/gzip/gzip-accepts-single-minus [new file with mode: 0644]
testsuite/gzip/gzip-removes-original-file [new file with mode: 0644]
testsuite/head/head-n-works [new file with mode: 0644]
testsuite/head/head-works [new file with mode: 0644]
testsuite/hostid/hostid-works [new file with mode: 0644]
testsuite/hostname/hostname-d-works [new file with mode: 0644]
testsuite/hostname/hostname-i-works [new file with mode: 0644]
testsuite/hostname/hostname-s-works [new file with mode: 0644]
testsuite/hostname/hostname-works [new file with mode: 0644]
testsuite/id/id-g-works [new file with mode: 0644]
testsuite/id/id-u-works [new file with mode: 0644]
testsuite/id/id-un-works [new file with mode: 0644]
testsuite/id/id-ur-works [new file with mode: 0644]
testsuite/ln/ln-creates-hard-links [new file with mode: 0644]
testsuite/ln/ln-creates-soft-links [new file with mode: 0644]
testsuite/ln/ln-force-creates-hard-links [new file with mode: 0644]
testsuite/ln/ln-force-creates-soft-links [new file with mode: 0644]
testsuite/ln/ln-preserves-hard-links [new file with mode: 0644]
testsuite/ln/ln-preserves-soft-links [new file with mode: 0644]
testsuite/ls/ls-1-works [new file with mode: 0644]
testsuite/ls/ls-h-works [new file with mode: 0644]
testsuite/ls/ls-l-works [new file with mode: 0644]
testsuite/ls/ls-s-works [new file with mode: 0644]
testsuite/makedevs.device_table.txt [new file with mode: 0644]
testsuite/makedevs.tests [new file with mode: 0755]
testsuite/md5sum/md5sum-verifies-non-binary-file [new file with mode: 0644]
testsuite/mdev.tests [new file with mode: 0755]
testsuite/mkdir/mkdir-makes-a-directory [new file with mode: 0644]
testsuite/mkdir/mkdir-makes-parent-directories [new file with mode: 0644]
testsuite/mkfs.minix.tests [new file with mode: 0755]
testsuite/mount.testroot [new file with mode: 0755]
testsuite/mount.tests [new file with mode: 0755]
testsuite/msh/msh-supports-underscores-in-variable-names [new file with mode: 0644]
testsuite/mv/mv-files-to-dir [new file with mode: 0644]
testsuite/mv/mv-follows-links [new file with mode: 0644]
testsuite/mv/mv-moves-empty-file [new file with mode: 0644]
testsuite/mv/mv-moves-file [new file with mode: 0644]
testsuite/mv/mv-moves-hardlinks [new file with mode: 0644]
testsuite/mv/mv-moves-large-file [new file with mode: 0644]
testsuite/mv/mv-moves-small-file [new file with mode: 0644]
testsuite/mv/mv-moves-symlinks [new file with mode: 0644]
testsuite/mv/mv-moves-unreadable-files [new file with mode: 0644]
testsuite/mv/mv-preserves-hard-links [new file with mode: 0644]
testsuite/mv/mv-preserves-links [new file with mode: 0644]
testsuite/mv/mv-refuses-mv-dir-to-subdir [new file with mode: 0644]
testsuite/mv/mv-removes-source-file [new file with mode: 0644]
testsuite/od.tests [new file with mode: 0755]
testsuite/parse.tests [new file with mode: 0755]
testsuite/patch.tests [new file with mode: 0755]
testsuite/pidof.tests [new file with mode: 0755]
testsuite/printf.tests [new file with mode: 0755]
testsuite/pwd/pwd-prints-working-directory [new file with mode: 0644]
testsuite/readlink.tests [new file with mode: 0755]
testsuite/rm/rm-removes-file [new file with mode: 0644]
testsuite/rmdir/rmdir-removes-parent-directories [new file with mode: 0644]
testsuite/runtest [new file with mode: 0755]
testsuite/sed.tests [new file with mode: 0755]
testsuite/seq.tests [new file with mode: 0755]
testsuite/sort.tests [new file with mode: 0755]
testsuite/start-stop-daemon.tests [new file with mode: 0755]
testsuite/strings/strings-works-like-GNU [new file with mode: 0644]
testsuite/sum.tests [new file with mode: 0755]
testsuite/tail/tail-n-works [new file with mode: 0644]
testsuite/tail/tail-works [new file with mode: 0644]
testsuite/tar/tar-archives-multiple-files [new file with mode: 0644]
testsuite/tar/tar-complains-about-missing-file [new file with mode: 0644]
testsuite/tar/tar-demands-at-least-one-ctx [new file with mode: 0644]
testsuite/tar/tar-demands-at-most-one-ctx [new file with mode: 0644]
testsuite/tar/tar-extracts-all-subdirs [new file with mode: 0644]
testsuite/tar/tar-extracts-file [new file with mode: 0644]
testsuite/tar/tar-extracts-from-standard-input [new file with mode: 0644]
testsuite/tar/tar-extracts-multiple-files [new file with mode: 0644]
testsuite/tar/tar-extracts-to-standard-output [new file with mode: 0644]
testsuite/tar/tar-handles-cz-options [new file with mode: 0644]
testsuite/tar/tar-handles-empty-include-and-non-empty-exclude-list [new file with mode: 0644]
testsuite/tar/tar-handles-exclude-and-extract-lists [new file with mode: 0644]
testsuite/tar/tar-handles-multiple-X-options [new file with mode: 0644]
testsuite/tar/tar-handles-nested-exclude [new file with mode: 0644]
testsuite/tar/tar_with_link_with_size [new file with mode: 0644]
testsuite/tar/tar_with_prefix_fields [new file with mode: 0644]
testsuite/taskset.tests [new file with mode: 0755]
testsuite/tee/tee-appends-input [new file with mode: 0644]
testsuite/tee/tee-tees-input [new file with mode: 0644]
testsuite/test.tests [new file with mode: 0755]
testsuite/testing.sh [new file with mode: 0755]
testsuite/touch/touch-creates-file [new file with mode: 0644]
testsuite/touch/touch-does-not-create-file [new file with mode: 0644]
testsuite/touch/touch-touches-files-after-non-existent-file [new file with mode: 0644]
testsuite/tr.tests [new file with mode: 0644]
testsuite/tr/tr-d-alnum-works [new file with mode: 0644]
testsuite/tr/tr-d-works [new file with mode: 0644]
testsuite/tr/tr-non-gnu [new file with mode: 0644]
testsuite/tr/tr-rejects-wrong-class [new file with mode: 0644]
testsuite/tr/tr-works [new file with mode: 0644]
testsuite/true/true-is-silent [new file with mode: 0644]
testsuite/true/true-returns-success [new file with mode: 0644]
testsuite/umlwrapper.sh [new file with mode: 0755]
testsuite/unexpand.tests [new file with mode: 0755]
testsuite/unexpand/unexpand-works-like-GNU [new file with mode: 0644]
testsuite/uniq.tests [new file with mode: 0755]
testsuite/unzip.tests [new file with mode: 0755]
testsuite/uptime/uptime-works [new file with mode: 0644]
testsuite/uuencode.tests [new file with mode: 0755]
testsuite/wc/wc-counts-all [new file with mode: 0644]
testsuite/wc/wc-counts-characters [new file with mode: 0644]
testsuite/wc/wc-counts-lines [new file with mode: 0644]
testsuite/wc/wc-counts-words [new file with mode: 0644]
testsuite/wc/wc-prints-longest-line-length [new file with mode: 0644]
testsuite/wget/wget--O-overrides--P [new file with mode: 0644]
testsuite/wget/wget-handles-empty-path [new file with mode: 0644]
testsuite/wget/wget-retrieves-google-index [new file with mode: 0644]
testsuite/wget/wget-supports--P [new file with mode: 0644]
testsuite/which/which-uses-default-path [new file with mode: 0644]
testsuite/xargs.tests [new file with mode: 0755]
testsuite/xargs/xargs-works [new file with mode: 0644]
util-linux/Config.in [new file with mode: 0644]
util-linux/Kbuild [new file with mode: 0644]
util-linux/acpid.c [new file with mode: 0644]
util-linux/blkid.c [new file with mode: 0644]
util-linux/dmesg.c [new file with mode: 0644]
util-linux/fbset.c [new file with mode: 0644]
util-linux/fdformat.c [new file with mode: 0644]
util-linux/fdisk.c [new file with mode: 0644]
util-linux/fdisk_aix.c [new file with mode: 0644]
util-linux/fdisk_osf.c [new file with mode: 0644]
util-linux/fdisk_sgi.c [new file with mode: 0644]
util-linux/fdisk_sun.c [new file with mode: 0644]
util-linux/findfs.c [new file with mode: 0644]
util-linux/freeramdisk.c [new file with mode: 0644]
util-linux/fsck_minix.c [new file with mode: 0644]
util-linux/getopt.c [new file with mode: 0644]
util-linux/hexdump.c [new file with mode: 0644]
util-linux/hwclock.c [new file with mode: 0644]
util-linux/ipcrm.c [new file with mode: 0644]
util-linux/ipcs.c [new file with mode: 0644]
util-linux/losetup.c [new file with mode: 0644]
util-linux/mdev.c [new file with mode: 0644]
util-linux/minix.h [new file with mode: 0644]
util-linux/mkfs_minix.c [new file with mode: 0644]
util-linux/mkfs_vfat.c [new file with mode: 0644]
util-linux/mkswap.c [new file with mode: 0644]
util-linux/more.c [new file with mode: 0644]
util-linux/mount.c [new file with mode: 0644]
util-linux/pivot_root.c [new file with mode: 0644]
util-linux/rdate.c [new file with mode: 0644]
util-linux/rdev.c [new file with mode: 0644]
util-linux/readprofile.c [new file with mode: 0644]
util-linux/rtcwake.c [new file with mode: 0644]
util-linux/script.c [new file with mode: 0644]
util-linux/setarch.c [new file with mode: 0644]
util-linux/swaponoff.c [new file with mode: 0644]
util-linux/switch_root.c [new file with mode: 0644]
util-linux/umount.c [new file with mode: 0644]
util-linux/volume_id/Kbuild [new file with mode: 0644]
util-linux/volume_id/cramfs.c [new file with mode: 0644]
util-linux/volume_id/ext.c [new file with mode: 0644]
util-linux/volume_id/fat.c [new file with mode: 0644]
util-linux/volume_id/get_devname.c [new file with mode: 0644]
util-linux/volume_id/hfs.c [new file with mode: 0644]
util-linux/volume_id/iso9660.c [new file with mode: 0644]
util-linux/volume_id/jfs.c [new file with mode: 0644]
util-linux/volume_id/linux_raid.c [new file with mode: 0644]
util-linux/volume_id/linux_swap.c [new file with mode: 0644]
util-linux/volume_id/luks.c [new file with mode: 0644]
util-linux/volume_id/ntfs.c [new file with mode: 0644]
util-linux/volume_id/ocfs2.c [new file with mode: 0644]
util-linux/volume_id/reiserfs.c [new file with mode: 0644]
util-linux/volume_id/romfs.c [new file with mode: 0644]
util-linux/volume_id/sysv.c [new file with mode: 0644]
util-linux/volume_id/udf.c [new file with mode: 0644]
util-linux/volume_id/unused_highpoint.c [new file with mode: 0644]
util-linux/volume_id/unused_hpfs.c [new file with mode: 0644]
util-linux/volume_id/unused_isw_raid.c [new file with mode: 0644]
util-linux/volume_id/unused_lsi_raid.c [new file with mode: 0644]
util-linux/volume_id/unused_lvm.c [new file with mode: 0644]
util-linux/volume_id/unused_mac.c [new file with mode: 0644]
util-linux/volume_id/unused_minix.c [new file with mode: 0644]
util-linux/volume_id/unused_msdos.c [new file with mode: 0644]
util-linux/volume_id/unused_nvidia_raid.c [new file with mode: 0644]
util-linux/volume_id/unused_promise_raid.c [new file with mode: 0644]
util-linux/volume_id/unused_silicon_raid.c [new file with mode: 0644]
util-linux/volume_id/unused_ufs.c [new file with mode: 0644]
util-linux/volume_id/unused_via_raid.c [new file with mode: 0644]
util-linux/volume_id/util.c [new file with mode: 0644]
util-linux/volume_id/volume_id.c [new file with mode: 0644]
util-linux/volume_id/volume_id_internal.h [new file with mode: 0644]
util-linux/volume_id/xfs.c [new file with mode: 0644]

diff --git a/.indent.pro b/.indent.pro
new file mode 100644 (file)
index 0000000..492ecf1
--- /dev/null
@@ -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 (file)
index 0000000..378c332
--- /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 <psyphreak@phreaker.net>
+    eject
+
+Emanuele Aina <emanuele.aina@tiscali.it>
+    run-parts
+
+Erik Andersen <andersen@codepoet.org>
+    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 <l.d.anderson@warwick.ac.uk>
+    rpm2cpio, unzip, get_header_cpio, read_gz interface, rpm
+
+Jeff Angielski <jeff@theptrgroup.com>
+    ftpput, ftpget
+
+Enrik Berkhan <Enrik.Berkhan@inka.de>
+    setconsole
+
+Jim Bauer <jfbauer@nfr.com>
+    modprobe shell dependency
+
+Edward Betts <edward@debian.org>
+    expr, hostid, logname, whoami
+
+John Beppu <beppu@codepoet.org>
+    du, nslookup, sort
+
+David Brownell <dbrownell@users.sourceforge.net>
+    zcip
+
+Brian Candler <B.Candler@pobox.com>
+    tiny-ls(ls)
+
+Randolph Chung <tausq@debian.org>
+    fbset, ping, hostname
+
+Dave Cinege <dcinege@psychosis.com>
+    more(v2), makedevs, dutmp, modularization, auto links file,
+    various fixes, Linux Router Project maintenance
+
+Jordan Crouse <jordan@cosmicpenguin.net>
+    ipcalc
+
+Magnus Damm <damm@opensource.se>
+    tftp client
+    insmod powerpc support
+
+Larry Doolittle <ldoolitt@recycle.lbl.gov>
+    pristine source directory compilation, lots of patches and fixes.
+
+Glenn Engel <glenne@engel.org>
+    httpd
+
+Gennady Feldman <gfeldman@gena01.com>
+    Sysklogd (single threaded syslogd, IPC Circular buffer support,
+    logread), various fixes.
+
+Robert Griebl <sandman@handhelds.org>
+    modprobe, hwclock, suid/sgid handling, tinylogin integration
+    many bugfixes and enhancements
+
+Karl M. Hegbloom <karlheg@debian.org>
+    cp_mv.c, the test suite, various fixes to utility.c, &c.
+
+Daniel Jacobowitz <dan@debian.org>
+    mktemp.c
+
+Matt Kraai <kraai@alumni.cmu.edu>
+    documentation, bugfixes, test suite
+
+Rob Landley <rob@landley.net>
+    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 <linz@li-pro.net>
+    ipcalc, Red Hat equivalence
+
+John Lombardo <john@deltanet.com>
+    tr
+
+Glenn McGrath <glenn.l.mcgrath@gmail.com>
+    Common unarchiving code and unarchiving applets, ifupdown, ftpgetput,
+    nameif, sed, patch, fold, install, uudecode.
+    Various bugfixes, review and apply numerous patches.
+
+Manuel Novoa III <mjn3@codepoet.org>
+    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 <dzo@simtreas.ru>
+    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 <bruce@pixar.com>
+    Original author of BusyBox in 1995, 1996. Some of his code can
+    still be found hiding here and there...
+
+Rodney Radford <rradford@mindspring.com>
+    ipcs, ipcrm
+
+Tim Riker <Tim@Rikers.org>
+    bug fixes, member of fan club
+
+Kent Robotti <robotti@metconnect.com>
+    reset, tons and tons of bug reports and patches.
+
+Chip Rosenthal <chip@unicom.com>, <crosenth@covad.com>
+    wget - Contributed by permission of Covad Communications
+
+Pavel Roskin <proski@gnu.org>
+    Lots of bugs fixes and patches.
+
+Gyepi Sam <gyepi@praxis-sw.com>
+    Remote logging feature for syslogd
+
+Rob Sullivan <cogito.ergo.cogito@gmail.com>
+    comm
+
+Linus Torvalds
+    mkswap, fsck.minix, mkfs.minix
+
+Mark Whitley <markw@codepoet.org>
+    grep, sed, cut, xargs(previous),
+    style-guide, new-applet-HOWTO, bug fixes, etc.
+
+Charles P. Wright <cpwright@villagenet.com>
+    gzip, mini-netcat(nc)
+
+Enrique Zanardi <ezanardi@ull.es>
+    tarcat (since removed), loadkmap, various fixes, Debian maintenance
+
+Tito Ragusa <farmatito@tiscali.it>
+    devfsd and size optimizations in strings, openvt, chvt, deallocvt, hdparm,
+    fdformat, lsattr, chattr, id and eject.
+
+Paul Fox <pgf@foxharp.boston.ma.us>
+    vi editing mode for ash, various other patches/fixes
+
+Roberto A. Foglietta <me@roberto.foglietta.name>
+    port: dnsd
+
+Bernhard Reutner-Fischer <rep.dot.nop@gmail.com>
+    misc
+
+Mike Frysinger <vapier@gentoo.org>
+    initial e2fsprogs, printenv, setarch, sum, misc
diff --git a/Config.in b/Config.in
new file mode 100644 (file)
index 0000000..fff6d83
--- /dev/null
+++ b/Config.in
@@ -0,0 +1,610 @@
+#
+# 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 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.
+
+config EXTRA_COMPAT
+       bool "Provide compatible behavior for rare corner cases (bigger code)"
+       default n
+       help
+         This option makes grep, sed etc handle rare corner cases
+         (embedded NUL bytes and such). This makes code bigger and uses
+         some GNU extensions in libc. You probably only need this option
+         if you plan to run busybox on desktop.
+
+config FEATURE_ASSUME_UNICODE
+       bool "Assume that 1:1 char/glyph correspondence is not true"
+       default n
+       help
+         This makes various applets aware that one byte is not
+         one character on screen.
+
+         Busybox aims to eventually work correctly with Unicode displays.
+         Any older encodings are not guaranteed to work.
+         Probably by the time when busybox will be fully Unicode-clean,
+         other encodings will be mainly of historic interest.
+
+choice
+       prompt "Buffer allocation policy"
+       default FEATURE_BUFFERS_USE_MALLOC
+       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 <applet> --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.
+
+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 "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/<number> for the slave side. Otherwise, BSD style
+         /dev/ttyp<number> 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
+       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_PIDFILE
+       bool "Support writing pidfiles"
+       default n
+       help
+         This option makes some applets (e.g. crond, syslogd, inetd) write
+         a pidfile in /var/run. Some applications rely on them.
+
+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 will automatically drop
+         priviledges for applets that don't need root access.
+
+         If you are 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:
+
+         crontab, dnsd, findfs, ipcrm, ipcs, login, passwd, ping, su,
+         traceroute, vlock.
+
+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:
+
+         <applet> = [Ssx-][Ssx-][x-] (<username>|<uid>).(<groupname>|<gid>)
+
+         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:
+         <url: http://www.softforge.de/bb/suid.html >.
+
+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 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<libselinux-include-path> \
+               LDFLAGS=-L<libselinux-lib-path> \
+               make
+
+         Most people will leave this set to 'N'.
+
+config FEATURE_PREFER_APPLETS
+       bool "exec prefers applets"
+       default n
+       help
+         This is an experimental option which directs applets about to
+         call 'exec' to try and find an applicable busybox applet before
+         searching the PATH. This is typically done by exec'ing
+         /proc/self/exe.
+         This may affect shell, find -exec, xargs and similar applets.
+         They will use applets even if /bin/<applet> -> busybox link
+         is missing (or is not a link to busybox). However, this causes
+         problems in chroot jails without mounted /proc and with ps/top
+         (command name can be shown as 'exe' for applets started this way).
+
+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.
+
+# These are auto-selected by other options
+
+config FEATURE_SYSLOG
+       bool #No description makes it a hidden option
+       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_HAVE_RPC
+       bool #No description makes it a hidden option
+       default n
+       #help
+       #  This is automatically selected if any of enabled applets need it.
+       #  You do not need to select it manually.
+
+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 PIE
+       bool "Build BusyBox as a position independent executable"
+       default n
+       depends on !STATIC
+       help
+         (TODO: what is it and why/when is it useful?)
+         Most people will leave this set to 'N'.
+
+config NOMMU
+       bool "Force NOMMU build"
+       default n
+       help
+         Busybox tries to detect whether architecture it is being
+         built against supports MMU or not. If this detection fails,
+         or if you want to build NOMMU version of busybox for testing,
+         you may force NOMMU build here.
+
+         Most people will leave this set to 'N'.
+
+# PIE can be made to work with BUILD_LIBBUSYBOX, but currently
+# build system does not support that
+config BUILD_LIBBUSYBOX
+       bool "Build shared libbusybox"
+       default n
+       depends on !FEATURE_PREFER_APPLETS && !PIE && !STATIC
+       help
+         Build a shared library libbusybox.so.N.N.N which contains all
+         busybox code.
+
+         This feature allows every applet to be built as a tiny
+         separate executable. Enabling it for "one big busybox binary"
+         approach serves no purpose and increases code size.
+         You should almost certainly say "no" to this.
+
+### 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_INDIVIDUAL
+       bool "Produce a binary for each applet, linked against libbusybox"
+       default y
+       depends on BUILD_LIBBUSYBOX
+       help
+         If your CPU architecture doesn't allow for sharing text/rodata
+         sections of running binaries, but allows for runtime dynamic
+         libraries, this option will allow you to reduce memory footprint
+         when you have many different applets running at once.
+
+         If your CPU architecture allows for sharing text/rodata,
+         having single binary is more optimal.
+
+         Each applet will be a tiny program, dynamically linked
+         against libbusybox.so.N.N.N.
+
+         You need to have a working dynamic linker.
+
+config FEATURE_SHARED_BUSYBOX
+       bool "Produce additional busybox binary linked against libbusybox"
+       default y
+       depends on BUILD_LIBBUSYBOX
+       help
+         Build busybox, dynamically linked against libbusybox.so.N.N.N.
+
+         You need to have a working dynamic linker.
+
+### 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.
+
+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 CROSS_COMPILER_PREFIX
+       string "Cross Compiler prefix"
+       default ""
+       help
+         If you want to build BusyBox with a cross compiler, then you
+         will need to set this to the cross-compiler prefix, for example,
+         "i386-uclibc-".
+
+         Note that CROSS_COMPILE environment variable or
+         "make CROSS_COMPILE=xxx ..." will override this selection.
+
+         Native builds leave this empty.
+
+config EXTRA_CFLAGS
+       string "Additional CFLAGS"
+       default ""
+       help
+         Additional CFLAGS to pass to the compiler verbatim.
+
+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.
+
+config WERROR
+       bool "Abort compilation on any warning"
+       default n
+       help
+         Selecting this will add -Werror to gcc command line.
+
+         Most people should answer N.
+
+choice
+       prompt "Additional debugging library"
+       default NO_DEBUG_LIB
+       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 INCLUDE_SUSv2
+       bool "Enable obsolete features removed before SUSv3?"
+       default y
+       help
+         This option will enable backwards compatibility with SuSv2,
+         specifically, old-style numeric options ('command -1 <file>')
+         will be supported in head, tail, and fold. (Note: should
+         affect renice too.)
+
+### config PARSE
+###    bool "Uniform config file parser debugging applet: parse"
+
+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_SCRIPT_WRAPPERS
+       bool "as script wrappers"
+       help
+         Install applets as script wrappers that call the busybox binary.
+
+config INSTALL_APPLET_DONT
+       bool "not installed"
+       depends on FEATURE_INSTALLER || FEATURE_SH_STANDALONE || FEATURE_PREFER_APPLETS
+       help
+         Do not install applet links. Useful when using the -install feature
+         or a standalone shell for rescue purposes.
+
+endchoice
+
+choice
+       prompt "/bin/sh applet link"
+       default INSTALL_SH_APPLET_SYMLINK
+       depends on INSTALL_APPLET_SCRIPT_WRAPPERS
+       help
+         Choose how you install /bin/sh applet link.
+
+config INSTALL_SH_APPLET_SYMLINK
+       bool "as soft-link"
+       help
+         Install /bin/sh applet as soft-link to the busybox binary.
+
+config INSTALL_SH_APPLET_HARDLINK
+       bool "as hard-link"
+       help
+         Install /bin/sh applet as hard-link to the busybox binary.
+
+config INSTALL_SH_APPLET_SCRIPT_WRAPPER
+       bool "as script wrapper"
+       help
+         Install /bin/sh applet as script wrapper that call the busybox
+         binary.
+
+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 printutils/Config.in
+source mailutils/Config.in
+source procps/Config.in
+source runit/Config.in
+source selinux/Config.in
+source shell/Config.in
+source sysklogd/Config.in
diff --git a/INSTALL b/INSTALL
new file mode 100644 (file)
index 0000000..a7902ab
--- /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 CONFIG_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 CONFIG_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 (file)
index 0000000..9d9bdc7
--- /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.
+\f
+                   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.)
+\f
+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.
+\f
+  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.
+\f
+  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
+\f
+           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.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    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.
+
+  <signature of Ty Coon>, 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 (file)
index 0000000..9e1bd87
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,1319 @@
+VERSION = 1
+PATCHLEVEL = 14
+SUBLEVEL = 3
+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
+
+
+# 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
+
+CROSS_COMPILE ?=
+# bbox: we may have CONFIG_CROSS_COMPILER_PREFIX in .config,
+# and it has not been included yet... thus using an awkward syntax.
+ifeq ($(CROSS_COMPILE),)
+CROSS_COMPILE := $(shell grep ^CONFIG_CROSS_COMPILER_PREFIX .config 2>/dev/null)
+CROSS_COMPILE := $(subst CONFIG_CROSS_COMPILER_PREFIX=,,$(CROSS_COMPILE))
+CROSS_COMPILE := $(subst ",,$(CROSS_COMPILE))
+#")
+endif
+
+# 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.
+
+ifneq ($(CROSS_COMPILE),)
+SUBARCH := $(shell echo $(CROSS_COMPILE) | cut -d- -f1)
+else
+SUBARCH := $(shell uname -m)
+endif
+SUBARCH := $(shell echo $(SUBARCH) | 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/ )
+
+ARCH ?= $(SUBARCH)
+
+# 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)
+
+#      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 <whatever> 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)
+
+HOSTCC         = gcc
+HOSTCXX        = g++
+HOSTCFLAGS     :=
+HOSTCXXFLAGS   :=
+# We need some generic definitions
+include $(srctree)/scripts/Kbuild.include
+
+HOSTCFLAGS     += $(call hostcc-option,-Wall -Wstrict-prototypes -O2 -fomit-frame-pointer,)
+HOSTCXXFLAGS   += -O2
+
+# For maximum performance (+ possibly random breakage, uncomment
+# the following)
+
+MAKEFLAGS += -rR
+
+# Make variables (CC, etc...)
+
+AS             = $(CROSS_COMPILE)as
+CC             = $(CROSS_COMPILE)gcc
+LD             = $(CC) -nostdlib
+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         := $(CFLAGS)
+# Added only to final link stage of busybox binary
+CFLAGS_busybox := $(CFLAGS_busybox)
+CPPFLAGS       := $(CPPFLAGS)
+AFLAGS         := $(AFLAGS)
+LDFLAGS                := $(LDFLAGS)
+LDLIBS         :=
+
+# 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
+export FLTFLAGS
+
+# 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
+#bbox# check% is removed from above
+
+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/ \
+               editors/ \
+               findutils/ \
+               init/ \
+               libbb/ \
+               libpwdgrp/ \
+               loginutils/ \
+               mailutils/ \
+               miscutils/ \
+               modutils/ \
+               networking/ \
+               networking/libiproute/ \
+               networking/udhcp/ \
+               printutils/ \
+               procps/ \
+               runit/ \
+               selinux/ \
+               shell/ \
+               sysklogd/ \
+               util-linux/ \
+               util-linux/volume_id/ \
+
+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 overridden in the arch makefile
+all: busybox doc
+
+-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)
+
+# 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    $@
+      cmd_busybox__ ?= $(srctree)/scripts/trylink \
+      "$@" \
+      "$(CC)" \
+      "$(CFLAGS) $(CFLAGS_busybox)" \
+      "$(LDFLAGS) $(EXTRA_LDFLAGS)" \
+      "$(core-y)" \
+      "$(libs-y)" \
+      "$(LDLIBS)"
+
+# 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
+ifeq ($(SKIP_STRIP),y)
+       $(Q)cp $< $@
+else
+       $(Q)$(STRIP) -s --remove-section=.note --remove-section=.comment \
+               busybox_unstripped -o $@
+# strip is confused by PIE executable and does not set exec bits
+       $(Q)chmod a+x $@
+endif
+
+# 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 _BB_DISABLED_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 busybox'
+       $(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/*
+quiet_cmd_gen_bbconfigopts = GEN     include/bbconfigopts.h
+      cmd_gen_bbconfigopts = $(srctree)/scripts/mkconfigs > include/bbconfigopts.h
+quiet_cmd_split_autoconf   = SPLIT   include/autoconf.h -> include/config/*
+      cmd_split_autoconf   = scripts/basic/split-include include/autoconf.h include/config
+#bbox# piggybacked generation of few .h files
+include/config/MARKER: scripts/basic/split-include include/autoconf.h
+       $(call cmd,split_autoconf)
+       $(call cmd,gen_bbconfigopts)
+       @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 _BB_DISABLED_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) _install 0_lib
+CLEAN_FILES += busybox busybox_unstripped* busybox.links \
+                System.map .kernelrelease \
+                .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 \
+                 include/applet_tables.h \
+                 applets/usage \
+                 .kernelrelease Module.symvers tags TAGS cscope* \
+                 busybox_old
+
+# 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
+
+PHONY += doc-clean
+doc-clean: rm-files := docs/busybox.pod \
+                 docs/BusyBox.html docs/BusyBox.1 docs/BusyBox.txt
+doc-clean:
+       $(call cmd,rmfiles)
+
+# 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 -name '*.tmp' -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 (file)
index 0000000..d9a2367
--- /dev/null
@@ -0,0 +1,171 @@
+# ==========================================================================
+# Build system
+# ==========================================================================
+
+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
+ifeq ($(CONFIG_INSTALL_APPLET_SCRIPT_WRAPPERS),y)
+ifeq ($(CONFIG_INSTALL_SH_APPLET_SYMLINK),y)
+INSTALL_OPTS:= --sw-sh-sym
+endif
+ifeq ($(CONFIG_INSTALL_SH_APPLET_HARDLINK),y)
+INSTALL_OPTS:= --sw-sh-hard
+endif
+ifeq ($(CONFIG_INSTALL_SH_APPLET_SCRIPT_WRAPPER),y)
+INSTALL_OPTS:= --scriptwrapper
+endif
+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
+
+# Not very elegant: copies testsuite to objdir...
+# (cp -pPR is POSIX-compliant (cp -dpR or cp -a would not be))
+.PHONY: check
+.PHONY: test
+check test: busybox busybox.links
+       test -d $(objtree)/testsuite || cp -pPR $(srctree)/testsuite $(objtree)
+       bindir=$(objtree) srcdir=$(srctree)/testsuite \
+       $(SHELL) -c "cd $(objtree)/testsuite && $(srctree)/testsuite/runtest $(if $(KBUILD_VERBOSE:0=),-v)"
+
+.PHONY: release
+release: distclean
+       cd ..; \
+       rm -r -f busybox-$(VERSION).$(PATCHLEVEL).$(SUBLEVEL)$(EXTRAVERSION); \
+       cp -pPR 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
+       @$(CROSS_COMPILE)size busybox_old busybox_unstripped
+
+.PHONY: baseline
+baseline: busybox_unstripped
+       @mv busybox_unstripped busybox_old
+
+.PHONY: objsizes
+objsizes: busybox_unstripped
+       $(srctree)/scripts/objsizes
+
+.PHONY: stksizes
+stksizes: busybox_unstripped
+       $(CROSS_COMPILE)objdump -d busybox_unstripped | $(srctree)/scripts/checkstack.pl $(ARCH) | uniq
+
+.PHONY: bigdata
+bigdata: busybox_unstripped
+       $(CROSS_COMPILE)nm --size-sort busybox_unstripped | grep -vi ' [trw] '
+
+# Documentation Targets
+.PHONY: doc
+doc: docs/busybox.pod docs/BusyBox.txt docs/BusyBox.1 docs/BusyBox.html
+
+# FIXME: Doesn't belong here
+       cmd_doc =
+ quiet_cmd_doc = $(Q)echo "  DOC     $(@F)"
+silent_cmd_doc =
+disp_doc       = $($(quiet)cmd_doc)
+
+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 $@ $^
+
+-include $(srctree)/Makefile.local
diff --git a/Makefile.flags b/Makefile.flags
new file mode 100644 (file)
index 0000000..2109fdf
--- /dev/null
@@ -0,0 +1,122 @@
+# ==========================================================================
+# Build system
+# ==========================================================================
+
+BB_VER = $(VERSION).$(PATCHLEVEL).$(SUBLEVEL)$(EXTRAVERSION)
+export BB_VER
+SKIP_STRIP = n
+
+# -std=gnu99 needed for [U]LLONG_MAX on some systems
+CPPFLAGS += $(call cc-option,-std=gnu99,)
+
+CPPFLAGS += \
+       -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 += $(call cc-option,-Wall,)
+CFLAGS += $(call cc-option,-Wshadow,)
+CFLAGS += $(call cc-option,-Wwrite-strings,)
+CFLAGS += $(call cc-option,-Wundef,)
+CFLAGS += $(call cc-option,-Wstrict-prototypes,)
+CFLAGS += $(call cc-option,-Wunused -Wunused-parameter,)
+CFLAGS += $(call cc-option,-Wunused-function -Wunused-value,)
+CFLAGS += $(call cc-option,-Wmissing-prototypes -Wmissing-declarations,)
+# warn about C99 declaration after statement
+CFLAGS += $(call cc-option,-Wdeclaration-after-statement,)
+# If you want to add more -Wsomething above, make sure that it is
+# still possible to build bbox without warnings.
+
+ifeq ($(CONFIG_WERROR),y)
+CFLAGS += $(call cc-option,-Werror,)
+endif
+# gcc 3.x emits bogus "old style proto" warning on find.c:alloc_action()
+CFLAGS += $(call cc-ifversion, -ge, 0400, -Wold-style-definition)
+
+CFLAGS += $(call cc-option,-fno-builtin-strlen -finline-limit=0 -fomit-frame-pointer -ffunction-sections -fdata-sections,)
+# -fno-guess-branch-probability: prohibit pseudo-random guessing
+# of branch probabilities (hopefully makes bloatcheck more stable):
+CFLAGS += $(call cc-option,-fno-guess-branch-probability,)
+CFLAGS += $(call cc-option,-funsigned-char -static-libgcc,)
+CFLAGS += $(call cc-option,-falign-functions=1 -falign-jumps=1 -falign-labels=1 -falign-loops=1,)
+
+# FIXME: These warnings are at least partially to be concerned about and should
+# be fixed..
+#CFLAGS += $(call cc-option,-Wconversion,)
+
+ifneq ($(CONFIG_DEBUG),y)
+CFLAGS += $(call cc-option,-Os,)
+else
+CFLAGS += $(call cc-option,-g,)
+#CFLAGS += "-D_FORTIFY_SOURCE=2"
+ifeq ($(CONFIG_DEBUG_PESSIMIZE),y)
+CFLAGS += $(call cc-option,-O0,)
+else
+CFLAGS += $(call cc-option,-Os,)
+endif
+endif
+
+# If arch/$(ARCH)/Makefile did not override it (with, say, -fPIC)...
+ARCH_FPIC ?= -fpic
+ARCH_FPIE ?= -fpie
+ARCH_PIE ?= -pie
+
+ifeq ($(CONFIG_BUILD_LIBBUSYBOX),y)
+# on i386: 14% smaller libbusybox.so
+# (code itself is 9% bigger, we save on relocs/PLT/GOT)
+CFLAGS += $(ARCH_FPIC)
+# and another 4% reduction of libbusybox.so:
+# (external entry points must be marked EXTERNALLY_VISIBLE)
+CFLAGS += $(call cc-option,-fvisibility=hidden)
+endif
+
+ifeq ($(CONFIG_STATIC),y)
+CFLAGS_busybox += -static
+endif
+
+ifeq ($(CONFIG_PIE),y)
+CFLAGS_busybox += $(ARCH_PIE)
+CFLAGS += $(ARCH_FPIE)
+endif
+
+ifneq ($(CONFIG_EXTRA_CFLAGS),)
+CFLAGS += $(strip $(subst ",,$(CONFIG_EXTRA_CFLAGS)))
+#"))
+endif
+
+LDLIBS += m crypt
+
+ifeq ($(CONFIG_PAM),y)
+LDLIBS += pam pam_misc
+endif
+
+ifeq ($(CONFIG_SELINUX),y)
+LDLIBS += selinux sepol
+endif
+
+ifeq ($(CONFIG_EFENCE),y)
+LDLIBS += efence
+endif
+
+ifeq ($(CONFIG_DMALLOC),y)
+LDLIBS += dmalloc
+endif
+
+# If a flat binary should be built, CFLAGS_busybox="-elf2flt"
+# env var should be set for make invocation.
+# Here we check whether CFLAGS_busybox indeed contains that flag.
+# (For historical reasons, we also check LDFLAGS, which doesn't
+# seem to be entirely correct variable to put "-elf2flt" into).
+W_ELF2FLT = -elf2flt
+ifneq (,$(findstring $(W_ELF2FLT),$(LDFLAGS) $(CFLAGS_busybox)))
+SKIP_STRIP = y
+endif
+
+# Busybox is a stack-fatty so make sure we increase default size
+# TODO: use "make stksizes" to find & fix big stack users
+# (we stole scripts/checkstack.pl from the kernel... thanks guys!)
+# Reduced from 20k to 16k in 1.9.0.
+FLTFLAGS += -s 16000
diff --git a/Makefile.help b/Makefile.help
new file mode 100644 (file)
index 0000000..999d029
--- /dev/null
@@ -0,0 +1,44 @@
+# ==========================================================================
+# Build system
+# ==========================================================================
+
+help:
+       @echo 'Cleaning:'
+       @echo '  clean                  - delete temporary files created by build'
+       @echo '  distclean              - delete all non-source files (including .config)'
+       @echo '  doc-clean              - delete all generated documentation'
+       @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 '  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 CONFIG_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 '  bigdata                - show data objects, biggest first'
+       @echo '  stksizes               - show stack users, biggest first'
+       @echo
diff --git a/README b/README
new file mode 100644 (file)
index 0000000..4049044
--- /dev/null
+++ b/README
@@ -0,0 +1,199 @@
+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 CONFIG_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
+  (https://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.
+
+  Note: if you want to compile busybox in a busybox environment you must
+  select ENABLE_DESKTOP.
+
+----------------
+
+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.
+
+       -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.
+
+  In 2005 Shaun Jackman has ported busybox to a combination of newlib
+  and libgloss, and some of his patches have been integrated.
+
+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:
+       Denys Vlasenko
+        <vda.linux@googlemail.com>
diff --git a/TODO b/TODO
new file mode 100644 (file)
index 0000000..d6a60d1
--- /dev/null
+++ b/TODO
@@ -0,0 +1,285 @@
+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 suggested these:
+  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()
+
+  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 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.
+    lash is phased out. hush can be configured down to be nearly as small,
+    but less buggy :)
+  init
+    General cleanup (should use ENABLE_FEATURE_INIT_SYSLOG).
+  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 Reutner-Fischer <busybox@busybox.net> suggests to look at these:
+  New debug options:
+    -Wlarger-than-127
+    Cleanup any big users
+  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!
+
+As yet unclaimed:
+
+----
+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
+---
+ar
+  Write support!
+---
+stty / catv
+  stty's visible() function and catv's guts are identical. Merge them into
+  an appropriate libbb function.
+---
+struct suffix_mult
+  Several duplicate users of: grep -r "1024\*1024" * -B2 -A1
+  Merge to a single size_suffixes[] in libbb.
+  Users: head tail od_bloaty hexdump and (partially as it wouldn't hurt) svlogd
+---
+tail
+  ./busybox tail -f foo.c~ TODO
+  should not print fmt=header_fmt for subsequent date >> TODO; i.e. only
+  fmt+ if another (not the current) file did change
+
+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.  Use open_read_close().
+---
+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"
+---
+  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
+---
+  See grep -r strtod
+  Alot of duplication that wants cleanup.
+---
+  in_ether duplicated in network/{interface,ifconfig}.c
+---
+  unify progress_meter. wget, flash_eraseall, pipe_progress, fbsplash, setfiles.
+
+
+Code cleanup:
+
+Replace deprecated functions.
+
+---
+vdprintf() -> similar sized functionality
+---
diff --git a/TODO_config_nommu b/TODO_config_nommu
new file mode 100644 (file)
index 0000000..2061bfd
--- /dev/null
@@ -0,0 +1,872 @@
+#
+# Automatically generated make config: don't edit
+# Busybox version: 1.13.0.svn
+# Sun Nov  9 17:09:13 2008
+#
+CONFIG_HAVE_DOT_CONFIG=y
+
+#
+# Busybox Settings
+#
+
+#
+# General Configuration
+#
+CONFIG_DESKTOP=y
+CONFIG_EXTRA_COMPAT=y
+# CONFIG_FEATURE_ASSUME_UNICODE is not set
+CONFIG_FEATURE_BUFFERS_USE_MALLOC=y
+# 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 is not set
+CONFIG_GETOPT_LONG=y
+CONFIG_FEATURE_DEVPTS=y
+# CONFIG_FEATURE_CLEAN_UP is not set
+CONFIG_FEATURE_PIDFILE=y
+CONFIG_FEATURE_SUID=y
+CONFIG_FEATURE_SUID_CONFIG=y
+CONFIG_FEATURE_SUID_CONFIG_QUIET=y
+CONFIG_SELINUX=y
+CONFIG_FEATURE_PREFER_APPLETS=y
+CONFIG_BUSYBOX_EXEC_PATH="/proc/self/exe"
+CONFIG_FEATURE_SYSLOG=y
+CONFIG_FEATURE_HAVE_RPC=y
+
+#
+# Build Options
+#
+# CONFIG_STATIC is not set
+# CONFIG_PIE is not set
+CONFIG_NOMMU=y
+# CONFIG_BUILD_LIBBUSYBOX is not set
+# CONFIG_FEATURE_INDIVIDUAL is not set
+# CONFIG_FEATURE_SHARED_BUSYBOX is not set
+CONFIG_LFS=y
+CONFIG_CROSS_COMPILER_PREFIX=""
+
+#
+# Debugging Options
+#
+# CONFIG_DEBUG is not set
+# CONFIG_DEBUG_PESSIMIZE is not set
+# CONFIG_WERROR is not set
+CONFIG_NO_DEBUG_LIB=y
+# CONFIG_DMALLOC is not set
+# CONFIG_EFENCE is not set
+CONFIG_INCLUDE_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_SCRIPT_WRAPPERS is not set
+# CONFIG_INSTALL_APPLET_DONT is not set
+# CONFIG_INSTALL_SH_APPLET_SYMLINK is not set
+# CONFIG_INSTALL_SH_APPLET_HARDLINK is not set
+# CONFIG_INSTALL_SH_APPLET_SCRIPT_WRAPPER is not set
+CONFIG_PREFIX="./_install"
+
+#
+# Busybox Library Tuning
+#
+CONFIG_PASSWORD_MINLEN=6
+CONFIG_MD5_SIZE_VS_SPEED=2
+CONFIG_FEATURE_FAST_TOP=y
+CONFIG_FEATURE_ETC_NETWORKS=y
+CONFIG_FEATURE_EDITING=y
+CONFIG_FEATURE_EDITING_MAX_LEN=1024
+CONFIG_FEATURE_EDITING_VI=y
+CONFIG_FEATURE_EDITING_HISTORY=15
+# CONFIG_FEATURE_EDITING_SAVEHISTORY is not set
+CONFIG_FEATURE_TAB_COMPLETION=y
+CONFIG_FEATURE_USERNAME_COMPLETION=y
+CONFIG_FEATURE_EDITING_FANCY_PROMPT=y
+CONFIG_FEATURE_VERBOSE_CP_MESSAGE=y
+CONFIG_FEATURE_COPYBUF_KB=4
+CONFIG_MONOTONIC_SYSCALL=y
+CONFIG_IOCTL_HEX2STR_ERROR=y
+CONFIG_FEATURE_HWIB=y
+
+#
+# Applets
+#
+
+#
+# Archival Utilities
+#
+CONFIG_FEATURE_SEAMLESS_LZMA=y
+CONFIG_FEATURE_SEAMLESS_BZ2=y
+CONFIG_FEATURE_SEAMLESS_GZ=y
+CONFIG_FEATURE_SEAMLESS_Z=y
+CONFIG_AR=y
+CONFIG_FEATURE_AR_LONG_FILENAMES=y
+CONFIG_BUNZIP2=y
+CONFIG_BZIP2=y
+CONFIG_CPIO=y
+CONFIG_FEATURE_CPIO_O=y
+CONFIG_FEATURE_CPIO_P=y
+CONFIG_DPKG=y
+CONFIG_DPKG_DEB=y
+CONFIG_FEATURE_DPKG_DEB_EXTRACT_ONLY=y
+CONFIG_GUNZIP=y
+CONFIG_GZIP=y
+CONFIG_RPM2CPIO=y
+CONFIG_RPM=y
+CONFIG_TAR=y
+CONFIG_FEATURE_TAR_CREATE=y
+CONFIG_FEATURE_TAR_AUTODETECT=y
+CONFIG_FEATURE_TAR_FROM=y
+CONFIG_FEATURE_TAR_OLDGNU_COMPATIBILITY=y
+CONFIG_FEATURE_TAR_OLDSUN_COMPATIBILITY=y
+CONFIG_FEATURE_TAR_GNU_EXTENSIONS=y
+CONFIG_FEATURE_TAR_LONG_OPTIONS=y
+CONFIG_FEATURE_TAR_UNAME_GNAME=y
+CONFIG_UNCOMPRESS=y
+CONFIG_UNLZMA=y
+CONFIG_FEATURE_LZMA_FAST=y
+CONFIG_UNZIP=y
+
+#
+# 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_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_FEATURE_DF_FANCY=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_EXPAND=y
+CONFIG_FEATURE_EXPAND_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_READLINK=y
+CONFIG_FEATURE_READLINK_FOLLOW=y
+CONFIG_REALPATH=y
+CONFIG_RM=y
+CONFIG_RMDIR=y
+CONFIG_FEATURE_RMDIR_LONG_OPTIONS=y
+CONFIG_SEQ=y
+CONFIG_SHA1SUM=y
+CONFIG_SLEEP=y
+CONFIG_FEATURE_FANCY_SLEEP=y
+CONFIG_FEATURE_FLOAT_SLEEP=y
+CONFIG_SORT=y
+CONFIG_FEATURE_SORT_BIG=y
+CONFIG_SPLIT=y
+CONFIG_FEATURE_SPLIT_FANCY=y
+CONFIG_STAT=y
+CONFIG_FEATURE_STAT_FORMAT=y
+CONFIG_STTY=y
+CONFIG_SUM=y
+CONFIG_SYNC=y
+CONFIG_TAC=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_UNEXPAND=y
+CONFIG_FEATURE_UNEXPAND_LONG_OPTIONS=y
+CONFIG_UNIQ=y
+CONFIG_USLEEP=y
+CONFIG_UUDECODE=y
+CONFIG_UUENCODE=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_KBD_MODE=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=y
+CONFIG_SETFONT=y
+CONFIG_FEATURE_SETFONT_TEXTUAL_MAP=y
+CONFIG_DEFAULT_SETFONT_DIR=""
+CONFIG_SETKEYCODES=y
+CONFIG_SETLOGCONS=y
+CONFIG_SHOWKEY=y
+
+#
+# Debian Utilities
+#
+CONFIG_MKTEMP=y
+CONFIG_PIPE_PROGRESS=y
+CONFIG_RUN_PARTS=y
+CONFIG_FEATURE_RUN_PARTS_LONG_OPTIONS=y
+CONFIG_FEATURE_RUN_PARTS_FANCY=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_LIBM=y
+CONFIG_CMP=y
+CONFIG_DIFF=y
+CONFIG_FEATURE_DIFF_BINARY=y
+CONFIG_FEATURE_DIFF_DIR=y
+CONFIG_FEATURE_DIFF_MINIMAL=y
+CONFIG_ED=y
+CONFIG_PATCH=y
+CONFIG_SED=y
+CONFIG_VI=y
+CONFIG_FEATURE_VI_MAX_LEN=4096
+CONFIG_FEATURE_VI_8BIT=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
+CONFIG_FEATURE_ALLOW_EXEC=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_MAXDEPTH=y
+CONFIG_FEATURE_FIND_NEWER=y
+CONFIG_FEATURE_FIND_INUM=y
+CONFIG_FEATURE_FIND_EXEC=y
+CONFIG_FEATURE_FIND_USER=y
+CONFIG_FEATURE_FIND_GROUP=y
+CONFIG_FEATURE_FIND_NOT=y
+CONFIG_FEATURE_FIND_DEPTH=y
+CONFIG_FEATURE_FIND_PAREN=y
+CONFIG_FEATURE_FIND_SIZE=y
+CONFIG_FEATURE_FIND_PRUNE=y
+CONFIG_FEATURE_FIND_DELETE=y
+CONFIG_FEATURE_FIND_PATH=y
+CONFIG_FEATURE_FIND_REGEX=y
+CONFIG_FEATURE_FIND_CONTEXT=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_FEATURE_USE_INITTAB=y
+CONFIG_FEATURE_KILL_REMOVED=y
+CONFIG_FEATURE_KILL_DELAY=1
+CONFIG_FEATURE_INIT_SCTTY=y
+CONFIG_FEATURE_INIT_SYSLOG=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_PWD_GRP=y
+CONFIG_USE_BB_SHADOW=y
+CONFIG_USE_BB_CRYPT=y
+CONFIG_ADDGROUP=y
+CONFIG_FEATURE_ADDUSER_TO_GROUP=y
+CONFIG_DELGROUP=y
+CONFIG_FEATURE_DEL_USER_FROM_GROUP=y
+CONFIG_FEATURE_CHECK_NAMES=y
+CONFIG_ADDUSER=y
+CONFIG_FEATURE_ADDUSER_LONG_OPTIONS=y
+CONFIG_DELUSER=y
+CONFIG_GETTY=y
+CONFIG_FEATURE_UTMP=y
+CONFIG_FEATURE_WTMP=y
+CONFIG_LOGIN=y
+# CONFIG_PAM is not set
+CONFIG_LOGIN_SCRIPTS=y
+CONFIG_FEATURE_NOLOGIN=y
+CONFIG_FEATURE_SECURETTY=y
+CONFIG_PASSWD=y
+CONFIG_FEATURE_PASSWD_WEAK_CHECK=y
+CONFIG_CRYPTPW=y
+CONFIG_CHPASSWD=y
+CONFIG_SU=y
+CONFIG_FEATURE_SU_SYSLOG=y
+CONFIG_FEATURE_SU_CHECKS_SHELLS=y
+CONFIG_SULOGIN=y
+CONFIG_VLOCK=y
+
+#
+# Linux Ext2 FS Progs
+#
+CONFIG_CHATTR=y
+CONFIG_FSCK=y
+CONFIG_LSATTR=y
+
+#
+# Linux Module Utilities
+#
+CONFIG_DEFAULT_MODULES_DIR="/lib/modules"
+CONFIG_DEFAULT_DEPMOD_FILE="modules.dep"
+CONFIG_MODPROBE_SMALL=y
+CONFIG_FEATURE_MODPROBE_SMALL_OPTIONS_ON_CMDLINE=y
+CONFIG_FEATURE_MODPROBE_SMALL_CHECK_ALREADY_LOADED=y
+# CONFIG_INSMOD is not set
+# CONFIG_RMMOD is not set
+# CONFIG_LSMOD is not set
+# CONFIG_FEATURE_LSMOD_PRETTY_2_6_OUTPUT is not set
+# CONFIG_MODPROBE is not set
+# CONFIG_FEATURE_MODPROBE_BLACKLIST is not set
+# CONFIG_DEPMOD is not set
+
+#
+# Options common to multiple modutils
+#
+# CONFIG_FEATURE_2_4_MODULES is not set
+# CONFIG_FEATURE_INSMOD_VERSION_CHECKING is not set
+# CONFIG_FEATURE_INSMOD_KSYMOOPS_SYMBOLS is not set
+# CONFIG_FEATURE_INSMOD_LOADINKMEM is not set
+# CONFIG_FEATURE_INSMOD_LOAD_MAP is not set
+# CONFIG_FEATURE_INSMOD_LOAD_MAP_FULL is not set
+# CONFIG_FEATURE_CHECK_TAINTED_MODULE is not set
+# CONFIG_FEATURE_MODUTILS_ALIAS is not set
+# CONFIG_FEATURE_MODUTILS_SYMBOLS is not set
+
+#
+# Linux System Utilities
+#
+CONFIG_BLKID=y
+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=y
+CONFIG_FEATURE_SGI_LABEL=y
+CONFIG_FEATURE_SUN_LABEL=y
+CONFIG_FEATURE_OSF_LABEL=y
+CONFIG_FEATURE_FDISK_ADVANCED=y
+CONFIG_FINDFS=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_FEATURE_HEXDUMP_REVERSE=y
+CONFIG_HD=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_RENAME=y
+CONFIG_FEATURE_MDEV_RENAME_REGEXP=y
+CONFIG_FEATURE_MDEV_EXEC=y
+CONFIG_FEATURE_MDEV_LOAD_FIRMWARE=y
+CONFIG_MKSWAP=y
+CONFIG_FEATURE_MKSWAP_V0=y
+CONFIG_MORE=y
+CONFIG_FEATURE_USE_TERMIOS=y
+CONFIG_VOLUMEID=y
+CONFIG_FEATURE_VOLUMEID_EXT=y
+CONFIG_FEATURE_VOLUMEID_REISERFS=y
+CONFIG_FEATURE_VOLUMEID_FAT=y
+CONFIG_FEATURE_VOLUMEID_HFS=y
+CONFIG_FEATURE_VOLUMEID_JFS=y
+CONFIG_FEATURE_VOLUMEID_XFS=y
+CONFIG_FEATURE_VOLUMEID_NTFS=y
+CONFIG_FEATURE_VOLUMEID_ISO9660=y
+CONFIG_FEATURE_VOLUMEID_UDF=y
+CONFIG_FEATURE_VOLUMEID_LUKS=y
+CONFIG_FEATURE_VOLUMEID_LINUXSWAP=y
+CONFIG_FEATURE_VOLUMEID_CRAMFS=y
+CONFIG_FEATURE_VOLUMEID_ROMFS=y
+CONFIG_FEATURE_VOLUMEID_SYSV=y
+CONFIG_FEATURE_VOLUMEID_OCFS2=y
+CONFIG_FEATURE_VOLUMEID_LINUXRAID=y
+CONFIG_MOUNT=y
+CONFIG_FEATURE_MOUNT_FAKE=y
+CONFIG_FEATURE_MOUNT_VERBOSE=y
+CONFIG_FEATURE_MOUNT_HELPERS=y
+CONFIG_FEATURE_MOUNT_LABEL=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_RDEV=y
+CONFIG_READPROFILE=y
+CONFIG_RTCWAKE=y
+CONFIG_SCRIPT=y
+CONFIG_SETARCH=y
+CONFIG_SWAPONOFF=y
+CONFIG_FEATURE_SWAPON_PRI=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_CHAT=y
+CONFIG_FEATURE_CHAT_NOFAIL=y
+CONFIG_FEATURE_CHAT_TTY_HIFI=y
+CONFIG_FEATURE_CHAT_IMPLICIT_CR=y
+CONFIG_FEATURE_CHAT_SWALLOW_OPTS=y
+CONFIG_FEATURE_CHAT_SEND_ESCAPES=y
+CONFIG_FEATURE_CHAT_VAR_ABORT_LEN=y
+CONFIG_FEATURE_CHAT_CLR_ABORT=y
+CONFIG_CHRT=y
+CONFIG_CROND=y
+CONFIG_FEATURE_CROND_D=y
+CONFIG_FEATURE_CROND_CALL_SENDMAIL=y
+CONFIG_CRONTAB=y
+CONFIG_DC=y
+CONFIG_FEATURE_DC_LIBM=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_DEVMEM=y
+CONFIG_EJECT=y
+CONFIG_FEATURE_EJECT_SCSI=y
+CONFIG_FBSPLASH=y
+CONFIG_INOTIFYD=y
+CONFIG_LAST=y
+CONFIG_FEATURE_LAST_SMALL=y
+# CONFIG_FEATURE_LAST_FANCY is not set
+CONFIG_LESS=y
+CONFIG_FEATURE_LESS_MAXLINES=9999999
+CONFIG_FEATURE_LESS_BRACKETS=y
+CONFIG_FEATURE_LESS_FLAGS=y
+CONFIG_FEATURE_LESS_DASHCMD=y
+CONFIG_FEATURE_LESS_MARKS=y
+CONFIG_FEATURE_LESS_REGEXP=y
+CONFIG_FEATURE_LESS_LINENUMS=y
+CONFIG_FEATURE_LESS_WINCH=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_MAN=y
+CONFIG_MICROCOM=y
+CONFIG_MOUNTPOINT=y
+CONFIG_MT=y
+CONFIG_RAIDAUTORUN=y
+CONFIG_READAHEAD=y
+CONFIG_RUNLEVEL=y
+CONFIG_RX=y
+CONFIG_SETSID=y
+CONFIG_STRINGS=y
+CONFIG_TASKSET=y
+CONFIG_FEATURE_TASKSET_FANCY=y
+CONFIG_TIME=y
+CONFIG_TTYSIZE=y
+CONFIG_WATCHDOG=y
+
+#
+# Networking Utilities
+#
+CONFIG_FEATURE_IPV6=y
+CONFIG_FEATURE_PREFER_IPV4_ADDRESS=y
+CONFIG_VERBOSE_RESOLUTION_ERRORS=y
+CONFIG_ARP=y
+CONFIG_ARPING=y
+CONFIG_BRCTL=y
+CONFIG_FEATURE_BRCTL_FANCY=y
+CONFIG_FEATURE_BRCTL_SHOW=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_RANGES=y
+CONFIG_FEATURE_HTTPD_USE_SENDFILE=y
+CONFIG_FEATURE_HTTPD_SETUID=y
+CONFIG_FEATURE_HTTPD_BASIC_AUTH=y
+CONFIG_FEATURE_HTTPD_AUTH_MD5=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_FEATURE_HTTPD_ERROR_PAGES=y
+CONFIG_FEATURE_HTTPD_PROXY=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_IFENSLAVE=y
+CONFIG_IFUPDOWN=y
+CONFIG_IFUPDOWN_IFSTATE_PATH="/var/run/ifstate"
+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_MAPPING=y
+CONFIG_FEATURE_IFUPDOWN_EXTERNAL_DHCP=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_FEATURE_IP_RARE_PROTOCOLS=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_FEATURE_NAMEIF_EXTENDED=y
+CONFIG_NC=y
+CONFIG_NC_SERVER=y
+CONFIG_NC_EXTRA=y
+CONFIG_NETSTAT=y
+CONFIG_FEATURE_NETSTAT_WIDE=y
+CONFIG_FEATURE_NETSTAT_PRG=y
+CONFIG_NSLOOKUP=y
+CONFIG_PING=y
+CONFIG_PING6=y
+CONFIG_FEATURE_FANCY_PING=y
+CONFIG_PSCAN=y
+CONFIG_ROUTE=y
+CONFIG_SLATTACH=y
+CONFIG_TELNET=y
+CONFIG_FEATURE_TELNET_TTYPE=y
+CONFIG_FEATURE_TELNET_AUTOLOGIN=y
+CONFIG_TELNETD=y
+CONFIG_FEATURE_TELNETD_STANDALONE=y
+CONFIG_TFTP=y
+CONFIG_TFTPD=y
+CONFIG_FEATURE_TFTP_GET=y
+CONFIG_FEATURE_TFTP_PUT=y
+CONFIG_FEATURE_TFTP_BLOCKSIZE=y
+CONFIG_TFTP_DEBUG=y
+CONFIG_TRACEROUTE=y
+CONFIG_FEATURE_TRACEROUTE_VERBOSE=y
+CONFIG_FEATURE_TRACEROUTE_SOURCE_ROUTE=y
+CONFIG_FEATURE_TRACEROUTE_USE_ICMP=y
+CONFIG_APP_UDHCPD=y
+CONFIG_APP_DHCPRELAY=y
+CONFIG_APP_DUMPLEASES=y
+CONFIG_FEATURE_UDHCPD_WRITE_LEASES_EARLY=y
+CONFIG_DHCPD_LEASES_FILE="/var/lib/misc/udhcpd.leases"
+CONFIG_APP_UDHCPC=y
+CONFIG_FEATURE_UDHCPC_ARPING=y
+CONFIG_FEATURE_UDHCP_PORT=y
+CONFIG_UDHCP_DEBUG=y
+CONFIG_FEATURE_UDHCP_RFC3397=y
+CONFIG_UDHCPC_DEFAULT_SCRIPT="/usr/share/udhcpc/default.script"
+CONFIG_UDHCPC_SLACK_FOR_BUGGY_SERVERS=80
+CONFIG_VCONFIG=y
+CONFIG_WGET=y
+CONFIG_FEATURE_WGET_STATUSBAR=y
+CONFIG_FEATURE_WGET_AUTHENTICATION=y
+CONFIG_FEATURE_WGET_LONG_OPTIONS=y
+CONFIG_ZCIP=y
+CONFIG_TCPSVD=y
+CONFIG_UDPSVD=y
+
+#
+# Print Utilities
+#
+CONFIG_LPD=y
+CONFIG_LPR=y
+CONFIG_LPQ=y
+
+#
+# Mail Utilities
+#
+CONFIG_MAKEMIME=y
+CONFIG_FEATURE_MIME_CHARSET="us-ascii"
+CONFIG_POPMAILDIR=y
+CONFIG_FEATURE_POPMAILDIR_DELIVERY=y
+CONFIG_REFORMIME=y
+CONFIG_FEATURE_REFORMIME_COMPAT=y
+CONFIG_SENDMAIL=y
+CONFIG_FEATURE_SENDMAIL_MAILX=y
+CONFIG_FEATURE_SENDMAIL_MAILXX=y
+
+#
+# Process Utilities
+#
+CONFIG_FREE=y
+CONFIG_FUSER=y
+CONFIG_KILL=y
+CONFIG_KILLALL=y
+CONFIG_KILLALL5=y
+CONFIG_NMETER=y
+CONFIG_PGREP=y
+CONFIG_PIDOF=y
+CONFIG_FEATURE_PIDOF_SINGLE=y
+CONFIG_FEATURE_PIDOF_OMIT=y
+CONFIG_PKILL=y
+CONFIG_PS=y
+CONFIG_FEATURE_PS_WIDE=y
+CONFIG_FEATURE_PS_TIME=y
+CONFIG_FEATURE_PS_UNUSUAL_SYSTEMS=y
+CONFIG_RENICE=y
+CONFIG_BB_SYSCTL=y
+CONFIG_TOP=y
+CONFIG_FEATURE_TOP_CPU_USAGE_PERCENTAGE=y
+CONFIG_FEATURE_TOP_CPU_GLOBAL_PERCENTS=y
+CONFIG_FEATURE_TOP_SMP_CPU=y
+CONFIG_FEATURE_TOP_DECIMALS=y
+CONFIG_FEATURE_TOP_SMP_PROCESS=y
+CONFIG_FEATURE_TOPMEM=y
+CONFIG_UPTIME=y
+CONFIG_WATCH=y
+
+#
+# Runit Utilities
+#
+CONFIG_RUNSV=y
+CONFIG_RUNSVDIR=y
+CONFIG_FEATURE_RUNSVDIR_LOG=y
+CONFIG_SV=y
+CONFIG_SV_DEFAULT_SERVICE_DIR="/var/service"
+CONFIG_SVLOGD=y
+CONFIG_CHPST=y
+CONFIG_SETUIDGID=y
+CONFIG_ENVUIDGID=y
+CONFIG_ENVDIR=y
+CONFIG_SOFTLIMIT=y
+
+#
+# SELinux Utilities
+#
+CONFIG_CHCON=y
+CONFIG_FEATURE_CHCON_LONG_OPTIONS=y
+CONFIG_GETENFORCE=y
+CONFIG_GETSEBOOL=y
+CONFIG_LOAD_POLICY=y
+CONFIG_MATCHPATHCON=y
+CONFIG_RESTORECON=y
+CONFIG_RUNCON=y
+CONFIG_FEATURE_RUNCON_LONG_OPTIONS=y
+CONFIG_SELINUXENABLED=y
+CONFIG_SETENFORCE=y
+CONFIG_SETFILES=y
+CONFIG_FEATURE_SETFILES_CHECK_OPTION=y
+CONFIG_SETSEBOOL=y
+CONFIG_SESTATUS=y
+
+#
+# Shells
+#
+# CONFIG_FEATURE_SH_IS_ASH is not set
+CONFIG_FEATURE_SH_IS_HUSH=y
+# CONFIG_FEATURE_SH_IS_MSH is not set
+# CONFIG_FEATURE_SH_IS_NONE is not set
+# CONFIG_ASH is not set
+# CONFIG_ASH_BASH_COMPAT 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_PRINTF 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=y
+CONFIG_HUSH_HELP=y
+CONFIG_HUSH_INTERACTIVE=y
+CONFIG_HUSH_JOB=y
+CONFIG_HUSH_TICK=y
+CONFIG_HUSH_IF=y
+CONFIG_HUSH_LOOPS=y
+CONFIG_HUSH_CASE=y
+CONFIG_LASH=y
+CONFIG_MSH=y
+
+#
+# Bourne Shell Options
+#
+CONFIG_FEATURE_SH_EXTRA_QUIET=y
+CONFIG_FEATURE_SH_STANDALONE=y
+CONFIG_FEATURE_SH_NOFORK=y
+CONFIG_CTTYHACK=y
+
+#
+# System Logging Utilities
+#
+CONFIG_SYSLOGD=y
+CONFIG_FEATURE_ROTATE_LOGFILE=y
+CONFIG_FEATURE_REMOTE_LOG=y
+CONFIG_FEATURE_SYSLOGD_DUP=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
diff --git a/applets/Kbuild b/applets/Kbuild
new file mode 100644 (file)
index 0000000..2969e79
--- /dev/null
@@ -0,0 +1,34 @@
+# 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.
+
+obj-y :=
+obj-y += applets.o
+
+hostprogs-y:=
+hostprogs-y += usage applet_tables
+
+always:= $(hostprogs-y)
+
+# Generated files need additional love
+
+HOSTCFLAGS_usage.o = -I$(srctree)/include
+
+applets/applets.o: include/usage_compressed.h include/applet_tables.h
+
+applets/usage:         .config $(srctree)/applets/usage_compressed
+applets/applet_tables: .config
+
+quiet_cmd_gen_usage_compressed = GEN     include/usage_compressed.h
+      cmd_gen_usage_compressed = $(srctree)/applets/usage_compressed include/usage_compressed.h applets
+
+include/usage_compressed.h: applets/usage $(srctree)/applets/usage_compressed
+       $(call cmd,gen_usage_compressed)
+
+quiet_cmd_gen_applet_tables = GEN     include/applet_tables.h
+      cmd_gen_applet_tables = applets/applet_tables include/applet_tables.h
+
+include/applet_tables.h: applets/applet_tables
+       $(call cmd,gen_applet_tables)
diff --git a/applets/applet_tables.c b/applets/applet_tables.c
new file mode 100644 (file)
index 0000000..17135dd
--- /dev/null
@@ -0,0 +1,126 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Applet table generator.
+ * Runs on host and produces include/applet_tables.h
+ *
+ * Copyright (C) 2007 Denys Vlasenko <vda.linux@googlemail.com>
+ *
+ * Licensed under GPLv2, see file License in this tarball for details.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+#include "../include/autoconf.h"
+#include "../include/busybox.h"
+
+struct bb_applet {
+       const char *name;
+       const char *main;
+       enum bb_install_loc_t install_loc;
+       enum bb_suid_t need_suid;
+       /* true if instead of fork(); exec("applet"); waitpid();
+        * one can do fork(); exit(applet_main(argc,argv)); waitpid(); */
+       unsigned char noexec;
+       /* Even nicer */
+       /* true if instead of fork(); exec("applet"); waitpid();
+        * one can simply call applet_main(argc,argv); */
+       unsigned char nofork;
+};
+
+/* Define struct bb_applet applets[] */
+#include "../include/applets.h"
+
+enum { NUM_APPLETS = ARRAY_SIZE(applets) };
+
+static int offset[NUM_APPLETS];
+
+static int cmp_name(const void *a, const void *b)
+{
+       const struct bb_applet *aa = a;
+       const struct bb_applet *bb = b;
+       return strcmp(aa->name, bb->name);
+}
+
+int main(int argc, char **argv)
+{
+       int i;
+       int ofs;
+       unsigned MAX_APPLET_NAME_LEN = 1;
+
+       qsort(applets, NUM_APPLETS, sizeof(applets[0]), cmp_name);
+
+       ofs = 0;
+       for (i = 0; i < NUM_APPLETS; i++) {
+               offset[i] = ofs;
+               ofs += strlen(applets[i].name) + 1;
+       }
+       /* We reuse 4 high-order bits of offset array for other purposes,
+        * so if they are indeed needed, refuse to proceed */
+       if (ofs > 0xfff)
+               return 1;
+       if (!argv[1])
+               return 1;
+
+       i = open(argv[1], O_WRONLY | O_TRUNC | O_CREAT, 0666);
+       if (i < 0)
+               return 1;
+       dup2(i, 1);
+
+       /* Keep in sync with include/busybox.h! */
+
+       puts("/* This is a generated file, don't edit */\n");
+
+       printf("#define NUM_APPLETS %u\n", NUM_APPLETS);
+       if (NUM_APPLETS == 1) {
+               printf("#define SINGLE_APPLET_STR \"%s\"\n", applets[0].name);
+               printf("#define SINGLE_APPLET_MAIN %s_main\n", applets[0].name);
+       }
+
+       puts("\nconst char applet_names[] ALIGN1 = \"\"");
+       for (i = 0; i < NUM_APPLETS; i++) {
+               printf("\"%s\" \"\\0\"\n", applets[i].name);
+               if (MAX_APPLET_NAME_LEN < strlen(applets[i].name))
+                       MAX_APPLET_NAME_LEN = strlen(applets[i].name);
+       }
+       puts(";");
+
+       puts("\nint (*const applet_main[])(int argc, char **argv) = {");
+       for (i = 0; i < NUM_APPLETS; i++) {
+               printf("%s_main,\n", applets[i].main);
+       }
+       puts("};");
+
+       puts("const uint16_t applet_nameofs[] ALIGN2 = {");
+       for (i = 0; i < NUM_APPLETS; i++) {
+               printf("0x%04x,\n",
+                       offset[i]
+#if ENABLE_FEATURE_PREFER_APPLETS
+                       + (applets[i].nofork << 12)
+                       + (applets[i].noexec << 13)
+#endif
+#if ENABLE_FEATURE_SUID
+                       + (applets[i].need_suid << 14) /* 2 bits */
+#endif
+               );
+       }
+       puts("};");
+
+#if ENABLE_FEATURE_INSTALLER
+       puts("const uint8_t applet_install_loc[] ALIGN1 = {");
+       i = 0;
+       while (i < NUM_APPLETS) {
+               int v = applets[i].install_loc; /* 3 bits */
+               if (++i < NUM_APPLETS)
+                       v |= applets[i].install_loc << 4; /* 3 bits */
+               printf("0x%02x,\n", v);
+               i++;
+       }
+       puts("};\n");
+#endif
+
+       printf("#define MAX_APPLET_NAME_LEN %u\n", MAX_APPLET_NAME_LEN);
+
+       return 0;
+}
diff --git a/applets/applets.c b/applets/applets.c
new file mode 100644 (file)
index 0000000..133a215
--- /dev/null
@@ -0,0 +1,18 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Stub for linking busybox binary against libbusybox.
+ *
+ * Copyright (C) 2007 Denys Vlasenko <vda.linux@googlemail.com>
+ *
+ * Licensed under GPLv2, see file License in this tarball for details.
+ */
+
+#include <assert.h>
+#include "busybox.h"
+
+#if ENABLE_BUILD_LIBBUSYBOX
+int main(int argc UNUSED_PARAM, char **argv)
+{
+       return lbb_main(argv);
+}
+#endif
diff --git a/applets/busybox.mkll b/applets/busybox.mkll
new file mode 100755 (executable)
index 0000000..6d61f7e
--- /dev/null
@@ -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 <ldoolitt@recycle.lbl.gov>
+
+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 (file)
index 0000000..341f4d1
--- /dev/null
@@ -0,0 +1,24 @@
+/* Minimal wrapper to build an individual busybox applet.
+ *
+ * Copyright 2005 Rob Landley <rob@landley.net
+ *
+ * Licensed under GPL version 2, see file LICENSE in this tarball for details
+ */
+
+const char *applet_name;
+
+#include <stdio.h>
+#include <stdlib.h>
+#include "usage.h"
+
+int main(int argc, char **argv)
+{
+       applet_name = argv[0];
+       return APPLET_main(argc,argv);
+}
+
+void bb_show_usage(void)
+{
+       fputs(APPLET_full_usage "\n", stdout);
+       exit(EXIT_FAILURE);
+}
diff --git a/applets/install.sh b/applets/install.sh
new file mode 100755 (executable)
index 0000000..32049b1
--- /dev/null
@@ -0,0 +1,110 @@
+#!/bin/sh
+
+export LC_ALL=POSIX
+export LC_CTYPE=POSIX
+
+prefix=${1}
+if [ -z "$prefix" ]; then
+       echo "usage: applets/install.sh DESTINATION [--symlinks/--hardlinks/--scriptwrapper]"
+       exit 1;
+fi
+h=`sort busybox.links | uniq`
+scriptwrapper="n"
+cleanup="0"
+noclobber="0"
+case "$2" in
+       --hardlinks)     linkopts="-f";;
+       --symlinks)      linkopts="-fs";;
+       --scriptwrapper) scriptwrapper="y";swrapall="y";;
+       --sw-sh-hard)    scriptwrapper="y";linkopts="-f";;
+       --sw-sh-sym)     scriptwrapper="y";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 -pPR $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
+       `
+       exit 0
+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 [ "$scriptwrapper" = "y" ]; then
+               if [ "$swrapall" != "y" ] && [ "$i" = "/bin/sh" ]; then
+                       ln $linkopts busybox $prefix$i || exit 1
+               else
+                       rm -f $prefix$i
+                       echo "#!/bin/busybox" > $prefix$i
+                       chmod +x $prefix/$i
+               fi
+               echo "  $prefix$i"
+       else
+               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
+       fi
+done
+
+exit 0
diff --git a/applets/usage.c b/applets/usage.c
new file mode 100644 (file)
index 0000000..1e038b3
--- /dev/null
@@ -0,0 +1,34 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Copyright (C) 2008 Denys Vlasenko.
+ *
+ * Licensed under GPLv2, see file LICENSE in this tarball for details.
+ */
+#include <unistd.h>
+
+/* Just #include "autoconf.h" doesn't work for builds in separate
+ * object directory */
+#include "../include/autoconf.h"
+
+/* Since we can't use platform.h, have to do this again by hand: */
+#if ENABLE_NOMMU
+#define BB_MMU 0
+#define USE_FOR_NOMMU(...) __VA_ARGS__
+#define USE_FOR_MMU(...)
+#else
+#define BB_MMU 1
+#define USE_FOR_NOMMU(...)
+#define USE_FOR_MMU(...) __VA_ARGS__
+#endif
+
+static const char usage_messages[] = ""
+#define MAKE_USAGE
+#include "usage.h"
+#include "applets.h"
+;
+
+int main(void)
+{
+       write(STDOUT_FILENO, usage_messages, sizeof(usage_messages));
+       return 0;
+}
diff --git a/applets/usage_compressed b/applets/usage_compressed
new file mode 100755 (executable)
index 0000000..c30bcfa
--- /dev/null
@@ -0,0 +1,34 @@
+#!/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[] ALIGN1 = {'
+
+## Breaks on big-endian systems!
+## # Extra effort to avoid using "od -t x1": -t is not available
+## # in non-CONFIG_DESKTOPed busybox od
+##
+## "$loc/usage" | bzip2 -1 | od -v -x \
+## | $SED -e 's/^[^ ]*//' \
+## | $SED -e 's/ //g' \
+## | grep -v '^$' \
+## | $SED -e 's/\(..\)\(..\)/0x\2,0x\1,/g'
+
+"$loc/usage" | bzip2 -1 | od -v -t x1 \
+| $SED -e 's/^[^ ]*//' \
+| $SED -e 's/ //g' \
+| grep -v '^$' \
+| $SED -e 's/\(..\)/0x\1,/g'
+
+echo '};'
+echo '#define SIZEOF_usage_messages' `expr 0 + $sz`
diff --git a/arch/i386/Makefile b/arch/i386/Makefile
new file mode 100644 (file)
index 0000000..e6c99c6
--- /dev/null
@@ -0,0 +1,7 @@
+# ==========================================================================
+# Build system
+# ==========================================================================
+
+# -mpreferred-stack-boundary=2 is essential in preventing gcc 4.2.x
+# from aligning stack to 16 bytes. (Which is gcc's way of supporting SSE).
+CFLAGS += $(call cc-option,-march=i386 -mpreferred-stack-boundary=2,)
diff --git a/archival/Config.in b/archival/Config.in
new file mode 100644 (file)
index 0000000..64b44c2
--- /dev/null
@@ -0,0 +1,299 @@
+#
+# For a description of the syntax of this configuration file,
+# see scripts/kbuild/config-language.txt.
+#
+
+menu "Archival Utilities"
+
+config FEATURE_SEAMLESS_LZMA
+       bool "Make tar, rpm, modprobe etc understand .lzma data"
+       default n
+       help
+         Make tar, rpm, modprobe etc understand .lzma data.
+
+config FEATURE_SEAMLESS_BZ2
+       bool "Make tar, rpm, modprobe etc understand .bz2 data"
+       default n
+       help
+         Make tar, rpm, modprobe etc understand .bz2 data.
+
+config FEATURE_SEAMLESS_GZ
+       bool "Make tar, rpm, modprobe etc understand .gz data"
+       default n
+       help
+         Make tar, rpm, modprobe etc understand .gz data.
+
+config FEATURE_SEAMLESS_Z
+       bool "Make tar and gunzip understand .Z data"
+       default n
+       help
+         Make tar and gunzip understand .Z data.
+
+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 "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.
+
+         Unless you have a specific application which requires bunzip2, you
+         should probably say N here.
+
+config BZIP2
+       bool "bzip2"
+       default n
+       help
+         bzip2 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.
+
+         Unless you have a specific application which requires bzip2, 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 FEATURE_CPIO_O
+       bool "Support for archive creation"
+       default n
+       depends on CPIO
+       help
+         This implementation of cpio can create cpio archives in the "newc"
+         format only.
+
+config FEATURE_CPIO_P
+       bool "Support for passthrough mode"
+       default n
+       depends on FEATURE_CPIO_O
+       help
+         Passthrough mode. Rarely used.
+
+config DPKG
+       bool "dpkg"
+       default n
+       select FEATURE_SEAMLESS_GZ
+       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
+       select FEATURE_SEAMLESS_GZ
+       help
+         dpkg-deb 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,
+         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 <deb> 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 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 RPM packages.
+
+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.
+
+if TAR
+
+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_AUTODETECT
+       bool "Autodetect gz/bz2 compressed tarballs"
+       default n
+       depends on FEATURE_SEAMLESS_Z || FEATURE_SEAMLESS_GZ || FEATURE_SEAMLESS_BZ2 || FEATURE_SEAMLESS_LZMA
+       help
+         With this option tar can automatically detect gzip/bzip2 compressed
+         tarballs. Currently it works only on files (not pipes etc).
+
+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_OLDGNU_COMPATIBILITY
+       bool "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_OLDSUN_COMPATIBILITY
+       bool "Enable untarring of tarballs with checksums produced by buggy Sun tar"
+       default N
+       depends on TAR
+       help
+         This option is required to unpack archives created by some old
+         version of Sun's tar (it was calculating checksum using signed
+         arithmetic). It is said to be fixed in newer Sun tar, but "old"
+         tarballs still exist.
+
+config FEATURE_TAR_GNU_EXTENSIONS
+       bool "Support for GNU tar extensions (long filenames)"
+       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 FEATURE_TAR_UNAME_GNAME
+       bool "Enable use of user and group names"
+       default n
+       depends on TAR
+       help
+         Enables use of user and group names in tar. This affects contents
+         listings (-t) and preserving permissions when unpacking (-p).
+         +200 bytes.
+
+endif #tar
+
+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 "Optimize 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.
+
+endmenu
diff --git a/archival/Kbuild b/archival/Kbuild
new file mode 100644 (file)
index 0000000..72dbdda
--- /dev/null
@@ -0,0 +1,23 @@
+# 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.
+
+libs-y                         += libunarchive/
+
+lib-y:=
+lib-$(CONFIG_AR)               += ar.o
+lib-$(CONFIG_BUNZIP2)          += bbunzip.o
+lib-$(CONFIG_BZIP2)            += bzip2.o bbunzip.o
+lib-$(CONFIG_UNLZMA)           += bbunzip.o
+lib-$(CONFIG_CPIO)             += cpio.o
+lib-$(CONFIG_DPKG)             += dpkg.o
+lib-$(CONFIG_DPKG_DEB)         += dpkg_deb.o
+lib-$(CONFIG_GUNZIP)           += bbunzip.o
+lib-$(CONFIG_GZIP)             += gzip.o bbunzip.o
+lib-$(CONFIG_RPM2CPIO)         += rpm2cpio.o
+lib-$(CONFIG_RPM)              += rpm.o
+lib-$(CONFIG_TAR)              += tar.o
+lib-$(CONFIG_UNCOMPRESS)       += bbunzip.o
+lib-$(CONFIG_UNZIP)            += unzip.o
diff --git a/archival/ar.c b/archival/ar.c
new file mode 100644 (file)
index 0000000..dbff677
--- /dev/null
@@ -0,0 +1,87 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Mini ar implementation for busybox
+ *
+ * Copyright (C) 2000 by Glenn McGrath
+ *
+ * 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 "libbb.h"
+#include "unarchive.h"
+
+static void FAST_FUNC 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) MAIN_EXTERNALLY_VISIBLE;
+int ar_main(int argc, char **argv)
+{
+       static const char msg_unsupported_err[] ALIGN1 =
+               "archive %s is not supported";
+
+       archive_handle_t *archive_handle;
+       unsigned opt;
+
+       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(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->ah_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++]);
+       }
+
+       unpack_ar_archive(archive_handle);
+
+       return EXIT_SUCCESS;
+}
diff --git a/archival/bbunzip.c b/archival/bbunzip.c
new file mode 100644 (file)
index 0000000..75489f2
--- /dev/null
@@ -0,0 +1,384 @@
+/* vi: set sw=4 ts=4: */
+/*
+ *  Common code for gunzip-like applets
+ *
+ *  Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.h"
+#include "unarchive.h"
+
+enum {
+       OPT_STDOUT = 0x1,
+       OPT_FORCE = 0x2,
+/* gunzip and bunzip2 only: */
+       OPT_VERBOSE = 0x4,
+       OPT_DECOMPRESS = 0x8,
+       OPT_TEST = 0x10,
+};
+
+static
+int open_to_or_warn(int to_fd, const char *filename, int flags, int mode)
+{
+       int fd = open3_or_warn(filename, flags, mode);
+       if (fd < 0) {
+               return 1;
+       }
+       xmove_fd(fd, to_fd);
+       return 0;
+}
+
+int FAST_FUNC bbunpack(char **argv,
+       char* (*make_new_name)(char *filename),
+       USE_DESKTOP(long long) int (*unpacker)(unpack_info_t *info)
+)
+{
+       struct stat stat_buf;
+       USE_DESKTOP(long long) int status;
+       char *filename, *new_name;
+       smallint exitcode = 0;
+       unpack_info_t info;
+
+       do {
+               /* NB: new_name is *maybe* malloc'ed! */
+               new_name = NULL;
+               filename = *argv; /* can be NULL - 'streaming' bunzip2 */
+
+               if (filename && LONE_DASH(filename))
+                       filename = NULL;
+
+               /* Open src */
+               if (filename) {
+                       if (stat(filename, &stat_buf) != 0) {
+                               bb_simple_perror_msg(filename);
+ err:
+                               exitcode = 1;
+                               goto free_name;
+                       }
+                       if (open_to_or_warn(STDIN_FILENO, filename, O_RDONLY, 0))
+                               goto err;
+               }
+
+               /* Special cases: test, stdout */
+               if (option_mask32 & (OPT_STDOUT|OPT_TEST)) {
+                       if (option_mask32 & OPT_TEST)
+                               if (open_to_or_warn(STDOUT_FILENO, bb_dev_null, O_WRONLY, 0))
+                                       goto err;
+                       filename = NULL;
+               }
+
+               /* Open dst if we are going to unpack to file */
+               if (filename) {
+                       new_name = make_new_name(filename);
+                       if (!new_name) {
+                               bb_error_msg("%s: unknown suffix - ignored", filename);
+                               goto err;
+                       }
+
+                       /* -f: overwrite existing output files */
+                       if (option_mask32 & OPT_FORCE) {
+                               unlink(new_name);
+                       }
+
+                       /* O_EXCL: "real" bunzip2 doesn't overwrite files */
+                       /* GNU gunzip does not bail out, but goes to next file */
+                       if (open_to_or_warn(STDOUT_FILENO, new_name, O_WRONLY | O_CREAT | O_EXCL,
+                                       stat_buf.st_mode))
+                               goto err;
+               }
+
+               /* Check that the input is sane */
+               if (isatty(STDIN_FILENO) && (option_mask32 & OPT_FORCE) == 0) {
+                       bb_error_msg_and_die("compressed data not read from terminal, "
+                                       "use -f to force it");
+               }
+
+               /* memset(&info, 0, sizeof(info)); */
+               info.mtime = 0; /* so far it has one member only */
+               status = unpacker(&info);
+               if (status < 0)
+                       exitcode = 1;
+
+               if (filename) {
+                       char *del = new_name;
+                       if (status >= 0) {
+                               /* TODO: restore other things? */
+                               if (info.mtime) {
+                                       struct utimbuf times;
+
+                                       times.actime = info.mtime;
+                                       times.modtime = info.mtime;
+                                       /* Close first.
+                                        * On some systems calling utime
+                                        * then closing resets the mtime. */
+                                       close(STDOUT_FILENO);
+                                       /* Ignoring errors */
+                                       utime(new_name, &times);
+                               }
+
+                               /* Delete _compressed_ file */
+                               del = filename;
+                               /* restore extension (unless tgz -> tar case) */
+                               if (new_name == filename)
+                                       filename[strlen(filename)] = '.';
+                       }
+                       xunlink(del);
+
+#if 0 /* Currently buggy - wrong name: "a.gz: 261% - replaced with a.gz" */
+                       /* Extreme bloat for gunzip compat */
+                       if (ENABLE_DESKTOP && (option_mask32 & OPT_VERBOSE) && status >= 0) {
+                               fprintf(stderr, "%s: %u%% - replaced with %s\n",
+                                       filename, (unsigned)(stat_buf.st_size*100 / (status+1)), new_name);
+                       }
+#endif
+
+ free_name:
+                       if (new_name != filename)
+                               free(new_name);
+               }
+       } while (*argv && *++argv);
+
+       return exitcode;
+}
+
+#if ENABLE_BUNZIP2 || ENABLE_UNLZMA || ENABLE_UNCOMPRESS
+
+static
+char* make_new_name_generic(char *filename, const char *expected_ext)
+{
+       char *extension = strrchr(filename, '.');
+       if (!extension || strcmp(extension + 1, expected_ext) != 0) {
+               /* Mimic GNU gunzip - "real" bunzip2 tries to */
+               /* unpack file anyway, to file.out */
+               return NULL;
+       }
+       *extension = '\0';
+       return filename;
+}
+
+#endif
+
+
+/*
+ *  Modified for busybox by Glenn McGrath
+ *  Added support output to stdout by Thomas Lundquist <thomasez@zelow.no>
+ *
+ *  Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+#if ENABLE_BUNZIP2
+
+static
+char* make_new_name_bunzip2(char *filename)
+{
+       return make_new_name_generic(filename, "bz2");
+}
+
+static
+USE_DESKTOP(long long) int unpack_bunzip2(unpack_info_t *info UNUSED_PARAM)
+{
+       return unpack_bz2_stream_prime(STDIN_FILENO, STDOUT_FILENO);
+}
+
+int bunzip2_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int bunzip2_main(int argc UNUSED_PARAM, char **argv)
+{
+       getopt32(argv, "cfvdt");
+       argv += optind;
+       if (applet_name[2] == 'c')
+               option_mask32 |= OPT_STDOUT;
+
+       return bbunpack(argv, make_new_name_bunzip2, unpack_bunzip2);
+}
+
+#endif
+
+
+/*
+ * 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 <sr1@inf.tu-dresden.de>
+ * based on gzip sources
+ *
+ * Adjusted further by Erik Andersen <andersen@codepoet.org> 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.
+ */
+
+#if ENABLE_GUNZIP
+
+static
+char* make_new_name_gunzip(char *filename)
+{
+       char *extension = strrchr(filename, '.');
+
+       if (!extension)
+               return NULL;
+
+       extension++;
+       if (strcmp(extension, "tgz" + 1) == 0
+#if ENABLE_FEATURE_SEAMLESS_Z
+        || (extension[0] == 'Z' && extension[1] == '\0')
+#endif
+       ) {
+               extension[-1] = '\0';
+       } else if (strcmp(extension, "tgz") == 0) {
+               filename = xstrdup(filename);
+               extension = strrchr(filename, '.');
+               extension[2] = 'a';
+               extension[3] = 'r';
+       } else {
+               return NULL;
+       }
+       return filename;
+}
+
+static
+USE_DESKTOP(long long) int unpack_gunzip(unpack_info_t *info)
+{
+       USE_DESKTOP(long long) int status = -1;
+
+       /* do the decompression, and cleanup */
+       if (xread_char(STDIN_FILENO) == 0x1f) {
+               unsigned char magic2;
+
+               magic2 = xread_char(STDIN_FILENO);
+               if (ENABLE_FEATURE_SEAMLESS_Z && magic2 == 0x9d) {
+                       status = unpack_Z_stream(STDIN_FILENO, STDOUT_FILENO);
+               } else if (magic2 == 0x8b) {
+                       status = unpack_gz_stream_with_info(STDIN_FILENO, STDOUT_FILENO, info);
+               } else {
+                       goto bad_magic;
+               }
+               if (status < 0) {
+                       bb_error_msg("error inflating");
+               }
+       } else {
+ bad_magic:
+               bb_error_msg("invalid magic");
+               /* status is still == -1 */
+       }
+       return status;
+}
+
+/*
+ * Linux kernel build uses gzip -d -n. We accept and ignore it.
+ * Man page says:
+ * -n --no-name
+ * gzip: do not save the original file name and time stamp.
+ * (The original name is always saved if the name had to be truncated.)
+ * gunzip: do not restore the original file name/time even if present
+ * (remove only the gzip suffix from the compressed file name).
+ * This option is the default when decompressing.
+ * -N --name
+ * gzip: always save the original file name and time stamp (this is the default)
+ * gunzip: restore the original file name and time stamp if present.
+ */
+
+int gunzip_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int gunzip_main(int argc UNUSED_PARAM, char **argv)
+{
+       getopt32(argv, "cfvdtn");
+       argv += optind;
+       /* if called as zcat */
+       if (applet_name[1] == 'c')
+               option_mask32 |= OPT_STDOUT;
+
+       return bbunpack(argv, make_new_name_gunzip, unpack_gunzip);
+}
+
+#endif
+
+
+/*
+ * Small lzma deflate implementation.
+ * Copyright (C) 2006  Aurelien Jacobs <aurel@gnuage.org>
+ *
+ * Based on bunzip.c from busybox
+ *
+ * Licensed under GPL v2, see file LICENSE in this tarball for details.
+ */
+
+#if ENABLE_UNLZMA
+
+static
+char* make_new_name_unlzma(char *filename)
+{
+       return make_new_name_generic(filename, "lzma");
+}
+
+static
+USE_DESKTOP(long long) int unpack_unlzma(unpack_info_t *info UNUSED_PARAM)
+{
+       return unpack_lzma_stream(STDIN_FILENO, STDOUT_FILENO);
+}
+
+int unlzma_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int unlzma_main(int argc UNUSED_PARAM, char **argv)
+{
+       getopt32(argv, "cf");
+       argv += optind;
+       /* lzmacat? */
+       if (applet_name[4] == 'c')
+               option_mask32 |= OPT_STDOUT;
+
+       return bbunpack(argv, make_new_name_unlzma, unpack_unlzma);
+}
+
+#endif
+
+
+/*
+ *     Uncompress applet for busybox (c) 2002 Glenn McGrath
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+#if ENABLE_UNCOMPRESS
+
+static
+char* make_new_name_uncompress(char *filename)
+{
+       return make_new_name_generic(filename, "Z");
+}
+
+static
+USE_DESKTOP(long long) int unpack_uncompress(unpack_info_t *info UNUSED_PARAM)
+{
+       USE_DESKTOP(long long) int status = -1;
+
+       if ((xread_char(STDIN_FILENO) != 0x1f) || (xread_char(STDIN_FILENO) != 0x9d)) {
+               bb_error_msg("invalid magic");
+       } else {
+               status = unpack_Z_stream(STDIN_FILENO, STDOUT_FILENO);
+       }
+       return status;
+}
+
+int uncompress_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int uncompress_main(int argc UNUSED_PARAM, char **argv)
+{
+       getopt32(argv, "cf");
+       argv += optind;
+
+       return bbunpack(argv, make_new_name_uncompress, unpack_uncompress);
+}
+
+#endif
diff --git a/archival/bbunzip_test.sh b/archival/bbunzip_test.sh
new file mode 100644 (file)
index 0000000..b8e31bf
--- /dev/null
@@ -0,0 +1,61 @@
+#!/bin/sh
+# Test that concatenated gz files are unpacking correctly.
+# It also tests that unpacking in general is working right.
+# Since zip code has many corner cases, run it for a few hours
+# to get a decent coverage (200000 tests or more).
+
+gzip="gzip"
+gunzip="../busybox gunzip"
+# Or the other way around:
+#gzip="../busybox gzip"
+#gunzip="gunzip"
+
+c=0
+i=$PID
+while true; do
+    c=$((c+1))
+
+    # RANDOM is not very random on some shells. Spice it up.
+    # 100003 is prime
+    len1=$(( (((RANDOM*RANDOM)^i) & 0x7ffffff) % 100003 ))
+    i=$((i * 1664525 + 1013904223))
+    len2=$(( (((RANDOM*RANDOM)^i) & 0x7ffffff) % 100003 ))
+
+    # Just using urandom will make gzip use method 0 (store) -
+    # not good for test coverage!
+    cat /dev/urandom | while true; do read junk; echo "junk $c $i $junk"; done \
+    | dd bs=$len1 count=1 >z1 2>/dev/null
+    cat /dev/urandom | while true; do read junk; echo "junk $c $i $junk"; done \
+    | dd bs=$len2 count=1 >z2 2>/dev/null
+
+    $gzip <z1 >zz.gz
+    $gzip <z2 >>zz.gz
+    $gunzip -c zz.gz >z9 || {
+       echo "Exitcode $?"
+       exit
+    }
+    sum=`cat z1 z2 | md5sum`
+    sum9=`md5sum <z9`
+    test "$sum" == "$sum9" || {
+       echo "md5sums don't match"
+       exit
+    }
+    echo "Test $c ok: len1=$len1 len2=$len2 sum=$sum"
+
+    sum=`cat z1 z2 z1 z2 | md5sum`
+    rm z1.gz z2.gz 2>/dev/null
+    $gzip z1
+    $gzip z2
+    cat z1.gz z2.gz z1.gz z2.gz >zz.gz
+    $gunzip -c zz.gz >z9 || {
+       echo "Exitcode $? (2)"
+       exit
+    }
+    sum9=`md5sum <z9`
+    test "$sum" == "$sum9" || {
+       echo "md5sums don't match (1)"
+       exit
+    }
+
+    echo "Test $c ok: len1=$len1 len2=$len2 sum=$sum (2)"
+done
diff --git a/archival/bbunzip_test2.sh b/archival/bbunzip_test2.sh
new file mode 100644 (file)
index 0000000..5b7e83e
--- /dev/null
@@ -0,0 +1,10 @@
+#!/bin/sh
+# Leak test for gunzip. Watch top for growing process size.
+
+# Just using urandom will make gzip use method 0 (store) -
+# not good for test coverage!
+
+cat /dev/urandom \
+| while true; do read junk; echo "junk $RANDOM $junk"; done \
+| ../busybox gzip \
+| ../busybox gunzip -c >/dev/null
diff --git a/archival/bbunzip_test3.sh b/archival/bbunzip_test3.sh
new file mode 100644 (file)
index 0000000..2dc4afd
--- /dev/null
@@ -0,0 +1,23 @@
+#!/bin/sh
+# Leak test for gunzip. Watch top for growing process size.
+# In this case we look for leaks in "concatenated .gz" code -
+# we feed gunzip with a stream of .gz files.
+
+i=$PID
+c=0
+while true; do
+    c=$((c + 1))
+    echo "Block# $c" >&2
+    # RANDOM is not very random on some shells. Spice it up.
+    i=$((i * 1664525 + 1013904223))
+    # 100003 is prime
+    len=$(( (((RANDOM*RANDOM)^i) & 0x7ffffff) % 100003 ))
+
+    # Just using urandom will make gzip use method 0 (store) -
+    # not good for test coverage!
+    cat /dev/urandom \
+    | while true; do read junk; echo "junk $c $i $junk"; done \
+    | dd bs=$len count=1 2>/dev/null \
+    | gzip >xxx.gz
+    cat xxx.gz xxx.gz xxx.gz xxx.gz xxx.gz xxx.gz xxx.gz xxx.gz
+done | ../busybox gunzip -c >/dev/null
diff --git a/archival/bz/LICENSE b/archival/bz/LICENSE
new file mode 100644 (file)
index 0000000..da43465
--- /dev/null
@@ -0,0 +1,44 @@
+bzip2 applet in busybox is based on lightly-modified source
+of bzip2 version 1.0.4. bzip2 source is distributed
+under the following conditions (copied verbatim from LICENSE file)
+===========================================================
+
+
+This program, "bzip2", the associated library "libbzip2", and all
+documentation, are copyright (C) 1996-2006 Julian R Seward.  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. The origin of this software must not be misrepresented; you must
+   not claim that you wrote the original software.  If you use this
+   software in a product, an acknowledgment in the product
+   documentation would be appreciated but is not required.
+
+3. Altered source versions must be plainly marked as such, and must
+   not be misrepresented as being the original software.
+
+4. 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.
+
+Julian Seward, Cambridge, UK.
+jseward@bzip.org
+bzip2/libbzip2 version 1.0.4 of 20 December 2006
diff --git a/archival/bz/README b/archival/bz/README
new file mode 100644 (file)
index 0000000..fffd47b
--- /dev/null
@@ -0,0 +1,90 @@
+This file is an abridged version of README from bzip2 1.0.4
+Build instructions (which are not relevant to busyboxed bzip2)
+are removed.
+===========================================================
+
+
+This is the README for bzip2/libzip2.
+This version is fully compatible with the previous public releases.
+
+------------------------------------------------------------------
+This file is part of bzip2/libbzip2, a program and library for
+lossless, block-sorting data compression.
+
+bzip2/libbzip2 version 1.0.4 of 20 December 2006
+Copyright (C) 1996-2006 Julian Seward <jseward@bzip.org>
+
+Please read the WARNING, DISCLAIMER and PATENTS sections in this file.
+
+This program is released under the terms of the license contained
+in the file LICENSE.
+------------------------------------------------------------------
+
+Please read and be aware of the following:
+
+
+WARNING:
+
+   This program and library (attempts to) compress data by
+   performing several non-trivial transformations on it.
+   Unless you are 100% familiar with *all* the algorithms
+   contained herein, and with the consequences of modifying them,
+   you should NOT meddle with the compression or decompression
+   machinery.  Incorrect changes can and very likely *will*
+   lead to disastrous loss of data.
+
+
+DISCLAIMER:
+
+   I TAKE NO RESPONSIBILITY FOR ANY LOSS OF DATA ARISING FROM THE
+   USE OF THIS PROGRAM/LIBRARY, HOWSOEVER CAUSED.
+
+   Every compression of a file implies an assumption that the
+   compressed file can be decompressed to reproduce the original.
+   Great efforts in design, coding and testing have been made to
+   ensure that this program works correctly.  However, the complexity
+   of the algorithms, and, in particular, the presence of various
+   special cases in the code which occur with very low but non-zero
+   probability make it impossible to rule out the possibility of bugs
+   remaining in the program.  DO NOT COMPRESS ANY DATA WITH THIS
+   PROGRAM UNLESS YOU ARE PREPARED TO ACCEPT THE POSSIBILITY, HOWEVER
+   SMALL, THAT THE DATA WILL NOT BE RECOVERABLE.
+
+   That is not to say this program is inherently unreliable.
+   Indeed, I very much hope the opposite is true.  bzip2/libbzip2
+   has been carefully constructed and extensively tested.
+
+
+PATENTS:
+
+   To the best of my knowledge, bzip2/libbzip2 does not use any
+   patented algorithms.  However, I do not have the resources
+   to carry out a patent search.  Therefore I cannot give any
+   guarantee of the above statement.
+
+
+I hope you find bzip2 useful.  Feel free to contact me at
+   jseward@bzip.org
+if you have any suggestions or queries.  Many people mailed me with
+comments, suggestions and patches after the releases of bzip-0.15,
+bzip-0.21, and bzip2 versions 0.1pl2, 0.9.0, 0.9.5, 1.0.0, 1.0.1,
+1.0.2 and 1.0.3, and the changes in bzip2 are largely a result of this
+feedback.  I thank you for your comments.
+
+bzip2's "home" is http://www.bzip.org/
+
+Julian Seward
+jseward@bzip.org
+Cambridge, UK.
+
+18     July 1996 (version 0.15)
+25   August 1996 (version 0.21)
+ 7   August 1997 (bzip2, version 0.1)
+29   August 1997 (bzip2, version 0.1pl2)
+23   August 1998 (bzip2, version 0.9.0)
+ 8     June 1999 (bzip2, version 0.9.5)
+ 4     Sept 1999 (bzip2, version 0.9.5d)
+ 5      May 2000 (bzip2, version 1.0pre8)
+30 December 2001 (bzip2, version 1.0.2pre1)
+15 February 2005 (bzip2, version 1.0.3)
+20 December 2006 (bzip2, version 1.0.4)
diff --git a/archival/bz/blocksort.c b/archival/bz/blocksort.c
new file mode 100644 (file)
index 0000000..0e73ffe
--- /dev/null
@@ -0,0 +1,1072 @@
+/*
+ * bzip2 is written by Julian Seward <jseward@bzip.org>.
+ * Adapted for busybox by Denys Vlasenko <vda.linux@googlemail.com>.
+ * See README and LICENSE files in this directory for more information.
+ */
+
+/*-------------------------------------------------------------*/
+/*--- Block sorting machinery                               ---*/
+/*---                                           blocksort.c ---*/
+/*-------------------------------------------------------------*/
+
+/* ------------------------------------------------------------------
+This file is part of bzip2/libbzip2, a program and library for
+lossless, block-sorting data compression.
+
+bzip2/libbzip2 version 1.0.4 of 20 December 2006
+Copyright (C) 1996-2006 Julian Seward <jseward@bzip.org>
+
+Please read the WARNING, DISCLAIMER and PATENTS sections in the
+README file.
+
+This program is released under the terms of the license contained
+in the file LICENSE.
+------------------------------------------------------------------ */
+
+/* #include "bzlib_private.h" */
+
+#define mswap(zz1, zz2) \
+{ \
+       int32_t zztmp = zz1; \
+       zz1 = zz2; \
+       zz2 = zztmp; \
+}
+
+static
+/* No measurable speed gain with inlining */
+/* ALWAYS_INLINE */
+void mvswap(uint32_t* ptr, int32_t zzp1, int32_t zzp2, int32_t zzn)
+{
+       while (zzn > 0) {
+               mswap(ptr[zzp1], ptr[zzp2]);
+               zzp1++;
+               zzp2++;
+               zzn--;
+       }
+}
+
+static
+ALWAYS_INLINE
+int32_t mmin(int32_t a, int32_t b)
+{
+       return (a < b) ? a : b;
+}
+
+
+/*---------------------------------------------*/
+/*--- Fallback O(N log(N)^2) sorting        ---*/
+/*--- algorithm, for repetitive blocks      ---*/
+/*---------------------------------------------*/
+
+/*---------------------------------------------*/
+static
+inline
+void fallbackSimpleSort(uint32_t* fmap,
+               uint32_t* eclass,
+               int32_t   lo,
+               int32_t   hi)
+{
+       int32_t i, j, tmp;
+       uint32_t ec_tmp;
+
+       if (lo == hi) return;
+
+       if (hi - lo > 3) {
+               for (i = hi-4; i >= lo; i--) {
+                       tmp = fmap[i];
+                       ec_tmp = eclass[tmp];
+                       for (j = i+4; j <= hi && ec_tmp > eclass[fmap[j]]; j += 4)
+                               fmap[j-4] = fmap[j];
+                       fmap[j-4] = tmp;
+               }
+       }
+
+       for (i = hi-1; i >= lo; i--) {
+               tmp = fmap[i];
+               ec_tmp = eclass[tmp];
+               for (j = i+1; j <= hi && ec_tmp > eclass[fmap[j]]; j++)
+                       fmap[j-1] = fmap[j];
+               fmap[j-1] = tmp;
+       }
+}
+
+
+/*---------------------------------------------*/
+#define fpush(lz,hz) { \
+       stackLo[sp] = lz; \
+       stackHi[sp] = hz; \
+       sp++; \
+}
+
+#define fpop(lz,hz) { \
+       sp--; \
+       lz = stackLo[sp]; \
+       hz = stackHi[sp]; \
+}
+
+#define FALLBACK_QSORT_SMALL_THRESH 10
+#define FALLBACK_QSORT_STACK_SIZE   100
+
+static
+void fallbackQSort3(uint32_t* fmap,
+               uint32_t* eclass,
+               int32_t   loSt,
+               int32_t   hiSt)
+{
+       int32_t unLo, unHi, ltLo, gtHi, n, m;
+       int32_t sp, lo, hi;
+       uint32_t med, r, r3;
+       int32_t stackLo[FALLBACK_QSORT_STACK_SIZE];
+       int32_t stackHi[FALLBACK_QSORT_STACK_SIZE];
+
+       r = 0;
+
+       sp = 0;
+       fpush(loSt, hiSt);
+
+       while (sp > 0) {
+               AssertH(sp < FALLBACK_QSORT_STACK_SIZE - 1, 1004);
+
+               fpop(lo, hi);
+               if (hi - lo < FALLBACK_QSORT_SMALL_THRESH) {
+                       fallbackSimpleSort(fmap, eclass, lo, hi);
+                       continue;
+               }
+
+               /* Random partitioning.  Median of 3 sometimes fails to
+                * avoid bad cases.  Median of 9 seems to help but
+                * looks rather expensive.  This too seems to work but
+                * is cheaper.  Guidance for the magic constants
+                * 7621 and 32768 is taken from Sedgewick's algorithms
+                * book, chapter 35.
+                */
+               r = ((r * 7621) + 1) % 32768;
+               r3 = r % 3;
+               if (r3 == 0)
+                       med = eclass[fmap[lo]];
+               else if (r3 == 1)
+                       med = eclass[fmap[(lo+hi)>>1]];
+               else
+                       med = eclass[fmap[hi]];
+
+               unLo = ltLo = lo;
+               unHi = gtHi = hi;
+
+               while (1) {
+                       while (1) {
+                               if (unLo > unHi) break;
+                               n = (int32_t)eclass[fmap[unLo]] - (int32_t)med;
+                               if (n == 0) {
+                                       mswap(fmap[unLo], fmap[ltLo]);
+                                       ltLo++;
+                                       unLo++;
+                                       continue;
+                               };
+                               if (n > 0) break;
+                               unLo++;
+                       }
+                       while (1) {
+                               if (unLo > unHi) break;
+                               n = (int32_t)eclass[fmap[unHi]] - (int32_t)med;
+                               if (n == 0) {
+                                       mswap(fmap[unHi], fmap[gtHi]);
+                                       gtHi--; unHi--;
+                                       continue;
+                               };
+                               if (n < 0) break;
+                               unHi--;
+                       }
+                       if (unLo > unHi) break;
+                       mswap(fmap[unLo], fmap[unHi]); unLo++; unHi--;
+               }
+
+               AssertD(unHi == unLo-1, "fallbackQSort3(2)");
+
+               if (gtHi < ltLo) continue;
+
+               n = mmin(ltLo-lo, unLo-ltLo); mvswap(fmap, lo, unLo-n, n);
+               m = mmin(hi-gtHi, gtHi-unHi); mvswap(fmap, unLo, hi-m+1, m);
+
+               n = lo + unLo - ltLo - 1;
+               m = hi - (gtHi - unHi) + 1;
+
+               if (n - lo > hi - m) {
+                       fpush(lo, n);
+                       fpush(m, hi);
+               } else {
+                       fpush(m, hi);
+                       fpush(lo, n);
+               }
+       }
+}
+
+#undef fpush
+#undef fpop
+#undef FALLBACK_QSORT_SMALL_THRESH
+#undef FALLBACK_QSORT_STACK_SIZE
+
+
+/*---------------------------------------------*/
+/* Pre:
+ *     nblock > 0
+ *     eclass exists for [0 .. nblock-1]
+ *     ((uint8_t*)eclass) [0 .. nblock-1] holds block
+ *     ptr exists for [0 .. nblock-1]
+ *
+ * Post:
+ *     ((uint8_t*)eclass) [0 .. nblock-1] holds block
+ *     All other areas of eclass destroyed
+ *     fmap [0 .. nblock-1] holds sorted order
+ *     bhtab[0 .. 2+(nblock/32)] destroyed
+*/
+
+#define       SET_BH(zz)  bhtab[(zz) >> 5] |= (1 << ((zz) & 31))
+#define     CLEAR_BH(zz)  bhtab[(zz) >> 5] &= ~(1 << ((zz) & 31))
+#define     ISSET_BH(zz)  (bhtab[(zz) >> 5] & (1 << ((zz) & 31)))
+#define      WORD_BH(zz)  bhtab[(zz) >> 5]
+#define UNALIGNED_BH(zz)  ((zz) & 0x01f)
+
+static
+void fallbackSort(uint32_t* fmap,
+               uint32_t* eclass,
+               uint32_t* bhtab,
+               int32_t   nblock)
+{
+       int32_t ftab[257];
+       int32_t ftabCopy[256];
+       int32_t H, i, j, k, l, r, cc, cc1;
+       int32_t nNotDone;
+       int32_t nBhtab;
+       uint8_t* eclass8 = (uint8_t*)eclass;
+
+       /*
+        * Initial 1-char radix sort to generate
+        * initial fmap and initial BH bits.
+        */
+       for (i = 0; i < 257;    i++) ftab[i] = 0;
+       for (i = 0; i < nblock; i++) ftab[eclass8[i]]++;
+       for (i = 0; i < 256;    i++) ftabCopy[i] = ftab[i];
+
+       j = ftab[0];  /* bbox: optimized */
+       for (i = 1; i < 257;    i++) {
+               j += ftab[i];
+               ftab[i] = j;
+       }
+
+       for (i = 0; i < nblock; i++) {
+               j = eclass8[i];
+               k = ftab[j] - 1;
+               ftab[j] = k;
+               fmap[k] = i;
+       }
+
+       nBhtab = 2 + ((uint32_t)nblock / 32); /* bbox: unsigned div is easier */
+       for (i = 0; i < nBhtab; i++) bhtab[i] = 0;
+       for (i = 0; i < 256; i++) SET_BH(ftab[i]);
+
+       /*
+        * Inductively refine the buckets.  Kind-of an
+        * "exponential radix sort" (!), inspired by the
+        * Manber-Myers suffix array construction algorithm.
+        */
+
+       /*-- set sentinel bits for block-end detection --*/
+       for (i = 0; i < 32; i++) {
+               SET_BH(nblock + 2*i);
+               CLEAR_BH(nblock + 2*i + 1);
+       }
+
+       /*-- the log(N) loop --*/
+       H = 1;
+       while (1) {
+               j = 0;
+               for (i = 0; i < nblock; i++) {
+                       if (ISSET_BH(i))
+                               j = i;
+                       k = fmap[i] - H;
+                       if (k < 0)
+                               k += nblock;
+                       eclass[k] = j;
+               }
+
+               nNotDone = 0;
+               r = -1;
+               while (1) {
+
+               /*-- find the next non-singleton bucket --*/
+                       k = r + 1;
+                       while (ISSET_BH(k) && UNALIGNED_BH(k))
+                               k++;
+                       if (ISSET_BH(k)) {
+                               while (WORD_BH(k) == 0xffffffff) k += 32;
+                               while (ISSET_BH(k)) k++;
+                       }
+                       l = k - 1;
+                       if (l >= nblock)
+                               break;
+                       while (!ISSET_BH(k) && UNALIGNED_BH(k))
+                               k++;
+                       if (!ISSET_BH(k)) {
+                               while (WORD_BH(k) == 0x00000000) k += 32;
+                               while (!ISSET_BH(k)) k++;
+                       }
+                       r = k - 1;
+                       if (r >= nblock)
+                               break;
+
+                       /*-- now [l, r] bracket current bucket --*/
+                       if (r > l) {
+                               nNotDone += (r - l + 1);
+                               fallbackQSort3(fmap, eclass, l, r);
+
+                               /*-- scan bucket and generate header bits-- */
+                               cc = -1;
+                               for (i = l; i <= r; i++) {
+                                       cc1 = eclass[fmap[i]];
+                                       if (cc != cc1) {
+                                               SET_BH(i);
+                                               cc = cc1;
+                                       };
+                               }
+                       }
+               }
+
+               H *= 2;
+               if (H > nblock || nNotDone == 0)
+                       break;
+       }
+
+       /*
+        * Reconstruct the original block in
+        * eclass8 [0 .. nblock-1], since the
+        * previous phase destroyed it.
+        */
+       j = 0;
+       for (i = 0; i < nblock; i++) {
+               while (ftabCopy[j] == 0)
+                       j++;
+               ftabCopy[j]--;
+               eclass8[fmap[i]] = (uint8_t)j;
+       }
+       AssertH(j < 256, 1005);
+}
+
+#undef       SET_BH
+#undef     CLEAR_BH
+#undef     ISSET_BH
+#undef      WORD_BH
+#undef UNALIGNED_BH
+
+
+/*---------------------------------------------*/
+/*--- The main, O(N^2 log(N)) sorting       ---*/
+/*--- algorithm.  Faster for "normal"       ---*/
+/*--- non-repetitive blocks.                ---*/
+/*---------------------------------------------*/
+
+/*---------------------------------------------*/
+static
+NOINLINE
+int mainGtU(
+               uint32_t  i1,
+               uint32_t  i2,
+               uint8_t*  block,
+               uint16_t* quadrant,
+               uint32_t  nblock,
+               int32_t*  budget)
+{
+       int32_t  k;
+       uint8_t  c1, c2;
+       uint16_t s1, s2;
+
+/* Loop unrolling here is actually very useful
+ * (generated code is much simpler),
+ * code size increase is only 270 bytes (i386)
+ * but speeds up compression 10% overall
+ */
+
+#if CONFIG_BZIP2_FEATURE_SPEED >= 1
+
+#define TIMES_8(code) \
+       code; code; code; code; \
+       code; code; code; code;
+#define TIMES_12(code) \
+       code; code; code; code; \
+       code; code; code; code; \
+       code; code; code; code;
+
+#else
+
+#define TIMES_8(code) \
+{ \
+       int nn = 8; \
+       do { \
+               code; \
+       } while (--nn); \
+}
+#define TIMES_12(code) \
+{ \
+       int nn = 12; \
+       do { \
+               code; \
+       } while (--nn); \
+}
+
+#endif
+
+       AssertD(i1 != i2, "mainGtU");
+       TIMES_12(
+               c1 = block[i1]; c2 = block[i2];
+               if (c1 != c2) return (c1 > c2);
+               i1++; i2++;
+       )
+
+       k = nblock + 8;
+
+       do {
+               TIMES_8(
+                       c1 = block[i1]; c2 = block[i2];
+                       if (c1 != c2) return (c1 > c2);
+                       s1 = quadrant[i1]; s2 = quadrant[i2];
+                       if (s1 != s2) return (s1 > s2);
+                       i1++; i2++;
+               )
+
+               if (i1 >= nblock) i1 -= nblock;
+               if (i2 >= nblock) i2 -= nblock;
+
+               (*budget)--;
+               k -= 8;
+       } while (k >= 0);
+
+       return False;
+}
+#undef TIMES_8
+#undef TIMES_12
+
+/*---------------------------------------------*/
+/*
+ * Knuth's increments seem to work better
+ * than Incerpi-Sedgewick here.  Possibly
+ * because the number of elems to sort is
+ * usually small, typically <= 20.
+ */
+static
+const int32_t incs[14] = {
+       1, 4, 13, 40, 121, 364, 1093, 3280,
+       9841, 29524, 88573, 265720,
+       797161, 2391484
+};
+
+static
+void mainSimpleSort(uint32_t* ptr,
+               uint8_t*  block,
+               uint16_t* quadrant,
+               int32_t   nblock,
+               int32_t   lo,
+               int32_t   hi,
+               int32_t   d,
+               int32_t*  budget)
+{
+       int32_t i, j, h, bigN, hp;
+       uint32_t v;
+
+       bigN = hi - lo + 1;
+       if (bigN < 2) return;
+
+       hp = 0;
+       while (incs[hp] < bigN) hp++;
+       hp--;
+
+       for (; hp >= 0; hp--) {
+               h = incs[hp];
+
+               i = lo + h;
+               while (1) {
+                       /*-- copy 1 --*/
+                       if (i > hi) break;
+                       v = ptr[i];
+                       j = i;
+                       while (mainGtU(ptr[j-h]+d, v+d, block, quadrant, nblock, budget)) {
+                               ptr[j] = ptr[j-h];
+                               j = j - h;
+                               if (j <= (lo + h - 1)) break;
+                       }
+                       ptr[j] = v;
+                       i++;
+
+/* 1.5% overall speedup, +290 bytes */
+#if CONFIG_BZIP2_FEATURE_SPEED >= 3
+                       /*-- copy 2 --*/
+                       if (i > hi) break;
+                       v = ptr[i];
+                       j = i;
+                       while (mainGtU(ptr[j-h]+d, v+d, block, quadrant, nblock, budget)) {
+                               ptr[j] = ptr[j-h];
+                               j = j - h;
+                               if (j <= (lo + h - 1)) break;
+                       }
+                       ptr[j] = v;
+                       i++;
+
+                       /*-- copy 3 --*/
+                       if (i > hi) break;
+                       v = ptr[i];
+                       j = i;
+                       while (mainGtU(ptr[j-h]+d, v+d, block, quadrant, nblock, budget)) {
+                               ptr[j] = ptr[j-h];
+                               j = j - h;
+                               if (j <= (lo + h - 1)) break;
+                       }
+                       ptr[j] = v;
+                       i++;
+#endif
+                       if (*budget < 0) return;
+               }
+       }
+}
+
+
+/*---------------------------------------------*/
+/*
+ * The following is an implementation of
+ * an elegant 3-way quicksort for strings,
+ * described in a paper "Fast Algorithms for
+ * Sorting and Searching Strings", by Robert
+ * Sedgewick and Jon L. Bentley.
+ */
+
+static
+ALWAYS_INLINE
+uint8_t mmed3(uint8_t a, uint8_t b, uint8_t c)
+{
+       uint8_t t;
+       if (a > b) {
+               t = a;
+               a = b;
+               b = t;
+       };
+       /* here b >= a */
+       if (b > c) {
+               b = c;
+               if (a > b)
+                       b = a;
+       }
+       return b;
+}
+
+#define mpush(lz,hz,dz) \
+{ \
+       stackLo[sp] = lz; \
+       stackHi[sp] = hz; \
+       stackD [sp] = dz; \
+       sp++; \
+}
+
+#define mpop(lz,hz,dz) \
+{ \
+       sp--; \
+       lz = stackLo[sp]; \
+       hz = stackHi[sp]; \
+       dz = stackD [sp]; \
+}
+
+#define mnextsize(az) (nextHi[az] - nextLo[az])
+
+#define mnextswap(az,bz) \
+{ \
+       int32_t tz; \
+       tz = nextLo[az]; nextLo[az] = nextLo[bz]; nextLo[bz] = tz; \
+       tz = nextHi[az]; nextHi[az] = nextHi[bz]; nextHi[bz] = tz; \
+       tz = nextD [az]; nextD [az] = nextD [bz]; nextD [bz] = tz; \
+}
+
+#define MAIN_QSORT_SMALL_THRESH 20
+#define MAIN_QSORT_DEPTH_THRESH (BZ_N_RADIX + BZ_N_QSORT)
+#define MAIN_QSORT_STACK_SIZE   100
+
+static
+void mainQSort3(uint32_t* ptr,
+               uint8_t*  block,
+               uint16_t* quadrant,
+               int32_t   nblock,
+               int32_t   loSt,
+               int32_t   hiSt,
+               int32_t   dSt,
+               int32_t*  budget)
+{
+       int32_t unLo, unHi, ltLo, gtHi, n, m, med;
+       int32_t sp, lo, hi, d;
+
+       int32_t stackLo[MAIN_QSORT_STACK_SIZE];
+       int32_t stackHi[MAIN_QSORT_STACK_SIZE];
+       int32_t stackD [MAIN_QSORT_STACK_SIZE];
+
+       int32_t nextLo[3];
+       int32_t nextHi[3];
+       int32_t nextD [3];
+
+       sp = 0;
+       mpush(loSt, hiSt, dSt);
+
+       while (sp > 0) {
+               AssertH(sp < MAIN_QSORT_STACK_SIZE - 2, 1001);
+
+               mpop(lo, hi, d);
+               if (hi - lo < MAIN_QSORT_SMALL_THRESH
+                || d > MAIN_QSORT_DEPTH_THRESH
+               ) {
+                       mainSimpleSort(ptr, block, quadrant, nblock, lo, hi, d, budget);
+                       if (*budget < 0)
+                               return;
+                       continue;
+               }
+               med = (int32_t) mmed3(block[ptr[lo          ] + d],
+                                     block[ptr[hi          ] + d],
+                                     block[ptr[(lo+hi) >> 1] + d]);
+
+               unLo = ltLo = lo;
+               unHi = gtHi = hi;
+
+               while (1) {
+                       while (1) {
+                               if (unLo > unHi)
+                                       break;
+                               n = ((int32_t)block[ptr[unLo]+d]) - med;
+                               if (n == 0) {
+                                       mswap(ptr[unLo], ptr[ltLo]);
+                                       ltLo++;
+                                       unLo++;
+                                       continue;
+                               };
+                               if (n >  0) break;
+                               unLo++;
+                       }
+                       while (1) {
+                               if (unLo > unHi)
+                                       break;
+                               n = ((int32_t)block[ptr[unHi]+d]) - med;
+                               if (n == 0) {
+                                       mswap(ptr[unHi], ptr[gtHi]);
+                                       gtHi--;
+                                       unHi--;
+                                       continue;
+                               };
+                               if (n <  0) break;
+                               unHi--;
+                       }
+                       if (unLo > unHi)
+                               break;
+                       mswap(ptr[unLo], ptr[unHi]);
+                       unLo++;
+                       unHi--;
+               }
+
+               AssertD(unHi == unLo-1, "mainQSort3(2)");
+
+               if (gtHi < ltLo) {
+                       mpush(lo, hi, d + 1);
+                       continue;
+               }
+
+               n = mmin(ltLo-lo, unLo-ltLo); mvswap(ptr, lo, unLo-n, n);
+               m = mmin(hi-gtHi, gtHi-unHi); mvswap(ptr, unLo, hi-m+1, m);
+
+               n = lo + unLo - ltLo - 1;
+               m = hi - (gtHi - unHi) + 1;
+
+               nextLo[0] = lo;  nextHi[0] = n;   nextD[0] = d;
+               nextLo[1] = m;   nextHi[1] = hi;  nextD[1] = d;
+               nextLo[2] = n+1; nextHi[2] = m-1; nextD[2] = d+1;
+
+               if (mnextsize(0) < mnextsize(1)) mnextswap(0, 1);
+               if (mnextsize(1) < mnextsize(2)) mnextswap(1, 2);
+               if (mnextsize(0) < mnextsize(1)) mnextswap(0, 1);
+
+               AssertD (mnextsize(0) >= mnextsize(1), "mainQSort3(8)");
+               AssertD (mnextsize(1) >= mnextsize(2), "mainQSort3(9)");
+
+               mpush(nextLo[0], nextHi[0], nextD[0]);
+               mpush(nextLo[1], nextHi[1], nextD[1]);
+               mpush(nextLo[2], nextHi[2], nextD[2]);
+       }
+}
+
+#undef mpush
+#undef mpop
+#undef mnextsize
+#undef mnextswap
+#undef MAIN_QSORT_SMALL_THRESH
+#undef MAIN_QSORT_DEPTH_THRESH
+#undef MAIN_QSORT_STACK_SIZE
+
+
+/*---------------------------------------------*/
+/* Pre:
+ *     nblock > N_OVERSHOOT
+ *     block32 exists for [0 .. nblock-1 +N_OVERSHOOT]
+ *     ((uint8_t*)block32) [0 .. nblock-1] holds block
+ *     ptr exists for [0 .. nblock-1]
+ *
+ * Post:
+ *     ((uint8_t*)block32) [0 .. nblock-1] holds block
+ *     All other areas of block32 destroyed
+ *     ftab[0 .. 65536] destroyed
+ *     ptr [0 .. nblock-1] holds sorted order
+ *     if (*budget < 0), sorting was abandoned
+ */
+
+#define BIGFREQ(b) (ftab[((b)+1) << 8] - ftab[(b) << 8])
+#define SETMASK (1 << 21)
+#define CLEARMASK (~(SETMASK))
+
+static NOINLINE
+void mainSort(EState* state,
+               uint32_t* ptr,
+               uint8_t*  block,
+               uint16_t* quadrant,
+               uint32_t* ftab,
+               int32_t   nblock,
+               int32_t*  budget)
+{
+       int32_t  i, j, k, ss, sb;
+       uint8_t  c1;
+       int32_t  numQSorted;
+       uint16_t s;
+       Bool     bigDone[256];
+       /* bbox: moved to EState to save stack
+       int32_t  runningOrder[256];
+       int32_t  copyStart[256];
+       int32_t  copyEnd  [256];
+       */
+#define runningOrder (state->mainSort__runningOrder)
+#define copyStart    (state->mainSort__copyStart)
+#define copyEnd      (state->mainSort__copyEnd)
+
+       /*-- set up the 2-byte frequency table --*/
+       /* was: for (i = 65536; i >= 0; i--) ftab[i] = 0; */
+       memset(ftab, 0, 65537 * sizeof(ftab[0]));
+
+       j = block[0] << 8;
+       i = nblock - 1;
+/* 3%, +300 bytes */
+#if CONFIG_BZIP2_FEATURE_SPEED >= 2
+       for (; i >= 3; i -= 4) {
+               quadrant[i] = 0;
+               j = (j >> 8) | (((uint16_t)block[i]) << 8);
+               ftab[j]++;
+               quadrant[i-1] = 0;
+               j = (j >> 8) | (((uint16_t)block[i-1]) << 8);
+               ftab[j]++;
+               quadrant[i-2] = 0;
+               j = (j >> 8) | (((uint16_t)block[i-2]) << 8);
+               ftab[j]++;
+               quadrant[i-3] = 0;
+               j = (j >> 8) | (((uint16_t)block[i-3]) << 8);
+               ftab[j]++;
+       }
+#endif
+       for (; i >= 0; i--) {
+               quadrant[i] = 0;
+               j = (j >> 8) | (((uint16_t)block[i]) << 8);
+               ftab[j]++;
+       }
+
+       /*-- (emphasises close relationship of block & quadrant) --*/
+       for (i = 0; i < BZ_N_OVERSHOOT; i++) {
+               block   [nblock+i] = block[i];
+               quadrant[nblock+i] = 0;
+       }
+
+       /*-- Complete the initial radix sort --*/
+       j = ftab[0]; /* bbox: optimized */
+       for (i = 1; i <= 65536; i++) {
+               j += ftab[i];
+               ftab[i] = j;
+       }
+
+       s = block[0] << 8;
+       i = nblock - 1;
+#if CONFIG_BZIP2_FEATURE_SPEED >= 2
+       for (; i >= 3; i -= 4) {
+               s = (s >> 8) | (block[i] << 8);
+               j = ftab[s] - 1;
+               ftab[s] = j;
+               ptr[j] = i;
+               s = (s >> 8) | (block[i-1] << 8);
+               j = ftab[s] - 1;
+               ftab[s] = j;
+               ptr[j] = i-1;
+               s = (s >> 8) | (block[i-2] << 8);
+               j = ftab[s] - 1;
+               ftab[s] = j;
+               ptr[j] = i-2;
+               s = (s >> 8) | (block[i-3] << 8);
+               j = ftab[s] - 1;
+               ftab[s] = j;
+               ptr[j] = i-3;
+       }
+#endif
+       for (; i >= 0; i--) {
+               s = (s >> 8) | (block[i] << 8);
+               j = ftab[s] - 1;
+               ftab[s] = j;
+               ptr[j] = i;
+       }
+
+       /*
+        * Now ftab contains the first loc of every small bucket.
+        * Calculate the running order, from smallest to largest
+        * big bucket.
+        */
+       for (i = 0; i <= 255; i++) {
+               bigDone     [i] = False;
+               runningOrder[i] = i;
+       }
+
+       {
+               int32_t vv;
+               /* bbox: was: int32_t h = 1; */
+               /* do h = 3 * h + 1; while (h <= 256); */
+               uint32_t h = 364;
+
+               do {
+                       /*h = h / 3;*/
+                       h = (h * 171) >> 9; /* bbox: fast h/3 */
+                       for (i = h; i <= 255; i++) {
+                               vv = runningOrder[i];
+                               j = i;
+                               while (BIGFREQ(runningOrder[j-h]) > BIGFREQ(vv)) {
+                                       runningOrder[j] = runningOrder[j-h];
+                                       j = j - h;
+                                       if (j <= (h - 1))
+                                               goto zero;
+                               }
+ zero:
+                               runningOrder[j] = vv;
+                       }
+               } while (h != 1);
+       }
+
+       /*
+        * The main sorting loop.
+        */
+
+       numQSorted = 0;
+
+       for (i = 0; i <= 255; i++) {
+
+               /*
+                * Process big buckets, starting with the least full.
+                * Basically this is a 3-step process in which we call
+                * mainQSort3 to sort the small buckets [ss, j], but
+                * also make a big effort to avoid the calls if we can.
+                */
+               ss = runningOrder[i];
+
+               /*
+                * Step 1:
+                * Complete the big bucket [ss] by quicksorting
+                * any unsorted small buckets [ss, j], for j != ss.
+                * Hopefully previous pointer-scanning phases have already
+                * completed many of the small buckets [ss, j], so
+                * we don't have to sort them at all.
+                */
+               for (j = 0; j <= 255; j++) {
+                       if (j != ss) {
+                               sb = (ss << 8) + j;
+                               if (!(ftab[sb] & SETMASK)) {
+                                       int32_t lo =  ftab[sb]   & CLEARMASK;
+                                       int32_t hi = (ftab[sb+1] & CLEARMASK) - 1;
+                                       if (hi > lo) {
+                                               mainQSort3(
+                                                       ptr, block, quadrant, nblock,
+                                                       lo, hi, BZ_N_RADIX, budget
+                                               );
+                                               if (*budget < 0) return;
+                                               numQSorted += (hi - lo + 1);
+                                       }
+                               }
+                               ftab[sb] |= SETMASK;
+                       }
+               }
+
+               AssertH(!bigDone[ss], 1006);
+
+               /*
+                * Step 2:
+                * Now scan this big bucket [ss] so as to synthesise the
+                * sorted order for small buckets [t, ss] for all t,
+                * including, magically, the bucket [ss,ss] too.
+                * This will avoid doing Real Work in subsequent Step 1's.
+                */
+               {
+                       for (j = 0; j <= 255; j++) {
+                               copyStart[j] =  ftab[(j << 8) + ss]     & CLEARMASK;
+                               copyEnd  [j] = (ftab[(j << 8) + ss + 1] & CLEARMASK) - 1;
+                       }
+                       for (j = ftab[ss << 8] & CLEARMASK; j < copyStart[ss]; j++) {
+                               k = ptr[j] - 1;
+                               if (k < 0)
+                                       k += nblock;
+                               c1 = block[k];
+                               if (!bigDone[c1])
+                                       ptr[copyStart[c1]++] = k;
+                       }
+                       for (j = (ftab[(ss+1) << 8] & CLEARMASK) - 1; j > copyEnd[ss]; j--) {
+                               k = ptr[j]-1;
+                               if (k < 0)
+                                       k += nblock;
+                               c1 = block[k];
+                               if (!bigDone[c1])
+                                       ptr[copyEnd[c1]--] = k;
+                       }
+               }
+
+               /* Extremely rare case missing in bzip2-1.0.0 and 1.0.1.
+                * Necessity for this case is demonstrated by compressing
+                * a sequence of approximately 48.5 million of character
+                * 251; 1.0.0/1.0.1 will then die here. */
+               AssertH((copyStart[ss]-1 == copyEnd[ss]) \
+                    || (copyStart[ss] == 0 && copyEnd[ss] == nblock-1), 1007);
+
+               for (j = 0; j <= 255; j++)
+                       ftab[(j << 8) + ss] |= SETMASK;
+
+               /*
+                * Step 3:
+                * The [ss] big bucket is now done.  Record this fact,
+                * and update the quadrant descriptors.  Remember to
+                * update quadrants in the overshoot area too, if
+                * necessary.  The "if (i < 255)" test merely skips
+                * this updating for the last bucket processed, since
+                * updating for the last bucket is pointless.
+                *
+                * The quadrant array provides a way to incrementally
+                * cache sort orderings, as they appear, so as to
+                * make subsequent comparisons in fullGtU() complete
+                * faster.  For repetitive blocks this makes a big
+                * difference (but not big enough to be able to avoid
+                * the fallback sorting mechanism, exponential radix sort).
+                *
+                * The precise meaning is: at all times:
+                *
+                *      for 0 <= i < nblock and 0 <= j <= nblock
+                *
+                *      if block[i] != block[j],
+                *
+                *              then the relative values of quadrant[i] and
+                *                        quadrant[j] are meaningless.
+                *
+                *              else {
+                *                      if quadrant[i] < quadrant[j]
+                *                              then the string starting at i lexicographically
+                *                              precedes the string starting at j
+                *
+                *                      else if quadrant[i] > quadrant[j]
+                *                              then the string starting at j lexicographically
+                *                              precedes the string starting at i
+                *
+                *                      else
+                *                              the relative ordering of the strings starting
+                *                              at i and j has not yet been determined.
+                *              }
+                */
+               bigDone[ss] = True;
+
+               if (i < 255) {
+                       int32_t bbStart = ftab[ss << 8] & CLEARMASK;
+                       int32_t bbSize  = (ftab[(ss+1) << 8] & CLEARMASK) - bbStart;
+                       int32_t shifts  = 0;
+
+                       while ((bbSize >> shifts) > 65534) shifts++;
+
+                       for (j = bbSize-1; j >= 0; j--) {
+                               int32_t a2update   = ptr[bbStart + j];
+                               uint16_t qVal      = (uint16_t)(j >> shifts);
+                               quadrant[a2update] = qVal;
+                               if (a2update < BZ_N_OVERSHOOT)
+                                       quadrant[a2update + nblock] = qVal;
+                       }
+                       AssertH(((bbSize-1) >> shifts) <= 65535, 1002);
+               }
+       }
+#undef runningOrder
+#undef copyStart
+#undef copyEnd
+}
+
+#undef BIGFREQ
+#undef SETMASK
+#undef CLEARMASK
+
+
+/*---------------------------------------------*/
+/* Pre:
+ *     nblock > 0
+ *     arr2 exists for [0 .. nblock-1 +N_OVERSHOOT]
+ *       ((uint8_t*)arr2)[0 .. nblock-1] holds block
+ *     arr1 exists for [0 .. nblock-1]
+ *
+ * Post:
+ *     ((uint8_t*)arr2) [0 .. nblock-1] holds block
+ *     All other areas of block destroyed
+ *     ftab[0 .. 65536] destroyed
+ *     arr1[0 .. nblock-1] holds sorted order
+ */
+static NOINLINE
+void BZ2_blockSort(EState* s)
+{
+       /* In original bzip2 1.0.4, it's a parameter, but 30
+        * (which was the default) should work ok. */
+       enum { wfact = 30 };
+
+       uint32_t* ptr    = s->ptr;
+       uint8_t*  block  = s->block;
+       uint32_t* ftab   = s->ftab;
+       int32_t   nblock = s->nblock;
+       uint16_t* quadrant;
+       int32_t   budget;
+       int32_t   i;
+
+       if (nblock < 10000) {
+               fallbackSort(s->arr1, s->arr2, ftab, nblock);
+       } else {
+               /* Calculate the location for quadrant, remembering to get
+                * the alignment right.  Assumes that &(block[0]) is at least
+                * 2-byte aligned -- this should be ok since block is really
+                * the first section of arr2.
+                */
+               i = nblock + BZ_N_OVERSHOOT;
+               if (i & 1) i++;
+               quadrant = (uint16_t*)(&(block[i]));
+
+               /* (wfact-1) / 3 puts the default-factor-30
+                * transition point at very roughly the same place as
+                * with v0.1 and v0.9.0.
+                * Not that it particularly matters any more, since the
+                * resulting compressed stream is now the same regardless
+                * of whether or not we use the main sort or fallback sort.
+                */
+               budget = nblock * ((wfact-1) / 3);
+
+               mainSort(s, ptr, block, quadrant, ftab, nblock, &budget);
+               if (budget < 0) {
+                       fallbackSort(s->arr1, s->arr2, ftab, nblock);
+               }
+       }
+
+       s->origPtr = -1;
+       for (i = 0; i < s->nblock; i++)
+               if (ptr[i] == 0) {
+                       s->origPtr = i;
+                       break;
+               };
+
+       AssertH(s->origPtr != -1, 1003);
+}
+
+
+/*-------------------------------------------------------------*/
+/*--- end                                       blocksort.c ---*/
+/*-------------------------------------------------------------*/
diff --git a/archival/bz/bzlib.c b/archival/bz/bzlib.c
new file mode 100644 (file)
index 0000000..9957c2f
--- /dev/null
@@ -0,0 +1,429 @@
+/*
+ * bzip2 is written by Julian Seward <jseward@bzip.org>.
+ * Adapted for busybox by Denys Vlasenko <vda.linux@googlemail.com>.
+ * See README and LICENSE files in this directory for more information.
+ */
+
+/*-------------------------------------------------------------*/
+/*--- Library top-level functions.                          ---*/
+/*---                                               bzlib.c ---*/
+/*-------------------------------------------------------------*/
+
+/* ------------------------------------------------------------------
+This file is part of bzip2/libbzip2, a program and library for
+lossless, block-sorting data compression.
+
+bzip2/libbzip2 version 1.0.4 of 20 December 2006
+Copyright (C) 1996-2006 Julian Seward <jseward@bzip.org>
+
+Please read the WARNING, DISCLAIMER and PATENTS sections in the
+README file.
+
+This program is released under the terms of the license contained
+in the file LICENSE.
+------------------------------------------------------------------ */
+
+/* CHANGES
+ * 0.9.0    -- original version.
+ * 0.9.0a/b -- no changes in this file.
+ * 0.9.0c   -- made zero-length BZ_FLUSH work correctly in bzCompress().
+ *             fixed bzWrite/bzRead to ignore zero-length requests.
+ *            fixed bzread to correctly handle read requests after EOF.
+ *             wrong parameter order in call to bzDecompressInit in
+ *             bzBuffToBuffDecompress.  Fixed.
+ */
+
+/* #include "bzlib_private.h" */
+
+/*---------------------------------------------------*/
+/*--- Compression stuff                           ---*/
+/*---------------------------------------------------*/
+
+/*---------------------------------------------------*/
+#if BZ_LIGHT_DEBUG
+static
+void bz_assert_fail(int errcode)
+{
+       /* if (errcode == 1007) bb_error_msg_and_die("probably bad RAM"); */
+       bb_error_msg_and_die("internal error %d", errcode);
+}
+#endif
+
+/*---------------------------------------------------*/
+static
+void prepare_new_block(EState* s)
+{
+       int i;
+       s->nblock = 0;
+       s->numZ = 0;
+       s->state_out_pos = 0;
+       BZ_INITIALISE_CRC(s->blockCRC);
+       /* inlined memset would be nice to have here */
+       for (i = 0; i < 256; i++)
+               s->inUse[i] = 0;
+       s->blockNo++;
+}
+
+
+/*---------------------------------------------------*/
+static
+ALWAYS_INLINE
+void init_RL(EState* s)
+{
+       s->state_in_ch = 256;
+       s->state_in_len = 0;
+}
+
+
+static
+int isempty_RL(EState* s)
+{
+       return (s->state_in_ch >= 256 || s->state_in_len <= 0);
+}
+
+
+/*---------------------------------------------------*/
+static
+void BZ2_bzCompressInit(bz_stream *strm, int blockSize100k)
+{
+       int32_t n;
+       EState* s;
+
+       s = xzalloc(sizeof(EState));
+       s->strm = strm;
+
+       n        = 100000 * blockSize100k;
+       s->arr1  = xmalloc(n                    * sizeof(uint32_t));
+       s->mtfv  = (uint16_t*)s->arr1;
+       s->ptr   = (uint32_t*)s->arr1;
+       s->arr2  = xmalloc((n + BZ_N_OVERSHOOT) * sizeof(uint32_t));
+       s->block = (uint8_t*)s->arr2;
+       s->ftab  = xmalloc(65537                * sizeof(uint32_t));
+
+       s->crc32table = crc32_filltable(NULL, 1);
+
+       s->state             = BZ_S_INPUT;
+       s->mode              = BZ_M_RUNNING;
+       s->blockSize100k     = blockSize100k;
+       s->nblockMAX         = n - 19;
+
+       strm->state          = s;
+       /*strm->total_in     = 0;*/
+       strm->total_out      = 0;
+       init_RL(s);
+       prepare_new_block(s);
+}
+
+
+/*---------------------------------------------------*/
+static
+void add_pair_to_block(EState* s)
+{
+       int32_t i;
+       uint8_t ch = (uint8_t)(s->state_in_ch);
+       for (i = 0; i < s->state_in_len; i++) {
+               BZ_UPDATE_CRC(s, s->blockCRC, ch);
+       }
+       s->inUse[s->state_in_ch] = 1;
+       switch (s->state_in_len) {
+               case 3:
+                       s->block[s->nblock] = (uint8_t)ch; s->nblock++;
+                       /* fall through */
+               case 2:
+                       s->block[s->nblock] = (uint8_t)ch; s->nblock++;
+                       /* fall through */
+               case 1:
+                       s->block[s->nblock] = (uint8_t)ch; s->nblock++;
+                       break;
+               default:
+                       s->inUse[s->state_in_len - 4] = 1;
+                       s->block[s->nblock] = (uint8_t)ch; s->nblock++;
+                       s->block[s->nblock] = (uint8_t)ch; s->nblock++;
+                       s->block[s->nblock] = (uint8_t)ch; s->nblock++;
+                       s->block[s->nblock] = (uint8_t)ch; s->nblock++;
+                       s->block[s->nblock] = (uint8_t)(s->state_in_len - 4);
+                       s->nblock++;
+                       break;
+       }
+}
+
+
+/*---------------------------------------------------*/
+static
+void flush_RL(EState* s)
+{
+       if (s->state_in_ch < 256) add_pair_to_block(s);
+       init_RL(s);
+}
+
+
+/*---------------------------------------------------*/
+#define ADD_CHAR_TO_BLOCK(zs, zchh0) \
+{ \
+       uint32_t zchh = (uint32_t)(zchh0); \
+       /*-- fast track the common case --*/ \
+       if (zchh != zs->state_in_ch && zs->state_in_len == 1) { \
+               uint8_t ch = (uint8_t)(zs->state_in_ch); \
+               BZ_UPDATE_CRC(zs, zs->blockCRC, ch); \
+               zs->inUse[zs->state_in_ch] = 1; \
+               zs->block[zs->nblock] = (uint8_t)ch; \
+               zs->nblock++; \
+               zs->state_in_ch = zchh; \
+       } \
+       else \
+       /*-- general, uncommon cases --*/ \
+       if (zchh != zs->state_in_ch || zs->state_in_len == 255) { \
+               if (zs->state_in_ch < 256) \
+                       add_pair_to_block(zs); \
+               zs->state_in_ch = zchh; \
+               zs->state_in_len = 1; \
+       } else { \
+               zs->state_in_len++; \
+       } \
+}
+
+
+/*---------------------------------------------------*/
+static
+void /*Bool*/ copy_input_until_stop(EState* s)
+{
+       /*Bool progress_in = False;*/
+
+#ifdef SAME_CODE_AS_BELOW
+       if (s->mode == BZ_M_RUNNING) {
+               /*-- fast track the common case --*/
+               while (1) {
+                       /*-- no input? --*/
+                       if (s->strm->avail_in == 0) break;
+                       /*-- block full? --*/
+                       if (s->nblock >= s->nblockMAX) break;
+                       /*progress_in = True;*/
+                       ADD_CHAR_TO_BLOCK(s, (uint32_t)(*(uint8_t*)(s->strm->next_in)));
+                       s->strm->next_in++;
+                       s->strm->avail_in--;
+                       /*s->strm->total_in++;*/
+               }
+       } else
+#endif
+       {
+               /*-- general, uncommon case --*/
+               while (1) {
+                       /*-- no input? --*/
+                       if (s->strm->avail_in == 0) break;
+                       /*-- block full? --*/
+                       if (s->nblock >= s->nblockMAX) break;
+               //#     /*-- flush/finish end? --*/
+               //#     if (s->avail_in_expect == 0) break;
+                       /*progress_in = True;*/
+                       ADD_CHAR_TO_BLOCK(s, *(uint8_t*)(s->strm->next_in));
+                       s->strm->next_in++;
+                       s->strm->avail_in--;
+                       /*s->strm->total_in++;*/
+               //#     s->avail_in_expect--;
+               }
+       }
+       /*return progress_in;*/
+}
+
+
+/*---------------------------------------------------*/
+static
+void /*Bool*/ copy_output_until_stop(EState* s)
+{
+       /*Bool progress_out = False;*/
+
+       while (1) {
+               /*-- no output space? --*/
+               if (s->strm->avail_out == 0) break;
+
+               /*-- block done? --*/
+               if (s->state_out_pos >= s->numZ) break;
+
+               /*progress_out = True;*/
+               *(s->strm->next_out) = s->zbits[s->state_out_pos];
+               s->state_out_pos++;
+               s->strm->avail_out--;
+               s->strm->next_out++;
+               s->strm->total_out++;
+       }
+       /*return progress_out;*/
+}
+
+
+/*---------------------------------------------------*/
+static
+void /*Bool*/ handle_compress(bz_stream *strm)
+{
+       /*Bool progress_in  = False;*/
+       /*Bool progress_out = False;*/
+       EState* s = strm->state;
+
+       while (1) {
+               if (s->state == BZ_S_OUTPUT) {
+                       /*progress_out |=*/ copy_output_until_stop(s);
+                       if (s->state_out_pos < s->numZ) break;
+                       if (s->mode == BZ_M_FINISHING
+                       //# && s->avail_in_expect == 0
+                        && s->strm->avail_in == 0
+                        && isempty_RL(s))
+                               break;
+                       prepare_new_block(s);
+                       s->state = BZ_S_INPUT;
+#ifdef FLUSH_IS_UNUSED
+                       if (s->mode == BZ_M_FLUSHING
+                        && s->avail_in_expect == 0
+                        && isempty_RL(s))
+                               break;
+#endif
+               }
+
+               if (s->state == BZ_S_INPUT) {
+                       /*progress_in |=*/ copy_input_until_stop(s);
+                       //#if (s->mode != BZ_M_RUNNING && s->avail_in_expect == 0) {
+                       if (s->mode != BZ_M_RUNNING && s->strm->avail_in == 0) {
+                               flush_RL(s);
+                               BZ2_compressBlock(s, (s->mode == BZ_M_FINISHING));
+                               s->state = BZ_S_OUTPUT;
+                       } else
+                       if (s->nblock >= s->nblockMAX) {
+                               BZ2_compressBlock(s, 0);
+                               s->state = BZ_S_OUTPUT;
+                       } else
+                       if (s->strm->avail_in == 0) {
+                               break;
+                       }
+               }
+       }
+
+       /*return progress_in || progress_out;*/
+}
+
+
+/*---------------------------------------------------*/
+static
+int BZ2_bzCompress(bz_stream *strm, int action)
+{
+       /*Bool progress;*/
+       EState* s;
+
+       s = strm->state;
+
+       switch (s->mode) {
+               case BZ_M_RUNNING:
+                       if (action == BZ_RUN) {
+                               /*progress =*/ handle_compress(strm);
+                               /*return progress ? BZ_RUN_OK : BZ_PARAM_ERROR;*/
+                               return BZ_RUN_OK;
+                       }
+#ifdef FLUSH_IS_UNUSED
+                       else
+                       if (action == BZ_FLUSH) {
+                               //#s->avail_in_expect = strm->avail_in;
+                               s->mode = BZ_M_FLUSHING;
+                               goto case_BZ_M_FLUSHING;
+                       }
+#endif
+                       else
+                       /*if (action == BZ_FINISH)*/ {
+                               //#s->avail_in_expect = strm->avail_in;
+                               s->mode = BZ_M_FINISHING;
+                               goto case_BZ_M_FINISHING;
+                       }
+
+#ifdef FLUSH_IS_UNUSED
+ case_BZ_M_FLUSHING:
+               case BZ_M_FLUSHING:
+                       /*if (s->avail_in_expect != s->strm->avail_in)
+                               return BZ_SEQUENCE_ERROR;*/
+                       /*progress =*/ handle_compress(strm);
+                       if (s->avail_in_expect > 0 || !isempty_RL(s) || s->state_out_pos < s->numZ)
+                               return BZ_FLUSH_OK;
+                       s->mode = BZ_M_RUNNING;
+                       return BZ_RUN_OK;
+#endif
+
+ case_BZ_M_FINISHING:
+               /*case BZ_M_FINISHING:*/
+               default:
+                       /*if (s->avail_in_expect != s->strm->avail_in)
+                               return BZ_SEQUENCE_ERROR;*/
+                       /*progress =*/ handle_compress(strm);
+                       /*if (!progress) return BZ_SEQUENCE_ERROR;*/
+                       //#if (s->avail_in_expect > 0 || !isempty_RL(s) || s->state_out_pos < s->numZ)
+                       //#     return BZ_FINISH_OK;
+                       if (s->strm->avail_in > 0 || !isempty_RL(s) || s->state_out_pos < s->numZ)
+                               return BZ_FINISH_OK;
+                       /*s->mode = BZ_M_IDLE;*/
+                       return BZ_STREAM_END;
+       }
+       /* return BZ_OK; --not reached--*/
+}
+
+
+/*---------------------------------------------------*/
+#if ENABLE_FEATURE_CLEAN_UP
+static
+void BZ2_bzCompressEnd(bz_stream *strm)
+{
+       EState* s;
+
+       s = strm->state;
+       free(s->arr1);
+       free(s->arr2);
+       free(s->ftab);
+       free(s->crc32table);
+       free(strm->state);
+}
+#endif
+
+
+/*---------------------------------------------------*/
+/*--- Misc convenience stuff                      ---*/
+/*---------------------------------------------------*/
+
+/*---------------------------------------------------*/
+#ifdef EXAMPLE_CODE_FOR_MEM_TO_MEM_COMPRESSION
+static
+int BZ2_bzBuffToBuffCompress(char* dest,
+               unsigned int* destLen,
+               char*         source,
+               unsigned int  sourceLen,
+               int           blockSize100k)
+{
+       bz_stream strm;
+       int ret;
+
+       if (dest == NULL || destLen == NULL ||
+                source == NULL ||
+                blockSize100k < 1 || blockSize100k > 9)
+               return BZ_PARAM_ERROR;
+
+       BZ2_bzCompressInit(&strm, blockSize100k);
+
+       strm.next_in = source;
+       strm.next_out = dest;
+       strm.avail_in = sourceLen;
+       strm.avail_out = *destLen;
+
+       ret = BZ2_bzCompress(&strm, BZ_FINISH);
+       if (ret == BZ_FINISH_OK) goto output_overflow;
+       if (ret != BZ_STREAM_END) goto errhandler;
+
+       /* normal termination */
+       *destLen -= strm.avail_out;
+       BZ2_bzCompressEnd(&strm);
+       return BZ_OK;
+
+ output_overflow:
+       BZ2_bzCompressEnd(&strm);
+       return BZ_OUTBUFF_FULL;
+
+ errhandler:
+       BZ2_bzCompressEnd(&strm);
+       return ret;
+}
+#endif
+
+/*-------------------------------------------------------------*/
+/*--- end                                           bzlib.c ---*/
+/*-------------------------------------------------------------*/
diff --git a/archival/bz/bzlib.h b/archival/bz/bzlib.h
new file mode 100644 (file)
index 0000000..1bb811c
--- /dev/null
@@ -0,0 +1,65 @@
+/*
+ * bzip2 is written by Julian Seward <jseward@bzip.org>.
+ * Adapted for busybox by Denys Vlasenko <vda.linux@googlemail.com>.
+ * See README and LICENSE files in this directory for more information.
+ */
+
+/*-------------------------------------------------------------*/
+/*--- Public header file for the library.                   ---*/
+/*---                                               bzlib.h ---*/
+/*-------------------------------------------------------------*/
+
+/* ------------------------------------------------------------------
+This file is part of bzip2/libbzip2, a program and library for
+lossless, block-sorting data compression.
+
+bzip2/libbzip2 version 1.0.4 of 20 December 2006
+Copyright (C) 1996-2006 Julian Seward <jseward@bzip.org>
+
+Please read the WARNING, DISCLAIMER and PATENTS sections in the
+README file.
+
+This program is released under the terms of the license contained
+in the file LICENSE.
+------------------------------------------------------------------ */
+
+#define BZ_RUN               0
+#define BZ_FLUSH             1
+#define BZ_FINISH            2
+
+#define BZ_OK                0
+#define BZ_RUN_OK            1
+#define BZ_FLUSH_OK          2
+#define BZ_FINISH_OK         3
+#define BZ_STREAM_END        4
+#define BZ_SEQUENCE_ERROR    (-1)
+#define BZ_PARAM_ERROR       (-2)
+#define BZ_MEM_ERROR         (-3)
+#define BZ_DATA_ERROR        (-4)
+#define BZ_DATA_ERROR_MAGIC  (-5)
+#define BZ_IO_ERROR          (-6)
+#define BZ_UNEXPECTED_EOF    (-7)
+#define BZ_OUTBUFF_FULL      (-8)
+#define BZ_CONFIG_ERROR      (-9)
+
+typedef struct bz_stream {
+       void *state;
+       char *next_in;
+       char *next_out;
+       unsigned avail_in;
+       unsigned avail_out;
+       /*unsigned long long total_in;*/
+       unsigned long long total_out;
+} bz_stream;
+
+/*-- Core (low-level) library functions --*/
+
+static void BZ2_bzCompressInit(bz_stream *strm, int blockSize100k);
+static int BZ2_bzCompress(bz_stream *strm, int action);
+#if ENABLE_FEATURE_CLEAN_UP
+static void BZ2_bzCompressEnd(bz_stream *strm);
+#endif
+
+/*-------------------------------------------------------------*/
+/*--- end                                           bzlib.h ---*/
+/*-------------------------------------------------------------*/
diff --git a/archival/bz/bzlib_private.h b/archival/bz/bzlib_private.h
new file mode 100644 (file)
index 0000000..6430ce4
--- /dev/null
@@ -0,0 +1,219 @@
+/*
+ * bzip2 is written by Julian Seward <jseward@bzip.org>.
+ * Adapted for busybox by Denys Vlasenko <vda.linux@googlemail.com>.
+ * See README and LICENSE files in this directory for more information.
+ */
+
+/*-------------------------------------------------------------*/
+/*--- Private header file for the library.                  ---*/
+/*---                                       bzlib_private.h ---*/
+/*-------------------------------------------------------------*/
+
+/* ------------------------------------------------------------------
+This file is part of bzip2/libbzip2, a program and library for
+lossless, block-sorting data compression.
+
+bzip2/libbzip2 version 1.0.4 of 20 December 2006
+Copyright (C) 1996-2006 Julian Seward <jseward@bzip.org>
+
+Please read the WARNING, DISCLAIMER and PATENTS sections in the
+README file.
+
+This program is released under the terms of the license contained
+in the file LICENSE.
+------------------------------------------------------------------ */
+
+/* #include "bzlib.h" */
+
+/*-- General stuff. --*/
+
+typedef unsigned char Bool;
+
+#define True  ((Bool)1)
+#define False ((Bool)0)
+
+#if BZ_LIGHT_DEBUG
+static void bz_assert_fail(int errcode) NORETURN;
+#define AssertH(cond, errcode) \
+do { \
+       if (!(cond)) \
+               bz_assert_fail(errcode); \
+} while (0)
+#else
+#define AssertH(cond, msg) do { } while (0)
+#endif
+
+#if BZ_DEBUG
+#define AssertD(cond, msg) \
+do { \
+       if (!(cond)) \
+               bb_error_msg_and_die("(debug build): internal error %s", msg); \
+} while (0)
+#else
+#define AssertD(cond, msg) do { } while (0)
+#endif
+
+
+/*-- Header bytes. --*/
+
+#define BZ_HDR_B 0x42   /* 'B' */
+#define BZ_HDR_Z 0x5a   /* 'Z' */
+#define BZ_HDR_h 0x68   /* 'h' */
+#define BZ_HDR_0 0x30   /* '0' */
+
+#define BZ_HDR_BZh0 0x425a6830
+
+/*-- Constants for the back end. --*/
+
+#define BZ_MAX_ALPHA_SIZE 258
+#define BZ_MAX_CODE_LEN    23
+
+#define BZ_RUNA 0
+#define BZ_RUNB 1
+
+#define BZ_N_GROUPS 6
+#define BZ_G_SIZE   50
+#define BZ_N_ITERS  4
+
+#define BZ_MAX_SELECTORS (2 + (900000 / BZ_G_SIZE))
+
+
+/*-- Stuff for doing CRCs. --*/
+
+#define BZ_INITIALISE_CRC(crcVar) \
+{ \
+       crcVar = 0xffffffffL; \
+}
+
+#define BZ_FINALISE_CRC(crcVar) \
+{ \
+       crcVar = ~(crcVar); \
+}
+
+#define BZ_UPDATE_CRC(s, crcVar, cha) \
+{ \
+       crcVar = (crcVar << 8) ^ s->crc32table[(crcVar >> 24) ^ ((uint8_t)cha)]; \
+}
+
+
+/*-- States and modes for compression. --*/
+
+#define BZ_M_IDLE      1
+#define BZ_M_RUNNING   2
+#define BZ_M_FLUSHING  3
+#define BZ_M_FINISHING 4
+
+#define BZ_S_OUTPUT    1
+#define BZ_S_INPUT     2
+
+#define BZ_N_RADIX 2
+#define BZ_N_QSORT 12
+#define BZ_N_SHELL 18
+#define BZ_N_OVERSHOOT (BZ_N_RADIX + BZ_N_QSORT + BZ_N_SHELL + 2)
+
+
+/*-- Structure holding all the compression-side stuff. --*/
+
+typedef struct EState {
+       /* pointer back to the struct bz_stream */
+       bz_stream *strm;
+
+       /* mode this stream is in, and whether inputting */
+       /* or outputting data */
+       int32_t  mode;
+       int32_t  state;
+
+       /* remembers avail_in when flush/finish requested */
+/* bbox: not needed, strm->avail_in always has the same value */
+/* commented out with '//#' throughout the code */
+       /* uint32_t avail_in_expect; */
+
+       /* for doing the block sorting */
+       int32_t  origPtr;
+       uint32_t *arr1;
+       uint32_t *arr2;
+       uint32_t *ftab;
+
+       /* aliases for arr1 and arr2 */
+       uint32_t *ptr;
+       uint8_t  *block;
+       uint16_t *mtfv;
+       uint8_t  *zbits;
+
+       /* guess what */
+       uint32_t *crc32table;
+
+       /* run-length-encoding of the input */
+       uint32_t state_in_ch;
+       int32_t  state_in_len;
+
+       /* input and output limits and current posns */
+       int32_t  nblock;
+       int32_t  nblockMAX;
+       int32_t  numZ;
+       int32_t  state_out_pos;
+
+       /* the buffer for bit stream creation */
+       uint32_t bsBuff;
+       int32_t  bsLive;
+
+       /* block and combined CRCs */
+       uint32_t blockCRC;
+       uint32_t combinedCRC;
+
+       /* misc administratium */
+       int32_t  blockNo;
+       int32_t  blockSize100k;
+
+       /* stuff for coding the MTF values */
+       int32_t  nMTF;
+
+       /* map of bytes used in block */
+       int32_t  nInUse;
+       Bool     inUse[256] ALIGNED(sizeof(long));
+       uint8_t  unseqToSeq[256];
+
+       /* stuff for coding the MTF values */
+       int32_t  mtfFreq    [BZ_MAX_ALPHA_SIZE];
+       uint8_t  selector   [BZ_MAX_SELECTORS];
+       uint8_t  selectorMtf[BZ_MAX_SELECTORS];
+
+       uint8_t  len[BZ_N_GROUPS][BZ_MAX_ALPHA_SIZE];
+
+       /* stack-saving measures: these can be local, but they are too big */
+       int32_t  sendMTFValues__code [BZ_N_GROUPS][BZ_MAX_ALPHA_SIZE];
+       int32_t  sendMTFValues__rfreq[BZ_N_GROUPS][BZ_MAX_ALPHA_SIZE];
+#if CONFIG_BZIP2_FEATURE_SPEED >= 5
+       /* second dimension: only 3 needed; 4 makes index calculations faster */
+       uint32_t sendMTFValues__len_pack[BZ_MAX_ALPHA_SIZE][4];
+#endif
+       int32_t  BZ2_hbMakeCodeLengths__heap  [BZ_MAX_ALPHA_SIZE + 2];
+       int32_t  BZ2_hbMakeCodeLengths__weight[BZ_MAX_ALPHA_SIZE * 2];
+       int32_t  BZ2_hbMakeCodeLengths__parent[BZ_MAX_ALPHA_SIZE * 2];
+
+       int32_t  mainSort__runningOrder[256];
+       int32_t  mainSort__copyStart[256];
+       int32_t  mainSort__copyEnd[256];
+} EState;
+
+
+/*-- compression. --*/
+
+static void
+BZ2_blockSort(EState*);
+
+static void
+BZ2_compressBlock(EState*, int);
+
+static void
+BZ2_bsInitWrite(EState*);
+
+static void
+BZ2_hbAssignCodes(int32_t*, uint8_t*, int32_t, int32_t, int32_t);
+
+static void
+BZ2_hbMakeCodeLengths(EState*, uint8_t*, int32_t*, int32_t, int32_t);
+
+/*-------------------------------------------------------------*/
+/*--- end                                   bzlib_private.h ---*/
+/*-------------------------------------------------------------*/
diff --git a/archival/bz/compress.c b/archival/bz/compress.c
new file mode 100644 (file)
index 0000000..640b887
--- /dev/null
@@ -0,0 +1,687 @@
+/*
+ * bzip2 is written by Julian Seward <jseward@bzip.org>.
+ * Adapted for busybox by Denys Vlasenko <vda.linux@googlemail.com>.
+ * See README and LICENSE files in this directory for more information.
+ */
+
+/*-------------------------------------------------------------*/
+/*--- Compression machinery (not incl block sorting)        ---*/
+/*---                                            compress.c ---*/
+/*-------------------------------------------------------------*/
+
+/* ------------------------------------------------------------------
+This file is part of bzip2/libbzip2, a program and library for
+lossless, block-sorting data compression.
+
+bzip2/libbzip2 version 1.0.4 of 20 December 2006
+Copyright (C) 1996-2006 Julian Seward <jseward@bzip.org>
+
+Please read the WARNING, DISCLAIMER and PATENTS sections in the
+README file.
+
+This program is released under the terms of the license contained
+in the file LICENSE.
+------------------------------------------------------------------ */
+
+/* CHANGES
+ * 0.9.0    -- original version.
+ * 0.9.0a/b -- no changes in this file.
+ * 0.9.0c   -- changed setting of nGroups in sendMTFValues()
+ *             so as to do a bit better on small files
+*/
+
+/* #include "bzlib_private.h" */
+
+/*---------------------------------------------------*/
+/*--- Bit stream I/O                              ---*/
+/*---------------------------------------------------*/
+
+/*---------------------------------------------------*/
+static
+void BZ2_bsInitWrite(EState* s)
+{
+       s->bsLive = 0;
+       s->bsBuff = 0;
+}
+
+
+/*---------------------------------------------------*/
+static NOINLINE
+void bsFinishWrite(EState* s)
+{
+       while (s->bsLive > 0) {
+               s->zbits[s->numZ] = (uint8_t)(s->bsBuff >> 24);
+               s->numZ++;
+               s->bsBuff <<= 8;
+               s->bsLive -= 8;
+       }
+}
+
+
+/*---------------------------------------------------*/
+static
+/* Helps only on level 5, on other levels hurts. ? */
+#if CONFIG_BZIP2_FEATURE_SPEED >= 5
+ALWAYS_INLINE
+#endif
+void bsW(EState* s, int32_t n, uint32_t v)
+{
+       while (s->bsLive >= 8) {
+               s->zbits[s->numZ] = (uint8_t)(s->bsBuff >> 24);
+               s->numZ++;
+               s->bsBuff <<= 8;
+               s->bsLive -= 8;
+       }
+       s->bsBuff |= (v << (32 - s->bsLive - n));
+       s->bsLive += n;
+}
+
+
+/*---------------------------------------------------*/
+static
+void bsPutU32(EState* s, unsigned u)
+{
+       bsW(s, 8, (u >> 24) & 0xff);
+       bsW(s, 8, (u >> 16) & 0xff);
+       bsW(s, 8, (u >>  8) & 0xff);
+       bsW(s, 8,  u        & 0xff);
+}
+
+
+/*---------------------------------------------------*/
+static
+void bsPutU16(EState* s, unsigned u)
+{
+       bsW(s, 8, (u >>  8) & 0xff);
+       bsW(s, 8,  u        & 0xff);
+}
+
+
+/*---------------------------------------------------*/
+/*--- The back end proper                         ---*/
+/*---------------------------------------------------*/
+
+/*---------------------------------------------------*/
+static
+void makeMaps_e(EState* s)
+{
+       int i;
+       s->nInUse = 0;
+       for (i = 0; i < 256; i++) {
+               if (s->inUse[i]) {
+                       s->unseqToSeq[i] = s->nInUse;
+                       s->nInUse++;
+               }
+       }
+}
+
+
+/*---------------------------------------------------*/
+static NOINLINE
+void generateMTFValues(EState* s)
+{
+       uint8_t yy[256];
+       int32_t i, j;
+       int32_t zPend;
+       int32_t wr;
+       int32_t EOB;
+
+       /*
+        * After sorting (eg, here),
+        * s->arr1[0 .. s->nblock-1] holds sorted order,
+        * and
+        * ((uint8_t*)s->arr2)[0 .. s->nblock-1]
+        * holds the original block data.
+        *
+        * The first thing to do is generate the MTF values,
+        * and put them in
+        *      ((uint16_t*)s->arr1)[0 .. s->nblock-1].
+        * Because there are strictly fewer or equal MTF values
+        * than block values, ptr values in this area are overwritten
+        * with MTF values only when they are no longer needed.
+        *
+        * The final compressed bitstream is generated into the
+        * area starting at
+        *      &((uint8_t*)s->arr2)[s->nblock]
+        *
+        * These storage aliases are set up in bzCompressInit(),
+        * except for the last one, which is arranged in
+        * compressBlock().
+        */
+       uint32_t* ptr   = s->ptr;
+       uint8_t*  block = s->block;
+       uint16_t* mtfv  = s->mtfv;
+
+       makeMaps_e(s);
+       EOB = s->nInUse+1;
+
+       for (i = 0; i <= EOB; i++)
+               s->mtfFreq[i] = 0;
+
+       wr = 0;
+       zPend = 0;
+       for (i = 0; i < s->nInUse; i++)
+               yy[i] = (uint8_t) i;
+
+       for (i = 0; i < s->nblock; i++) {
+               uint8_t ll_i;
+               AssertD(wr <= i, "generateMTFValues(1)");
+               j = ptr[i] - 1;
+               if (j < 0)
+                       j += s->nblock;
+               ll_i = s->unseqToSeq[block[j]];
+               AssertD(ll_i < s->nInUse, "generateMTFValues(2a)");
+
+               if (yy[0] == ll_i) {
+                       zPend++;
+               } else {
+                       if (zPend > 0) {
+                               zPend--;
+                               while (1) {
+                                       if (zPend & 1) {
+                                               mtfv[wr] = BZ_RUNB; wr++;
+                                               s->mtfFreq[BZ_RUNB]++;
+                                       } else {
+                                               mtfv[wr] = BZ_RUNA; wr++;
+                                               s->mtfFreq[BZ_RUNA]++;
+                                       }
+                                       if (zPend < 2) break;
+                                       zPend = (uint32_t)(zPend - 2) / 2;
+                                       /* bbox: unsigned div is easier */
+                               };
+                               zPend = 0;
+                       }
+                       {
+                               register uint8_t  rtmp;
+                               register uint8_t* ryy_j;
+                               register uint8_t  rll_i;
+                               rtmp  = yy[1];
+                               yy[1] = yy[0];
+                               ryy_j = &(yy[1]);
+                               rll_i = ll_i;
+                               while (rll_i != rtmp) {
+                                       register uint8_t rtmp2;
+                                       ryy_j++;
+                                       rtmp2  = rtmp;
+                                       rtmp   = *ryy_j;
+                                       *ryy_j = rtmp2;
+                               };
+                               yy[0] = rtmp;
+                               j = ryy_j - &(yy[0]);
+                               mtfv[wr] = j+1;
+                               wr++;
+                               s->mtfFreq[j+1]++;
+                       }
+
+               }
+       }
+
+       if (zPend > 0) {
+               zPend--;
+               while (1) {
+                       if (zPend & 1) {
+                               mtfv[wr] = BZ_RUNB;
+                               wr++;
+                               s->mtfFreq[BZ_RUNB]++;
+                       } else {
+                               mtfv[wr] = BZ_RUNA;
+                               wr++;
+                               s->mtfFreq[BZ_RUNA]++;
+                       }
+                       if (zPend < 2)
+                               break;
+                       zPend = (uint32_t)(zPend - 2) / 2;
+                       /* bbox: unsigned div is easier */
+               };
+               zPend = 0;
+       }
+
+       mtfv[wr] = EOB;
+       wr++;
+       s->mtfFreq[EOB]++;
+
+       s->nMTF = wr;
+}
+
+
+/*---------------------------------------------------*/
+#define BZ_LESSER_ICOST  0
+#define BZ_GREATER_ICOST 15
+
+static NOINLINE
+void sendMTFValues(EState* s)
+{
+       int32_t v, t, i, j, gs, ge, totc, bt, bc, iter;
+       int32_t nSelectors, alphaSize, minLen, maxLen, selCtr;
+       int32_t nGroups, nBytes;
+
+       /*
+        * uint8_t len[BZ_N_GROUPS][BZ_MAX_ALPHA_SIZE];
+        * is a global since the decoder also needs it.
+        *
+        * int32_t  code[BZ_N_GROUPS][BZ_MAX_ALPHA_SIZE];
+        * int32_t  rfreq[BZ_N_GROUPS][BZ_MAX_ALPHA_SIZE];
+        * are also globals only used in this proc.
+        * Made global to keep stack frame size small.
+        */
+#define code     sendMTFValues__code
+#define rfreq    sendMTFValues__rfreq
+#define len_pack sendMTFValues__len_pack
+
+       uint16_t cost[BZ_N_GROUPS];
+       int32_t  fave[BZ_N_GROUPS];
+
+       uint16_t* mtfv = s->mtfv;
+
+       alphaSize = s->nInUse + 2;
+       for (t = 0; t < BZ_N_GROUPS; t++)
+               for (v = 0; v < alphaSize; v++)
+                       s->len[t][v] = BZ_GREATER_ICOST;
+
+       /*--- Decide how many coding tables to use ---*/
+       AssertH(s->nMTF > 0, 3001);
+       if (s->nMTF < 200)  nGroups = 2; else
+       if (s->nMTF < 600)  nGroups = 3; else
+       if (s->nMTF < 1200) nGroups = 4; else
+       if (s->nMTF < 2400) nGroups = 5; else
+       nGroups = 6;
+
+       /*--- Generate an initial set of coding tables ---*/
+       {
+               int32_t nPart, remF, tFreq, aFreq;
+
+               nPart = nGroups;
+               remF  = s->nMTF;
+               gs = 0;
+               while (nPart > 0) {
+                       tFreq = remF / nPart;
+                       ge = gs - 1;
+                       aFreq = 0;
+                       while (aFreq < tFreq && ge < alphaSize-1) {
+                               ge++;
+                               aFreq += s->mtfFreq[ge];
+                       }
+
+                       if (ge > gs
+                        && nPart != nGroups && nPart != 1
+                        && ((nGroups - nPart) % 2 == 1) /* bbox: can this be replaced by x & 1? */
+                       ) {
+                               aFreq -= s->mtfFreq[ge];
+                               ge--;
+                       }
+
+                       for (v = 0; v < alphaSize; v++)
+                               if (v >= gs && v <= ge)
+                                       s->len[nPart-1][v] = BZ_LESSER_ICOST;
+                               else
+                                       s->len[nPart-1][v] = BZ_GREATER_ICOST;
+
+                       nPart--;
+                       gs = ge + 1;
+                       remF -= aFreq;
+               }
+       }
+
+       /*
+        * Iterate up to BZ_N_ITERS times to improve the tables.
+        */
+       for (iter = 0; iter < BZ_N_ITERS; iter++) {
+               for (t = 0; t < nGroups; t++)
+                       fave[t] = 0;
+
+               for (t = 0; t < nGroups; t++)
+                       for (v = 0; v < alphaSize; v++)
+                               s->rfreq[t][v] = 0;
+
+#if CONFIG_BZIP2_FEATURE_SPEED >= 5
+               /*
+                * Set up an auxiliary length table which is used to fast-track
+                * the common case (nGroups == 6).
+                */
+               if (nGroups == 6) {
+                       for (v = 0; v < alphaSize; v++) {
+                               s->len_pack[v][0] = (s->len[1][v] << 16) | s->len[0][v];
+                               s->len_pack[v][1] = (s->len[3][v] << 16) | s->len[2][v];
+                               s->len_pack[v][2] = (s->len[5][v] << 16) | s->len[4][v];
+                       }
+               }
+#endif
+               nSelectors = 0;
+               totc = 0;
+               gs = 0;
+               while (1) {
+                       /*--- Set group start & end marks. --*/
+                       if (gs >= s->nMTF)
+                               break;
+                       ge = gs + BZ_G_SIZE - 1;
+                       if (ge >= s->nMTF)
+                               ge = s->nMTF-1;
+
+                       /*
+                        * Calculate the cost of this group as coded
+                        * by each of the coding tables.
+                        */
+                       for (t = 0; t < nGroups; t++)
+                               cost[t] = 0;
+#if CONFIG_BZIP2_FEATURE_SPEED >= 5
+                       if (nGroups == 6 && 50 == ge-gs+1) {
+                               /*--- fast track the common case ---*/
+                               register uint32_t cost01, cost23, cost45;
+                               register uint16_t icv;
+                               cost01 = cost23 = cost45 = 0;
+#define BZ_ITER(nn) \
+       icv = mtfv[gs+(nn)]; \
+       cost01 += s->len_pack[icv][0]; \
+       cost23 += s->len_pack[icv][1]; \
+       cost45 += s->len_pack[icv][2];
+                               BZ_ITER(0);  BZ_ITER(1);  BZ_ITER(2);  BZ_ITER(3);  BZ_ITER(4);
+                               BZ_ITER(5);  BZ_ITER(6);  BZ_ITER(7);  BZ_ITER(8);  BZ_ITER(9);
+                               BZ_ITER(10); BZ_ITER(11); BZ_ITER(12); BZ_ITER(13); BZ_ITER(14);
+                               BZ_ITER(15); BZ_ITER(16); BZ_ITER(17); BZ_ITER(18); BZ_ITER(19);
+                               BZ_ITER(20); BZ_ITER(21); BZ_ITER(22); BZ_ITER(23); BZ_ITER(24);
+                               BZ_ITER(25); BZ_ITER(26); BZ_ITER(27); BZ_ITER(28); BZ_ITER(29);
+                               BZ_ITER(30); BZ_ITER(31); BZ_ITER(32); BZ_ITER(33); BZ_ITER(34);
+                               BZ_ITER(35); BZ_ITER(36); BZ_ITER(37); BZ_ITER(38); BZ_ITER(39);
+                               BZ_ITER(40); BZ_ITER(41); BZ_ITER(42); BZ_ITER(43); BZ_ITER(44);
+                               BZ_ITER(45); BZ_ITER(46); BZ_ITER(47); BZ_ITER(48); BZ_ITER(49);
+#undef BZ_ITER
+                               cost[0] = cost01 & 0xffff; cost[1] = cost01 >> 16;
+                               cost[2] = cost23 & 0xffff; cost[3] = cost23 >> 16;
+                               cost[4] = cost45 & 0xffff; cost[5] = cost45 >> 16;
+
+                       } else
+#endif
+                       {
+                               /*--- slow version which correctly handles all situations ---*/
+                               for (i = gs; i <= ge; i++) {
+                                       uint16_t icv = mtfv[i];
+                                       for (t = 0; t < nGroups; t++)
+                                               cost[t] += s->len[t][icv];
+                               }
+                       }
+                       /*
+                        * Find the coding table which is best for this group,
+                        * and record its identity in the selector table.
+                        */
+                       /*bc = 999999999;*/
+                       /*bt = -1;*/
+                       bc = cost[0];
+                       bt = 0;
+                       for (t = 1 /*0*/; t < nGroups; t++) {
+                               if (cost[t] < bc) {
+                                       bc = cost[t];
+                                       bt = t;
+                               }
+                       }
+                       totc += bc;
+                       fave[bt]++;
+                       s->selector[nSelectors] = bt;
+                       nSelectors++;
+
+                       /*
+                        * Increment the symbol frequencies for the selected table.
+                        */
+/* 1% faster compress. +800 bytes */
+#if CONFIG_BZIP2_FEATURE_SPEED >= 4
+                       if (nGroups == 6 && 50 == ge-gs+1) {
+                               /*--- fast track the common case ---*/
+#define BZ_ITUR(nn) s->rfreq[bt][mtfv[gs + (nn)]]++
+                               BZ_ITUR(0);  BZ_ITUR(1);  BZ_ITUR(2);  BZ_ITUR(3);  BZ_ITUR(4);
+                               BZ_ITUR(5);  BZ_ITUR(6);  BZ_ITUR(7);  BZ_ITUR(8);  BZ_ITUR(9);
+                               BZ_ITUR(10); BZ_ITUR(11); BZ_ITUR(12); BZ_ITUR(13); BZ_ITUR(14);
+                               BZ_ITUR(15); BZ_ITUR(16); BZ_ITUR(17); BZ_ITUR(18); BZ_ITUR(19);
+                               BZ_ITUR(20); BZ_ITUR(21); BZ_ITUR(22); BZ_ITUR(23); BZ_ITUR(24);
+                               BZ_ITUR(25); BZ_ITUR(26); BZ_ITUR(27); BZ_ITUR(28); BZ_ITUR(29);
+                               BZ_ITUR(30); BZ_ITUR(31); BZ_ITUR(32); BZ_ITUR(33); BZ_ITUR(34);
+                               BZ_ITUR(35); BZ_ITUR(36); BZ_ITUR(37); BZ_ITUR(38); BZ_ITUR(39);
+                               BZ_ITUR(40); BZ_ITUR(41); BZ_ITUR(42); BZ_ITUR(43); BZ_ITUR(44);
+                               BZ_ITUR(45); BZ_ITUR(46); BZ_ITUR(47); BZ_ITUR(48); BZ_ITUR(49);
+#undef BZ_ITUR
+                               gs = ge + 1;
+                       } else
+#endif
+                       {
+                               /*--- slow version which correctly handles all situations ---*/
+                               while (gs <= ge) {
+                                       s->rfreq[bt][mtfv[gs]]++;
+                                       gs++;
+                               }
+                               /* already is: gs = ge + 1; */
+                       }
+               }
+
+               /*
+                * Recompute the tables based on the accumulated frequencies.
+                */
+               /* maxLen was changed from 20 to 17 in bzip2-1.0.3.  See
+                * comment in huffman.c for details. */
+               for (t = 0; t < nGroups; t++)
+                       BZ2_hbMakeCodeLengths(s, &(s->len[t][0]), &(s->rfreq[t][0]), alphaSize, 17 /*20*/);
+       }
+
+       AssertH(nGroups < 8, 3002);
+       AssertH(nSelectors < 32768 && nSelectors <= (2 + (900000 / BZ_G_SIZE)), 3003);
+
+       /*--- Compute MTF values for the selectors. ---*/
+       {
+               uint8_t pos[BZ_N_GROUPS], ll_i, tmp2, tmp;
+
+               for (i = 0; i < nGroups; i++)
+                       pos[i] = i;
+               for (i = 0; i < nSelectors; i++) {
+                       ll_i = s->selector[i];
+                       j = 0;
+                       tmp = pos[j];
+                       while (ll_i != tmp) {
+                               j++;
+                               tmp2 = tmp;
+                               tmp = pos[j];
+                               pos[j] = tmp2;
+                       };
+                       pos[0] = tmp;
+                       s->selectorMtf[i] = j;
+               }
+       };
+
+       /*--- Assign actual codes for the tables. --*/
+       for (t = 0; t < nGroups; t++) {
+               minLen = 32;
+               maxLen = 0;
+               for (i = 0; i < alphaSize; i++) {
+                       if (s->len[t][i] > maxLen) maxLen = s->len[t][i];
+                       if (s->len[t][i] < minLen) minLen = s->len[t][i];
+               }
+               AssertH(!(maxLen > 17 /*20*/), 3004);
+               AssertH(!(minLen < 1), 3005);
+               BZ2_hbAssignCodes(&(s->code[t][0]), &(s->len[t][0]), minLen, maxLen, alphaSize);
+       }
+
+       /*--- Transmit the mapping table. ---*/
+       {
+               /* bbox: optimized a bit more than in bzip2 */
+               int inUse16 = 0;
+               for (i = 0; i < 16; i++) {
+                       if (sizeof(long) <= 4) {
+                               inUse16 = inUse16*2 +
+                                       ((*(uint32_t*)&(s->inUse[i * 16 + 0])
+                                       | *(uint32_t*)&(s->inUse[i * 16 + 4])
+                                       | *(uint32_t*)&(s->inUse[i * 16 + 8])
+                                       | *(uint32_t*)&(s->inUse[i * 16 + 12])) != 0);
+                       } else { /* Our CPU can do better */
+                               inUse16 = inUse16*2 +
+                                       ((*(uint64_t*)&(s->inUse[i * 16 + 0])
+                                       | *(uint64_t*)&(s->inUse[i * 16 + 8])) != 0);
+                       }
+               }
+
+               nBytes = s->numZ;
+               bsW(s, 16, inUse16);
+
+               inUse16 <<= (sizeof(int)*8 - 16); /* move 15th bit into sign bit */
+               for (i = 0; i < 16; i++) {
+                       if (inUse16 < 0) {
+                               unsigned v16 = 0;
+                               for (j = 0; j < 16; j++)
+                                       v16 = v16*2 + s->inUse[i * 16 + j];
+                               bsW(s, 16, v16);
+                       }
+                       inUse16 <<= 1;
+               }
+       }
+
+       /*--- Now the selectors. ---*/
+       nBytes = s->numZ;
+       bsW(s, 3, nGroups);
+       bsW(s, 15, nSelectors);
+       for (i = 0; i < nSelectors; i++) {
+               for (j = 0; j < s->selectorMtf[i]; j++)
+                       bsW(s, 1, 1);
+               bsW(s, 1, 0);
+       }
+
+       /*--- Now the coding tables. ---*/
+       nBytes = s->numZ;
+
+       for (t = 0; t < nGroups; t++) {
+               int32_t curr = s->len[t][0];
+               bsW(s, 5, curr);
+               for (i = 0; i < alphaSize; i++) {
+                       while (curr < s->len[t][i]) { bsW(s, 2, 2); curr++; /* 10 */ };
+                       while (curr > s->len[t][i]) { bsW(s, 2, 3); curr--; /* 11 */ };
+                       bsW(s, 1, 0);
+               }
+       }
+
+       /*--- And finally, the block data proper ---*/
+       nBytes = s->numZ;
+       selCtr = 0;
+       gs = 0;
+       while (1) {
+               if (gs >= s->nMTF)
+                       break;
+               ge = gs + BZ_G_SIZE - 1;
+               if (ge >= s->nMTF)
+                       ge = s->nMTF-1;
+               AssertH(s->selector[selCtr] < nGroups, 3006);
+
+/* Costs 1300 bytes and is _slower_ (on Intel Core 2) */
+#if 0
+               if (nGroups == 6 && 50 == ge-gs+1) {
+                       /*--- fast track the common case ---*/
+                       uint16_t mtfv_i;
+                       uint8_t* s_len_sel_selCtr  = &(s->len[s->selector[selCtr]][0]);
+                       int32_t* s_code_sel_selCtr = &(s->code[s->selector[selCtr]][0]);
+#define BZ_ITAH(nn) \
+       mtfv_i = mtfv[gs+(nn)]; \
+       bsW(s, s_len_sel_selCtr[mtfv_i], s_code_sel_selCtr[mtfv_i])
+                       BZ_ITAH(0);  BZ_ITAH(1);  BZ_ITAH(2);  BZ_ITAH(3);  BZ_ITAH(4);
+                       BZ_ITAH(5);  BZ_ITAH(6);  BZ_ITAH(7);  BZ_ITAH(8);  BZ_ITAH(9);
+                       BZ_ITAH(10); BZ_ITAH(11); BZ_ITAH(12); BZ_ITAH(13); BZ_ITAH(14);
+                       BZ_ITAH(15); BZ_ITAH(16); BZ_ITAH(17); BZ_ITAH(18); BZ_ITAH(19);
+                       BZ_ITAH(20); BZ_ITAH(21); BZ_ITAH(22); BZ_ITAH(23); BZ_ITAH(24);
+                       BZ_ITAH(25); BZ_ITAH(26); BZ_ITAH(27); BZ_ITAH(28); BZ_ITAH(29);
+                       BZ_ITAH(30); BZ_ITAH(31); BZ_ITAH(32); BZ_ITAH(33); BZ_ITAH(34);
+                       BZ_ITAH(35); BZ_ITAH(36); BZ_ITAH(37); BZ_ITAH(38); BZ_ITAH(39);
+                       BZ_ITAH(40); BZ_ITAH(41); BZ_ITAH(42); BZ_ITAH(43); BZ_ITAH(44);
+                       BZ_ITAH(45); BZ_ITAH(46); BZ_ITAH(47); BZ_ITAH(48); BZ_ITAH(49);
+#undef BZ_ITAH
+                       gs = ge+1;
+               } else
+#endif
+               {
+                       /*--- slow version which correctly handles all situations ---*/
+                       /* code is bit bigger, but moves multiply out of the loop */
+                       uint8_t* s_len_sel_selCtr  = &(s->len [s->selector[selCtr]][0]);
+                       int32_t* s_code_sel_selCtr = &(s->code[s->selector[selCtr]][0]);
+                       while (gs <= ge) {
+                               bsW(s,
+                                       s_len_sel_selCtr[mtfv[gs]],
+                                       s_code_sel_selCtr[mtfv[gs]]
+                               );
+                               gs++;
+                       }
+                       /* already is: gs = ge+1; */
+               }
+               selCtr++;
+       }
+       AssertH(selCtr == nSelectors, 3007);
+#undef code
+#undef rfreq
+#undef len_pack
+}
+
+
+/*---------------------------------------------------*/
+static
+void BZ2_compressBlock(EState* s, int is_last_block)
+{
+       if (s->nblock > 0) {
+               BZ_FINALISE_CRC(s->blockCRC);
+               s->combinedCRC = (s->combinedCRC << 1) | (s->combinedCRC >> 31);
+               s->combinedCRC ^= s->blockCRC;
+               if (s->blockNo > 1)
+                       s->numZ = 0;
+
+               BZ2_blockSort(s);
+       }
+
+       s->zbits = &((uint8_t*)s->arr2)[s->nblock];
+
+       /*-- If this is the first block, create the stream header. --*/
+       if (s->blockNo == 1) {
+               BZ2_bsInitWrite(s);
+               /*bsPutU8(s, BZ_HDR_B);*/
+               /*bsPutU8(s, BZ_HDR_Z);*/
+               /*bsPutU8(s, BZ_HDR_h);*/
+               /*bsPutU8(s, BZ_HDR_0 + s->blockSize100k);*/
+               bsPutU32(s, BZ_HDR_BZh0 + s->blockSize100k);
+       }
+
+       if (s->nblock > 0) {
+               /*bsPutU8(s, 0x31);*/
+               /*bsPutU8(s, 0x41);*/
+               /*bsPutU8(s, 0x59);*/
+               /*bsPutU8(s, 0x26);*/
+               bsPutU32(s, 0x31415926);
+               /*bsPutU8(s, 0x53);*/
+               /*bsPutU8(s, 0x59);*/
+               bsPutU16(s, 0x5359);
+
+               /*-- Now the block's CRC, so it is in a known place. --*/
+               bsPutU32(s, s->blockCRC);
+
+               /*
+                * Now a single bit indicating (non-)randomisation.
+                * As of version 0.9.5, we use a better sorting algorithm
+                * which makes randomisation unnecessary.  So always set
+                * the randomised bit to 'no'.  Of course, the decoder
+                * still needs to be able to handle randomised blocks
+                * so as to maintain backwards compatibility with
+                * older versions of bzip2.
+                */
+               bsW(s, 1, 0);
+
+               bsW(s, 24, s->origPtr);
+               generateMTFValues(s);
+               sendMTFValues(s);
+       }
+
+       /*-- If this is the last block, add the stream trailer. --*/
+       if (is_last_block) {
+               /*bsPutU8(s, 0x17);*/
+               /*bsPutU8(s, 0x72);*/
+               /*bsPutU8(s, 0x45);*/
+               /*bsPutU8(s, 0x38);*/
+               bsPutU32(s, 0x17724538);
+               /*bsPutU8(s, 0x50);*/
+               /*bsPutU8(s, 0x90);*/
+               bsPutU16(s, 0x5090);
+               bsPutU32(s, s->combinedCRC);
+               bsFinishWrite(s);
+       }
+}
+
+
+/*-------------------------------------------------------------*/
+/*--- end                                        compress.c ---*/
+/*-------------------------------------------------------------*/
diff --git a/archival/bz/huffman.c b/archival/bz/huffman.c
new file mode 100644 (file)
index 0000000..676b1af
--- /dev/null
@@ -0,0 +1,229 @@
+/*
+ * bzip2 is written by Julian Seward <jseward@bzip.org>.
+ * Adapted for busybox by Denys Vlasenko <vda.linux@googlemail.com>.
+ * See README and LICENSE files in this directory for more information.
+ */
+
+/*-------------------------------------------------------------*/
+/*--- Huffman coding low-level stuff                        ---*/
+/*---                                             huffman.c ---*/
+/*-------------------------------------------------------------*/
+
+/* ------------------------------------------------------------------
+This file is part of bzip2/libbzip2, a program and library for
+lossless, block-sorting data compression.
+
+bzip2/libbzip2 version 1.0.4 of 20 December 2006
+Copyright (C) 1996-2006 Julian Seward <jseward@bzip.org>
+
+Please read the WARNING, DISCLAIMER and PATENTS sections in the
+README file.
+
+This program is released under the terms of the license contained
+in the file LICENSE.
+------------------------------------------------------------------ */
+
+/* #include "bzlib_private.h" */
+
+/*---------------------------------------------------*/
+#define WEIGHTOF(zz0)  ((zz0) & 0xffffff00)
+#define DEPTHOF(zz1)   ((zz1) & 0x000000ff)
+#define MYMAX(zz2,zz3) ((zz2) > (zz3) ? (zz2) : (zz3))
+
+#define ADDWEIGHTS(zw1,zw2) \
+       (WEIGHTOF(zw1)+WEIGHTOF(zw2)) | \
+       (1 + MYMAX(DEPTHOF(zw1),DEPTHOF(zw2)))
+
+#define UPHEAP(z) \
+{ \
+       int32_t zz, tmp; \
+       zz = z; \
+       tmp = heap[zz]; \
+       while (weight[tmp] < weight[heap[zz >> 1]]) { \
+               heap[zz] = heap[zz >> 1]; \
+               zz >>= 1; \
+       } \
+       heap[zz] = tmp; \
+}
+
+
+/* 90 bytes, 0.3% of overall compress speed */
+#if CONFIG_BZIP2_FEATURE_SPEED >= 1
+
+/* macro works better than inline (gcc 4.2.1) */
+#define DOWNHEAP1(heap, weight, Heap) \
+{ \
+       int32_t zz, yy, tmp; \
+       zz = 1; \
+       tmp = heap[zz]; \
+       while (1) { \
+               yy = zz << 1; \
+               if (yy > nHeap) \
+                       break; \
+               if (yy < nHeap \
+                && weight[heap[yy+1]] < weight[heap[yy]]) \
+                       yy++; \
+               if (weight[tmp] < weight[heap[yy]]) \
+                       break; \
+               heap[zz] = heap[yy]; \
+               zz = yy; \
+       } \
+       heap[zz] = tmp; \
+}
+
+#else
+
+static
+void DOWNHEAP1(int32_t *heap, int32_t *weight, int32_t nHeap)
+{
+       int32_t zz, yy, tmp;
+       zz = 1;
+       tmp = heap[zz];
+       while (1) {
+               yy = zz << 1;
+               if (yy > nHeap)
+                       break;
+               if (yy < nHeap
+                && weight[heap[yy + 1]] < weight[heap[yy]])
+                       yy++;
+               if (weight[tmp] < weight[heap[yy]])
+                       break;
+               heap[zz] = heap[yy];
+               zz = yy;
+       }
+       heap[zz] = tmp;
+}
+
+#endif
+
+/*---------------------------------------------------*/
+static
+void BZ2_hbMakeCodeLengths(EState *s,
+               uint8_t *len,
+               int32_t *freq,
+               int32_t alphaSize,
+               int32_t maxLen)
+{
+       /*
+        * Nodes and heap entries run from 1.  Entry 0
+        * for both the heap and nodes is a sentinel.
+        */
+       int32_t nNodes, nHeap, n1, n2, i, j, k;
+       Bool  tooLong;
+
+       /* bbox: moved to EState to save stack
+       int32_t heap  [BZ_MAX_ALPHA_SIZE + 2];
+       int32_t weight[BZ_MAX_ALPHA_SIZE * 2];
+       int32_t parent[BZ_MAX_ALPHA_SIZE * 2];
+       */
+#define heap   (s->BZ2_hbMakeCodeLengths__heap)
+#define weight (s->BZ2_hbMakeCodeLengths__weight)
+#define parent (s->BZ2_hbMakeCodeLengths__parent)
+
+       for (i = 0; i < alphaSize; i++)
+               weight[i+1] = (freq[i] == 0 ? 1 : freq[i]) << 8;
+
+       while (1) {
+               nNodes = alphaSize;
+               nHeap = 0;
+
+               heap[0] = 0;
+               weight[0] = 0;
+               parent[0] = -2;
+
+               for (i = 1; i <= alphaSize; i++) {
+                       parent[i] = -1;
+                       nHeap++;
+                       heap[nHeap] = i;
+                       UPHEAP(nHeap);
+               }
+
+               AssertH(nHeap < (BZ_MAX_ALPHA_SIZE+2), 2001);
+
+               while (nHeap > 1) {
+                       n1 = heap[1]; heap[1] = heap[nHeap]; nHeap--; DOWNHEAP1(heap, weight, nHeap);
+                       n2 = heap[1]; heap[1] = heap[nHeap]; nHeap--; DOWNHEAP1(heap, weight, nHeap);
+                       nNodes++;
+                       parent[n1] = parent[n2] = nNodes;
+                       weight[nNodes] = ADDWEIGHTS(weight[n1], weight[n2]);
+                       parent[nNodes] = -1;
+                       nHeap++;
+                       heap[nHeap] = nNodes;
+                       UPHEAP(nHeap);
+               }
+
+               AssertH(nNodes < (BZ_MAX_ALPHA_SIZE * 2), 2002);
+
+               tooLong = False;
+               for (i = 1; i <= alphaSize; i++) {
+                       j = 0;
+                       k = i;
+                       while (parent[k] >= 0) {
+                               k = parent[k];
+                               j++;
+                       }
+                       len[i-1] = j;
+                       if (j > maxLen)
+                               tooLong = True;
+               }
+
+               if (!tooLong)
+                       break;
+
+               /* 17 Oct 04: keep-going condition for the following loop used
+               to be 'i < alphaSize', which missed the last element,
+               theoretically leading to the possibility of the compressor
+               looping.  However, this count-scaling step is only needed if
+               one of the generated Huffman code words is longer than
+               maxLen, which up to and including version 1.0.2 was 20 bits,
+               which is extremely unlikely.  In version 1.0.3 maxLen was
+               changed to 17 bits, which has minimal effect on compression
+               ratio, but does mean this scaling step is used from time to
+               time, enough to verify that it works.
+
+               This means that bzip2-1.0.3 and later will only produce
+               Huffman codes with a maximum length of 17 bits.  However, in
+               order to preserve backwards compatibility with bitstreams
+               produced by versions pre-1.0.3, the decompressor must still
+               handle lengths of up to 20. */
+
+               for (i = 1; i <= alphaSize; i++) {
+                       j = weight[i] >> 8;
+                       /* bbox: yes, it is a signed division.
+                        * don't replace with shift! */
+                       j = 1 + (j / 2);
+                       weight[i] = j << 8;
+               }
+       }
+#undef heap
+#undef weight
+#undef parent
+}
+
+
+/*---------------------------------------------------*/
+static
+void BZ2_hbAssignCodes(int32_t *code,
+               uint8_t *length,
+               int32_t minLen,
+               int32_t maxLen,
+               int32_t alphaSize)
+{
+       int32_t n, vec, i;
+
+       vec = 0;
+       for (n = minLen; n <= maxLen; n++) {
+               for (i = 0; i < alphaSize; i++) {
+                       if (length[i] == n) {
+                               code[i] = vec;
+                               vec++;
+                       };
+               }
+               vec <<= 1;
+       }
+}
+
+
+/*-------------------------------------------------------------*/
+/*--- end                                         huffman.c ---*/
+/*-------------------------------------------------------------*/
diff --git a/archival/bzip2.c b/archival/bzip2.c
new file mode 100644 (file)
index 0000000..8eb5ca9
--- /dev/null
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2007 Denys Vlasenko <vda.linux@googlemail.com>
+ *
+ * This file uses bzip2 library code which is written
+ * by Julian Seward <jseward@bzip.org>.
+ * See README and LICENSE files in bz/ directory for more information
+ * about bzip2 library code.
+ */
+
+#include "libbb.h"
+#include "unarchive.h"
+
+#define CONFIG_BZIP2_FEATURE_SPEED 1
+
+/* Speed test:
+ * Compiled with gcc 4.2.1, run on Athlon 64 1800 MHz (512K L2 cache).
+ * Stock bzip2 is 26.4% slower than bbox bzip2 at SPEED 1
+ * (time to compress gcc-4.2.1.tar is 126.4% compared to bbox).
+ * At SPEED 5 difference is 32.7%.
+ *
+ * Test run of all CONFIG_BZIP2_FEATURE_SPEED values on a 11Mb text file:
+ *     Size   Time (3 runs)
+ * 0:  10828  4.145 4.146 4.148
+ * 1:  11097  3.845 3.860 3.861
+ * 2:  11392  3.763 3.767 3.768
+ * 3:  11892  3.722 3.724 3.727
+ * 4:  12740  3.637 3.640 3.644
+ * 5:  17273  3.497 3.509 3.509
+ */
+
+
+#define BZ_DEBUG 0
+/* Takes ~300 bytes, detects corruption caused by bad RAM etc */
+#define BZ_LIGHT_DEBUG 0
+
+#include "bz/bzlib.h"
+
+#include "bz/bzlib_private.h"
+
+#include "bz/blocksort.c"
+#include "bz/bzlib.c"
+#include "bz/compress.c"
+#include "bz/huffman.c"
+
+/* No point in being shy and having very small buffer here.
+ * bzip2 internal buffers are much bigger anyway, hundreds of kbytes.
+ * If iobuf is several pages long, malloc() may use mmap,
+ * making iobuf is page aligned and thus (maybe) have one memcpy less
+ * if kernel is clever enough.
+ */
+enum {
+       IOBUF_SIZE = 8 * 1024
+};
+
+static uint8_t level;
+
+/* NB: compressStream() has to return -1 on errors, not die.
+ * bbunpack() will correctly clean up in this case
+ * (delete incomplete .bz2 file)
+ */
+
+/* Returns:
+ * -1 on errors
+ * total written bytes so far otherwise
+ */
+static
+USE_DESKTOP(long long) int bz_write(bz_stream *strm, void* rbuf, ssize_t rlen, void *wbuf)
+{
+       int n, n2, ret;
+
+       strm->avail_in = rlen;
+       strm->next_in = rbuf;
+       while (1) {
+               strm->avail_out = IOBUF_SIZE;
+               strm->next_out = wbuf;
+
+               ret = BZ2_bzCompress(strm, rlen ? BZ_RUN : BZ_FINISH);
+               if (ret != BZ_RUN_OK /* BZ_RUNning */
+                && ret != BZ_FINISH_OK /* BZ_FINISHing, but not done yet */
+                && ret != BZ_STREAM_END /* BZ_FINISHed */
+               ) {
+                       bb_error_msg_and_die("internal error %d", ret);
+               }
+
+               n = IOBUF_SIZE - strm->avail_out;
+               if (n) {
+                       n2 = full_write(STDOUT_FILENO, wbuf, n);
+                       if (n2 != n) {
+                               if (n2 >= 0)
+                                       errno = 0; /* prevent bogus error message */
+                               bb_perror_msg(n2 >= 0 ? "short write" : "write error");
+                               return -1;
+                       }
+               }
+
+               if (ret == BZ_STREAM_END)
+                       break;
+               if (rlen && strm->avail_in == 0)
+                       break;
+       }
+       return 0 USE_DESKTOP( + strm->total_out );
+}
+
+static
+USE_DESKTOP(long long) int compressStream(unpack_info_t *info UNUSED_PARAM)
+{
+       USE_DESKTOP(long long) int total;
+       ssize_t count;
+       bz_stream bzs; /* it's small */
+#define strm (&bzs)
+       char *iobuf;
+#define rbuf iobuf
+#define wbuf (iobuf + IOBUF_SIZE)
+
+       iobuf = xmalloc(2 * IOBUF_SIZE);
+       BZ2_bzCompressInit(strm, level);
+
+       while (1) {
+               count = full_read(STDIN_FILENO, rbuf, IOBUF_SIZE);
+               if (count < 0) {
+                       bb_perror_msg("read error");
+                       total = -1;
+                       break;
+               }
+               /* if count == 0, bz_write finalizes compression */
+               total = bz_write(strm, rbuf, count, wbuf);
+               if (count == 0 || total < 0)
+                       break;
+       }
+
+#if ENABLE_FEATURE_CLEAN_UP
+       BZ2_bzCompressEnd(strm);
+       free(iobuf);
+#endif
+       return total;
+}
+
+static
+char* make_new_name_bzip2(char *filename)
+{
+       return xasprintf("%s.bz2", filename);
+}
+
+int bzip2_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int bzip2_main(int argc UNUSED_PARAM, char **argv)
+{
+       unsigned opt;
+
+       /* standard bzip2 flags
+        * -d --decompress force decompression
+        * -z --compress force compression
+        * -k --keep     keep (don't delete) input files
+        * -f --force    overwrite existing output files
+        * -t --test     test compressed file integrity
+        * -c --stdout   output to standard out
+        * -q --quiet    suppress noncritical error messages
+        * -v --verbose  be verbose (a 2nd -v gives more)
+        * -s --small    use less memory (at most 2500k)
+        * -1 .. -9      set block size to 100k .. 900k
+        * --fast        alias for -1
+        * --best        alias for -9
+        */
+
+       opt_complementary = "s2"; /* -s means -2 (compatibility) */
+       /* Must match bbunzip's constants OPT_STDOUT, OPT_FORCE! */
+       opt = getopt32(argv, "cfv" USE_BUNZIP2("dt") "123456789qzs");
+#if ENABLE_BUNZIP2 /* bunzip2_main may not be visible... */
+       if (opt & 0x18) // -d and/or -t
+               return bunzip2_main(argc, argv);
+       opt >>= 5;
+#else
+       opt >>= 3;
+#endif
+       opt = (uint8_t)opt; /* isolate bits for -1..-8 */
+       opt |= 0x100; /* if nothing else, assume -9 */
+       level = 1;
+       while (!(opt & 1)) {
+               level++;
+               opt >>= 1;
+       }
+
+       argv += optind;
+       option_mask32 &= 0x7; /* ignore all except -cfv */
+       return bbunpack(argv, make_new_name_bzip2, compressStream);
+}
diff --git a/archival/cpio.c b/archival/cpio.c
new file mode 100644 (file)
index 0000000..11b22e4
--- /dev/null
@@ -0,0 +1,413 @@
+/* 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 "libbb.h"
+#include "unarchive.h"
+
+/* GNU cpio 2.9 --help (abridged):
+
+ Modes:
+  -t, --list                 List the archive
+  -i, --extract              Extract files from an archive
+  -o, --create               Create the archive
+  -p, --pass-through         Copy-pass mode [was ist das?!]
+
+ Options valid in any mode:
+      --block-size=SIZE      I/O block size = SIZE * 512 bytes
+  -B                         I/O block size = 5120 bytes
+  -c                         Use the old portable (ASCII) archive format
+  -C, --io-size=NUMBER       I/O block size in bytes
+  -f, --nonmatching          Only copy files that do not match given pattern
+  -F, --file=FILE            Use FILE instead of standard input or output
+  -H, --format=FORMAT        Use given archive FORMAT
+  -M, --message=STRING       Print STRING when the end of a volume of the
+                             backup media is reached
+  -n, --numeric-uid-gid      If -v, show numeric UID and GID
+      --quiet                Do not print the number of blocks copied
+      --rsh-command=COMMAND  Use remote COMMAND instead of rsh
+  -v, --verbose              Verbosely list the files processed
+  -V, --dot                  Print a "." for each file processed
+  -W, --warning=FLAG         Control warning display: 'none','truncate','all';
+                             multiple options accumulate
+
+ Options valid only in --extract mode:
+  -b, --swap                 Swap both halfwords of words and bytes of
+                             halfwords in the data (equivalent to -sS)
+  -r, --rename               Interactively rename files
+  -s, --swap-bytes           Swap the bytes of each halfword in the files
+  -S, --swap-halfwords       Swap the halfwords of each word (4 bytes)
+      --to-stdout            Extract files to standard output
+  -E, --pattern-file=FILE    Read additional patterns specifying filenames to
+                             extract or list from FILE
+      --only-verify-crc      Verify CRC's, don't actually extract the files
+
+ Options valid only in --create mode:
+  -A, --append               Append to an existing archive
+  -O FILE                    File to use instead of standard output
+
+ Options valid only in --pass-through mode:
+  -l, --link                 Link files instead of copying them, when possible
+
+ Options valid in --extract and --create modes:
+      --absolute-filenames   Do not strip file system prefix components from
+                             the file names
+      --no-absolute-filenames Create all files relative to the current dir
+
+ Options valid in --create and --pass-through modes:
+  -0, --null                 A list of filenames is terminated by a NUL
+  -a, --reset-access-time    Reset the access times of files after reading them
+  -I FILE                    File to use instead of standard input
+  -L, --dereference          Dereference symbolic links (copy the files
+                             that they point to instead of copying the links)
+  -R, --owner=[USER][:.][GROUP] Set owner of created files
+
+ Options valid in --extract and --pass-through modes:
+  -d, --make-directories     Create leading directories where needed
+  -m, --preserve-modification-time  Retain mtime when creating files
+      --no-preserve-owner    Do not change the ownership of the files
+      --sparse               Write files with blocks of zeros as sparse files
+  -u, --unconditional        Replace all files unconditionally
+ */
+enum {
+       CPIO_OPT_EXTRACT            = (1 << 0),
+       CPIO_OPT_TEST               = (1 << 1),
+       CPIO_OPT_NUL_TERMINATED     = (1 << 2),
+       CPIO_OPT_UNCONDITIONAL      = (1 << 3),
+       CPIO_OPT_VERBOSE            = (1 << 4),
+       CPIO_OPT_CREATE_LEADING_DIR = (1 << 5),
+       CPIO_OPT_PRESERVE_MTIME     = (1 << 6),
+       CPIO_OPT_DEREF              = (1 << 7),
+       CPIO_OPT_FILE               = (1 << 8),
+       CPIO_OPT_CREATE             = (1 << 9) * ENABLE_FEATURE_CPIO_O,
+       CPIO_OPT_FORMAT             = (1 << 10) * ENABLE_FEATURE_CPIO_O,
+       CPIO_OPT_PASSTHROUGH        = (1 << 11) * ENABLE_FEATURE_CPIO_P,
+};
+
+#define OPTION_STR "it0uvdmLF:"
+
+#if ENABLE_FEATURE_CPIO_O
+static off_t cpio_pad4(off_t size)
+{
+       int i;
+
+       i = (- size) & 3;
+       size += i;
+       while (--i >= 0)
+               bb_putchar('\0');
+       return size;
+}
+
+/* Return value will become exit code.
+ * It's ok to exit instead of return. */
+static int cpio_o(void)
+{
+       static const char trailer[] ALIGN1 = "TRAILER!!!";
+       struct name_s {
+               struct name_s *next;
+               char name[1];
+       };
+       struct inodes_s {
+               struct inodes_s *next;
+               struct name_s *names;
+               struct stat st;
+       };
+
+       struct inodes_s *links = NULL;
+       off_t bytes = 0; /* output bytes count */
+
+       while (1) {
+               const char *name;
+               char *line;
+               struct stat st;
+
+               line = (option_mask32 & CPIO_OPT_NUL_TERMINATED)
+                               ? bb_get_chunk_from_file(stdin, NULL)
+                               : xmalloc_fgetline(stdin);
+
+               if (line) {
+                       /* Strip leading "./[./]..." from the filename */
+                       name = line;
+                       while (name[0] == '.' && name[1] == '/') {
+                               while (*++name == '/')
+                                       continue;
+                       }
+                       if (!*name) { /* line is empty */
+                               free(line);
+                               continue;
+                       }
+                       if ((option_mask32 & CPIO_OPT_DEREF)
+                                       ? stat(name, &st)
+                                       : lstat(name, &st)
+                       ) {
+ abort_cpio_o:
+                               bb_simple_perror_msg_and_die(name);
+                       }
+
+                       if (!(S_ISLNK(st.st_mode) || S_ISREG(st.st_mode)))
+                               st.st_size = 0; /* paranoia */
+
+                       /* Store hardlinks for later processing, dont output them */
+                       if (!S_ISDIR(st.st_mode) && st.st_nlink > 1) {
+                               struct name_s *n;
+                               struct inodes_s *l;
+
+                               /* Do we have this hardlink remembered? */
+                               l = links;
+                               while (1) {
+                                       if (l == NULL) {
+                                               /* Not found: add new item to "links" list */
+                                               l = xzalloc(sizeof(*l));
+                                               l->st = st;
+                                               l->next = links;
+                                               links = l;
+                                               break;
+                                       }
+                                       if (l->st.st_ino == st.st_ino) {
+                                               /* found */
+                                               break;
+                                       }
+                                       l = l->next;
+                               }
+                               /* Add new name to "l->names" list */
+                               n = xmalloc(sizeof(*n) + strlen(name));
+                               strcpy(n->name, name);
+                               n->next = l->names;
+                               l->names = n;
+
+                               free(line);
+                               continue;
+                       }
+
+               } else { /* line == NULL: EOF */
+ next_link:
+                       if (links) {
+                               /* Output hardlink's data */
+                               st = links->st;
+                               name = links->names->name;
+                               links->names = links->names->next;
+                               /* GNU cpio is reported to emit file data
+                                * only for the last instance. Mimic that. */
+                               if (links->names == NULL)
+                                       links = links->next;
+                               else
+                                       st.st_size = 0;
+                               /* NB: we leak links->names and/or links,
+                                * this is intended (we exit soon anyway) */
+                       } else {
+                               /* If no (more) hardlinks to output,
+                                * output "trailer" entry */
+                               name = trailer;
+                               /* st.st_size == 0 is a must, but for uniformity
+                                * in the output, we zero out everything */
+                               memset(&st, 0, sizeof(st));
+                               /* st.st_nlink = 1; - GNU cpio does this */
+                       }
+               }
+
+               bytes += printf("070701"
+                               "%08X%08X%08X%08X%08X%08X%08X"
+                               "%08X%08X%08X%08X" /* GNU cpio uses uppercase hex */
+                               /* strlen+1: */ "%08X"
+                               /* chksum: */   "00000000" /* (only for "070702" files) */
+                               /* name,NUL: */ "%s%c",
+                               (unsigned)(uint32_t) st.st_ino,
+                               (unsigned)(uint32_t) st.st_mode,
+                               (unsigned)(uint32_t) st.st_uid,
+                               (unsigned)(uint32_t) st.st_gid,
+                               (unsigned)(uint32_t) st.st_nlink,
+                               (unsigned)(uint32_t) st.st_mtime,
+                               (unsigned)(uint32_t) st.st_size,
+                               (unsigned)(uint32_t) major(st.st_dev),
+                               (unsigned)(uint32_t) minor(st.st_dev),
+                               (unsigned)(uint32_t) major(st.st_rdev),
+                               (unsigned)(uint32_t) minor(st.st_rdev),
+                               (unsigned)(strlen(name) + 1),
+                               name, '\0');
+               bytes = cpio_pad4(bytes);
+
+               if (st.st_size) {
+                       if (S_ISLNK(st.st_mode)) {
+                               char *lpath = xmalloc_readlink_or_warn(name);
+                               if (!lpath)
+                                       goto abort_cpio_o;
+                               bytes += printf("%s", lpath);
+                               free(lpath);
+                       } else { /* S_ISREG */
+                               int fd = xopen(name, O_RDONLY);
+                               fflush(stdout);
+                               /* We must abort if file got shorter too! */
+                               bb_copyfd_exact_size(fd, STDOUT_FILENO, st.st_size);
+                               bytes += st.st_size;
+                               close(fd);
+                       }
+                       bytes = cpio_pad4(bytes);
+               }
+
+               if (!line) {
+                       if (name != trailer)
+                               goto next_link;
+                       /* TODO: GNU cpio pads trailer to 512 bytes, do we want that? */
+                       return EXIT_SUCCESS;
+               }
+
+               free(line);
+       } /* end of "while (1)" */
+}
+#endif
+
+int cpio_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int cpio_main(int argc UNUSED_PARAM, char **argv)
+{
+       archive_handle_t *archive_handle;
+       char *cpio_filename;
+       USE_FEATURE_CPIO_O(const char *cpio_fmt = "";)
+       unsigned opt;
+
+#if ENABLE_GETOPT_LONG
+       applet_long_options =
+               "extract\0"      No_argument       "i"
+               "list\0"         No_argument       "t"
+#if ENABLE_FEATURE_CPIO_O
+               "create\0"       No_argument       "o"
+               "format\0"       Required_argument "H"
+#if ENABLE_FEATURE_CPIO_P
+               "pass-through\0" No_argument       "p"
+#endif
+#endif
+               ;
+#endif
+
+       /* As of now we do not enforce this: */
+       /* -i,-t,-o,-p are mutually exclusive */
+       /* -u,-d,-m make sense only with -i or -p */
+       /* -L makes sense only with -o or -p */
+
+#if !ENABLE_FEATURE_CPIO_O
+       opt = getopt32(argv, OPTION_STR, &cpio_filename);
+#else
+       opt = getopt32(argv, OPTION_STR "oH:" USE_FEATURE_CPIO_P("p"), &cpio_filename, &cpio_fmt);
+       if (opt & CPIO_OPT_PASSTHROUGH) {
+               pid_t pid;
+               struct fd_pair pp;
+
+               if (argv[optind] == NULL)
+                       bb_show_usage();
+               if (opt & CPIO_OPT_CREATE_LEADING_DIR)
+                       mkdir(argv[optind], 0777);
+               /* Crude existence check:
+                * close(xopen(argv[optind], O_RDONLY | O_DIRECTORY));
+                * We can also xopen, fstat, IS_DIR, later fchdir.
+                * This would check for existence earlier and cleaner.
+                * As it stands now, if we fail xchdir later,
+                * child dies on EPIPE, unless it caught
+                * a diffrerent problem earlier.
+                * This is good enough for now.
+                */
+#if !BB_MMU
+               pp.rd = 3;
+               pp.wr = 4;
+               if (!re_execed) {
+                       close(3);
+                       close(4);
+                       xpiped_pair(pp);
+               }
+#else
+               xpiped_pair(pp);
+#endif
+               pid = fork_or_rexec(argv);
+               if (pid == 0) { /* child */
+                       close(pp.rd);
+                       xmove_fd(pp.wr, STDOUT_FILENO);
+                       goto dump;
+               }
+               /* parent */
+               xchdir(argv[optind++]);
+               close(pp.wr);
+               xmove_fd(pp.rd, STDIN_FILENO);
+               opt &= ~CPIO_OPT_PASSTHROUGH;
+               opt |= CPIO_OPT_EXTRACT;
+               goto skip;
+       }
+       /* -o */
+       if (opt & CPIO_OPT_CREATE) {
+               if (*cpio_fmt != 'n') /* we _require_ "-H newc" */
+                       bb_show_usage();
+               if (opt & CPIO_OPT_FILE) {
+                       fclose(stdout);
+                       stdout = fopen_for_write(cpio_filename);
+                       /* Paranoia: I don't trust libc that much */
+                       xdup2(fileno(stdout), STDOUT_FILENO);
+               }
+ dump:
+               return cpio_o();
+       }
+ skip:
+#endif
+       argv += optind;
+
+       archive_handle = init_handle();
+       archive_handle->src_fd = STDIN_FILENO;
+       archive_handle->seek = seek_by_read;
+       archive_handle->ah_flags = ARCHIVE_EXTRACT_NEWER;
+
+       /* 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 */
+               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->ah_flags |= ARCHIVE_EXTRACT_UNCONDITIONAL;
+               archive_handle->ah_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 (opt & CPIO_OPT_FILE) { /* -F */
+               archive_handle->src_fd = xopen(cpio_filename, O_RDONLY);
+               archive_handle->seek = seek_by_jump;
+       }
+       if (opt & CPIO_OPT_CREATE_LEADING_DIR) {
+               archive_handle->ah_flags |= ARCHIVE_CREATE_LEADING_DIRS;
+       }
+       if (opt & CPIO_OPT_PRESERVE_MTIME) {
+               archive_handle->ah_flags |= ARCHIVE_PRESERVE_DATE;
+       }
+
+       while (*argv) {
+               archive_handle->filter = filter_accept_list;
+               llist_add_to(&(archive_handle->accept), *argv);
+               argv++;
+       }
+
+       /* see get_header_cpio */
+       archive_handle->ah_priv[2] = (void*) ~(ptrdiff_t)0;
+       while (get_header_cpio(archive_handle) == EXIT_SUCCESS)
+               continue;
+
+       if (archive_handle->ah_priv[2] != (void*) ~(ptrdiff_t)0)
+               printf("%lu blocks\n", (unsigned long)(ptrdiff_t)(archive_handle->ah_priv[2]));
+
+       return EXIT_SUCCESS;
+}
diff --git a/archival/dpkg.c b/archival/dpkg.c
new file mode 100644 (file)
index 0000000..577b77f
--- /dev/null
@@ -0,0 +1,1772 @@
+/* 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
+ *
+ *  parts of the version comparison code is plucked from the real dpkg
+ *  application which is licensed GPLv2 and
+ *  copyright (c) 1995 Ian Jackson <ian@chiark.greenend.org.uk>
+ *
+ *  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 "libbb.h"
+#include <fnmatch.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, 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 operator:4; /* was:3 */
+       unsigned type:4;
+       unsigned name:16; /* was:14 */
+       unsigned version:16; /* was:14 */
+} edge_t;
+
+typedef struct common_node_s {
+       unsigned name:16; /* was:14 */
+       unsigned version:16; /* was:14 */
+       unsigned num_of_edges:16; /* was: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 package:16; /* was:14 */       /* has to fit PACKAGE_HASH_PRIME */
+       unsigned status:16; /* was:14 */        /* has to fit STATUS_HASH_PRIME */
+} status_node_t;
+
+
+/* Globals */
+struct globals {
+       char          *name_hashtable[NAME_HASH_PRIME + 1];
+       common_node_t *package_hashtable[PACKAGE_HASH_PRIME + 1];
+       status_node_t *status_hashtable[STATUS_HASH_PRIME + 1];
+};
+#define G (*ptr_to_globals)
+#define name_hashtable    (G.name_hashtable   )
+#define package_hashtable (G.package_hashtable)
+#define status_hashtable  (G.status_hashtable )
+#define INIT_G() do { \
+       SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
+} while (0)
+
+
+/* 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
+};
+
+typedef struct deb_file_s {
+       char *control_file;
+       char *filename;
+       unsigned package:16; /* was:14 */
+} deb_file_t;
+
+
+static void make_hash(const char *key, unsigned *start, unsigned *decrement, const int hash_prime)
+{
+       unsigned long 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) hash_num % hash_prime;
+       *decrement = (unsigned) 1 + (hash_num % (hash_prime - 1));
+}
+
+/* this adds the key to the hash table */
+static int search_name_hashtable(const char *key)
+{
+       unsigned probe_address;
+       unsigned probe_decrement;
+
+       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;
+               }
+               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 search_status_hashtable(const char *key)
+{
+       unsigned probe_address;
+       unsigned probe_decrement;
+
+       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;
+               }
+               probe_address -= probe_decrement;
+               if ((int)probe_address < 0) {
+                       probe_address += STATUS_HASH_PRIME;
+               }
+       }
+       return probe_address;
+}
+
+static int order(char x)
+{
+       return (x == '~' ? -1
+               : x == '\0' ? 0
+               : isdigit(x) ? 0
+               : isalpha(x) ? x
+               : (unsigned char)x + 256
+       );
+}
+
+/* This code is taken from dpkg and modified slightly to work with busybox */
+static int version_compare_part(const char *val, const char *ref)
+{
+       if (!val) val = "";
+       if (!ref) ref = "";
+
+       while (*val || *ref) {
+               int first_diff;
+
+               while ((*val && !isdigit(*val)) || (*ref && !isdigit(*ref))) {
+                       int vc = order(*val);
+                       int rc = order(*ref);
+                       if (vc != rc)
+                               return vc - rc;
+                       val++;
+                       ref++;
+               }
+
+               while (*val == '0')
+                       val++;
+               while (*ref == '0')
+                       ref++;
+
+               first_diff = 0;
+               while (isdigit(*val) && isdigit(*ref)) {
+                       if (first_diff == 0)
+                               first_diff = *val - *ref;
+                       val++;
+                       ref++;
+               }
+               if (isdigit(*val))
+                       return 1;
+               if (isdigit(*ref))
+                       return -1;
+               if (first_diff)
+                       return first_diff;
+       }
+       return 0;
+}
+
+/* if ver1 < ver2 return -1,
+ * if ver1 = ver2 return 0,
+ * if ver1 > ver2 return 1,
+ */
+static int version_compare(const unsigned ver1, const unsigned ver2)
+{
+       char *ch_ver1 = name_hashtable[ver1];
+       char *ch_ver2 = name_hashtable[ver2];
+       unsigned long epoch1 = 0, epoch2 = 0;
+       char *colon;
+       char *deb_ver1, *deb_ver2;
+       char *upstream_ver1;
+       char *upstream_ver2;
+       int result;
+
+       /* Compare epoch */
+       colon = strchr(ch_ver1, ':');
+       if (colon) {
+               epoch1 = atoi(ch_ver1);
+               ch_ver1 = colon + 1;
+       }
+       colon = strchr(ch_ver2, ':');
+       if (colon) {
+               epoch2 = atoi(ch_ver2);
+               ch_ver2 = colon + 1;
+       }
+       if (epoch1 < epoch2) {
+               return -1;
+       }
+       if (epoch1 > epoch2) {
+               return 1;
+       }
+
+       /* Compare upstream version */
+       upstream_ver1 = xstrdup(ch_ver1);
+       upstream_ver2 = xstrdup(ch_ver2);
+
+       /* 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 version1, const unsigned version2, const unsigned operator)
+{
+       const int version_result = version_compare(version1, version2);
+       switch (operator) {
+       case VER_ANY:
+               return TRUE;
+       case VER_EQUAL:
+               return (version_result == 0);
+       case VER_LESS:
+               return (version_result < 0);
+       case VER_LESS_EQUAL:
+               return (version_result <= 0);
+       case VER_MORE:
+               return (version_result > 0);
+       case VER_MORE_EQUAL:
+               return (version_result >= 0);
+       }
+       return FALSE;
+}
+
+static int search_package_hashtable(const unsigned name, const unsigned version, const unsigned operator)
+{
+       unsigned probe_address;
+       unsigned probe_decrement;
+
+       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->edge = xrealloc_vector(node->edge, 2, node->num_of_edges);
+       node->edge[node->num_of_edges++] = 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 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);
+               or_edge = NULL;
+               if ((edge_type == EDGE_DEPENDS || edge_type == EDGE_PRE_DEPENDS)
+                && (strcmp(field, field2) != 0)
+               ) {
+                       or_edge = xzalloc(sizeof(edge_t));
+                       or_edge->type = edge_type + 1;
+                       or_edge->name = search_name_hashtable(field);
+                       //or_edge->version = 0; // tracks the number of alternatives
+                       add_edge_to_node(parent_node, or_edge);
+               }
+
+               do {
+                       edge = 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(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);
+                       field2 = strtok_r(NULL, "|", &line_ptr2);
+               } while (field2 != NULL);
+
+               free(line2);
+               field = strtok_r(NULL, ",", &line_ptr1);
+       } while (field != NULL);
+
+       free(line);
+}
+
+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++;
+       }
+       *field_name = NULL;
+       if (name_length) {
+               *field_name = xstrndup(&package_buffer[offset_name_start], name_length);
+       }
+       *field_value = NULL;
+       if (value_length > 0) {
+               *field_value = xstrndup(&package_buffer[offset_value_start], value_length);
+       }
+       return next_offset;
+}
+
+static unsigned fill_package_struct(char *control_buffer)
+{
+       static const char field_names[] ALIGN1 =
+               "Package\0""Version\0"
+               "Pre-Depends\0""Depends\0""Replaces\0""Provides\0"
+               "Conflicts\0""Suggests\0""Recommends\0""Enhances\0";
+
+       common_node_t *new_node = 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;
+               }
+
+               field_num = index_in_strings(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);
+       free_package(package_hashtable[num]);
+       package_hashtable[num] = new_node;
+       return num;
+}
+
+/* if num = 1, it returns the want status, 2 returns flag, 3 returns status */
+static unsigned get_status(const unsigned status_node, const int num)
+{
+       char *status_string = name_hashtable[status_hashtable[status_node]->status];
+       char *state_sub_string;
+       unsigned 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 status_node_num, const char *new_value, const int position)
+{
+       const unsigned new_value_len = strlen(new_value);
+       const unsigned new_value_num = search_name_hashtable(new_value);
+       unsigned want = get_status(status_node_num, 1);
+       unsigned flag = get_status(status_node_num, 2);
+       unsigned 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);
+}
+
+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";
+
+       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 status_num;
+
+       status_file = xfopen_for_read(filename);
+       while ((control_buffer = xmalloc_fgetline_str(status_file, "\n\n")) != NULL) {
+               const unsigned 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);
+}
+
+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);
+               }
+       }
+}
+
+/* This could do with a cleanup */
+static void write_status_file(deb_file_t **deb_file)
+{
+       FILE *old_status_file = xfopen_for_read("/var/lib/dpkg/status");
+       FILE *new_status_file = xfopen_for_write("/var/lib/dpkg/status.udeb");
+       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_fgetline_str(old_status_file, "\n\n")) != NULL) {
+               tmp_string = strstr(control_buffer, "Package:");
+               if (tmp_string == 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) {
+               if (errno != ENOENT)
+                       bb_error_msg_and_die("cannot create backup status file");
+               /* 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");
+       }
+
+       xrename("/var/lib/dpkg/status.udeb", "/var/lib/dpkg/status");
+}
+
+/* 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 package_num = deb_file[i]->package;
+               conflicts = xrealloc_vector(conflicts, 2, conflicts_num);
+               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 = 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_vector(conflicts, 2, conflicts_num);
+                               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 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 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;
+                       }
+                       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]);
+                                       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));
+                               }
+                               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;
+       char *line;
+       int count;
+
+       /* don't use [xw]fopen here, handle error ourself */
+       list_stream = fopen_for_read(filename);
+       if (list_stream == NULL) {
+               return NULL;
+       }
+
+       file_list = NULL;
+       count = 0;
+       while ((line = xmalloc_fgetline(list_stream)) != NULL) {
+               file_list = xrealloc_vector(file_list, 2, count);
+               file_list[count++] = line;
+               /*file_list[count] = NULL; - xrealloc_vector did it */
+       }
+       fclose(list_stream);
+
+       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 remove_flag = 1; /* not removed anything yet */
+       int i, j;
+
+       if (remove_names == NULL) {
+               return 0;
+       }
+       for (i = 0; remove_names[i] != NULL; i++) {
+               if (exclude_names != NULL) {
+                       for (j = 0; exclude_names[j] != NULL; j++) {
+                               if (strcmp(remove_names[i], exclude_names[j]) == 0) {
+                                       goto skip;
+                               }
+                       }
+               }
+               /* TODO: why we are checking lstat? we can just try rm/rmdir */
+               if (lstat(remove_names[i], &path_stat) < 0) {
+                       continue;
+               }
+               if (S_ISDIR(path_stat.st_mode)) {
+                       remove_flag &= rmdir(remove_names[i]); /* 0 if no error */
+               } else {
+                       remove_flag &= unlink(remove_names[i]); /* 0 if no error */
+               }
+ skip:
+               continue;
+       }
+       return (remove_flag == 0);
+}
+
+static void run_package_script_or_die(const char *package_name, const char *script_type)
+{
+       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 fatal */
+       result = access(script_path, F_OK) ? EXIT_SUCCESS : system(script_path);
+       free(script_path);
+       if (result)
+               bb_error_msg_and_die("%s failed, exit code %d", script_type, result);
+}
+
+/*
+The policy manual defines what scripts get called when and with
+what arguments. I realize that busybox does not support all of
+these scenarios, but it does support some of them; it does not,
+however, run them with any parameters in run_package_script_or_die().
+Here are the scripts:
+
+preinst install
+preinst install <old_version>
+preinst upgrade <old_version>
+preinst abort_upgrade <new_version>
+postinst configure <most_recent_version>
+postinst abort-upgade <new_version>
+postinst abort-remove
+postinst abort-remove in-favour <package> <version>
+postinst abort-deconfigure in-favor <failed_install_package> removing <conflicting_package> <version>
+prerm remove
+prerm upgrade <new_version>
+prerm failed-upgrade <old_version>
+prerm remove in-favor <package> <new_version>
+prerm deconfigure in-favour <package> <version> removing <package> <version>
+postrm remove
+postrm purge
+postrm upgrade <new_version>
+postrm failed-upgrade <old_version>
+postrm abort-install
+postrm abort-install <old_version>
+postrm abort-upgrade <old_version>
+postrm disappear <overwriter> <version>
+*/
+static const char *const all_control_files[] = {
+       "preinst", "postinst", "prerm", "postrm",
+       "list", "md5sums", "shlibs", "conffiles",
+       "config", "templates"
+};
+
+static char **all_control_list(const char *package_name)
+{
+       unsigned i = 0;
+       char **remove_files;
+
+       /* Create a list of all /var/lib/dpkg/info/<package> files */
+       remove_files = xzalloc(sizeof(all_control_files) + sizeof(char*));
+       while (i < ARRAY_SIZE(all_control_files)) {
+               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(const char *pattern)
+{
+       int i;
+
+       puts("    Name           Version");
+       puts("+++-==============-==============");
+
+       /* go through status hash, dereference package hash and finally strings */
+       for (i = 0; i < STATUS_HASH_PRIME+1; i++) {
+               if (status_hashtable[i]) {
+                       const char *stat_str;  /* status string */
+                       const char *name_str;  /* package name */
+                       const char *vers_str;  /* version */
+                       char  s1, s2;          /* status abbreviations */
+                       int   spccnt;          /* space count */
+                       int   j;
+
+                       stat_str = name_hashtable[status_hashtable[i]->status];
+                       name_str = name_hashtable[package_hashtable[status_hashtable[i]->package]->name];
+                       vers_str = name_hashtable[package_hashtable[status_hashtable[i]->package]->version];
+
+                       if (pattern && fnmatch(pattern, name_str, 0))
+                               continue;
+
+                       /* 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 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 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];
+
+       if (noisy)
+               printf("Removing %s (%s)...\n", package_name, package_version);
+
+       /* Run prerm script */
+       run_package_script_or_die(package_name, "prerm");
+
+       /* Create a list of files to remove, and a separate list of those to keep */
+       sprintf(list_name, "/var/lib/dpkg/info/%s.%s", package_name, "list");
+       remove_files = create_list(list_name);
+
+       sprintf(conffile_name, "/var/lib/dpkg/info/%s.%s", package_name, "conffiles");
+       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))
+               continue;
+       free_array(exclude_files);
+       free_array(remove_files);
+
+       /* Create a list of files in /var/lib/dpkg/info/<package>.* to keep  */
+       exclude_files = xzalloc(sizeof(char*) * 3);
+       exclude_files[0] = xstrdup(conffile_name);
+       exclude_files[1] = xasprintf("/var/lib/dpkg/info/%s.%s", package_name, "postrm");
+
+       /* Create a list of all /var/lib/dpkg/info/<package> files */
+       remove_files = all_control_list(package_name);
+
+       remove_file_array(remove_files, exclude_files);
+       free_array(remove_files);
+       free_array(exclude_files);
+
+       /* rename <package>.conffiles to <package>.list
+        * The conffiles control file isn't required in Debian packages, so don't
+        * error out if it's missing.  */
+       rename(conffile_name, list_name);
+
+       /* Change package status */
+       set_status(status_num, "config-files", 3);
+}
+
+static void purge_package(const unsigned 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 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 */
+       run_package_script_or_die(package_name, "prerm");
+
+       /* Create a list of files to remove */
+       sprintf(list_name, "/var/lib/dpkg/info/%s.%s", package_name, "list");
+       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/<package> 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 */
+       run_package_script_or_die(package_name, "postrm");
+
+       /* 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.* */
+#if ENABLE_FEATURE_SEAMLESS_GZ
+       llist_add_to(&(ar_handle->accept), (char*)"control.tar.gz");
+#endif
+#if ENABLE_FEATURE_SEAMLESS_BZ2
+       llist_add_to(&(ar_handle->accept), (char*)"control.tar.bz2");
+#endif
+
+       /* Assign the tar handle as a subarchive of the ar handle */
+       ar_handle->sub_archive = tar_handle;
+}
+
+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.* */
+#if ENABLE_FEATURE_SEAMLESS_GZ
+       llist_add_to(&(ar_handle->accept), (char*)"data.tar.gz");
+#endif
+#if ENABLE_FEATURE_SEAMLESS_BZ2
+       llist_add_to(&(ar_handle->accept), (char*)"data.tar.bz2");
+#endif
+
+       /* Assign the tar handle as a subarchive of the ar handle */
+       ar_handle->sub_archive = tar_handle;
+}
+
+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 FAST_FUNC 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);
+       }
+}
+
+static void unpack_package(deb_file_t *deb_file)
+{
+       const char *package_name = name_hashtable[package_hashtable[deb_file->package]->name];
+       const unsigned status_num = search_status_hashtable(package_name);
+       const unsigned 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;
+       int i;
+
+       /* 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/<package>.filename */
+       info_prefix = xasprintf("/var/lib/dpkg/info/%s.%s", package_name, "");
+       archive_handle = init_archive_deb_ar(deb_file->filename);
+       init_archive_deb_control(archive_handle);
+
+       accept_list = NULL;
+       i = 0;
+       while (i < ARRAY_SIZE(all_control_files)) {
+               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->ah_flags |= ARCHIVE_EXTRACT_UNCONDITIONAL;
+       unpack_ar_archive(archive_handle);
+
+       /* Run the preinst prior to extracting */
+       run_package_script_or_die(package_name, "preinst");
+
+       /* 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 = (char*)"/"; /* huh? */
+       archive_handle->sub_archive->ah_flags |= ARCHIVE_EXTRACT_UNCONDITIONAL;
+       unpack_ar_archive(archive_handle);
+
+       /* Create the list file */
+       list_filename = xasprintf("/var/lib/dpkg/info/%s.%s", package_name, "list");
+       out_stream = xfopen_for_write(list_filename);
+       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 */
+       /* TODO: handle failure gracefully */
+       run_package_script_or_die(package_name, "postinst");
+
+       /* Change status to reflect success */
+       set_status(status_num, "install", 1);
+       set_status(status_num, "installed", 3);
+}
+
+int dpkg_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int dpkg_main(int argc UNUSED_PARAM, char **argv)
+{
+       deb_file_t **deb_file = NULL;
+       status_node_t *status_node;
+       char *str_f;
+       int opt;
+       int package_num;
+       int deb_count = 0;
+       int state_status;
+       int status_num;
+       int i;
+       enum {
+               OPT_configure = 0x1,
+               OPT_force_ignore_depends = 0x2,
+               OPT_install = 0x4,
+               OPT_list_installed = 0x8,
+               OPT_purge = 0x10,
+               OPT_remove = 0x20,
+               OPT_unpack = 0x40,
+       };
+
+       INIT_G();
+
+       opt = getopt32(argv, "CF:ilPru", &str_f);
+       //if (opt & OPT_configure) ... // -C
+       if (opt & OPT_force_ignore_depends) { // -F (--force in official dpkg)
+               if (strcmp(str_f, "depends"))
+                       opt &= ~OPT_force_ignore_depends;
+       }
+       //if (opt & OPT_install) ... // -i
+       //if (opt & OPT_list_installed) ... // -l
+       //if (opt & OPT_purge) ... // -P
+       //if (opt & OPT_remove) ... // -r
+       //if (opt & OPT_unpack) ... // -u (--unpack in official dpkg)
+       argv += optind;
+       /* check for non-option argument if expected  */
+       if (!opt || (!argv[0] && !(opt && 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 (opt & OPT_list_installed) {
+               list_packages(argv[0]);
+               return EXIT_SUCCESS;
+       }
+
+       /* Read arguments and store relevant info in structs */
+       while (*argv) {
+               /* deb_count = nb_elem - 1 and we need nb_elem + 1 to allocate terminal node [NULL pointer] */
+               deb_file = xrealloc_vector(deb_file, 2, deb_count);
+               deb_file[deb_count] = xzalloc(sizeof(deb_file[0][0]));
+               if (opt & (OPT_install | OPT_unpack)) {
+                       /* -i/-u: require filename */
+                       archive_handle_t *archive_handle;
+                       llist_t *control_list = NULL;
+
+                       /* Extract the control file */
+                       llist_add_to(&control_list, (char*)"./control");
+                       archive_handle = init_archive_deb_ar(argv[0]);
+                       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[0]);
+                       package_num = fill_package_struct(deb_file[deb_count]->control_file);
+
+                       if (package_num == -1) {
+                               bb_error_msg("invalid control file in %s", argv[0]);
+                               argv++;
+                               continue;
+                       }
+                       deb_file[deb_count]->package = (unsigned) package_num;
+
+                       /* Add the package to the status hashtable */
+                       if (opt & (OPT_unpack | 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 = 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 (opt & (OPT_configure | OPT_purge | OPT_remove)) {
+                       /* -C/-p/-r: require package name */
+                       deb_file[deb_count]->package = search_package_hashtable(
+                                       search_name_hashtable(argv[0]),
+                                       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[0]);
+                       }
+                       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 (opt & 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 (opt & 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++;
+               argv++;
+       }
+       if (!deb_count)
+               bb_error_msg_and_die("no package files specified");
+       deb_file[deb_count] = NULL;
+
+       /* Check that the deb file arguments are installable */
+       if (!(opt & 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 (opt & OPT_remove) {
+                       remove_package(deb_file[i]->package, 1);
+               }
+               else if (opt & OPT_purge) {
+                       purge_package(deb_file[i]->package);
+               }
+               else if (opt & OPT_unpack) {
+                       unpack_package(deb_file[i]);
+               }
+               else if (opt & OPT_install) {
+                       unpack_package(deb_file[i]);
+                       /* package is configured in second pass below */
+               }
+               else if (opt & OPT_configure) {
+                       configure_package(deb_file[i]);
+               }
+       }
+       /* configure installed packages */
+       if (opt & 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++) {
+                       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 (file)
index 0000000..f94c90c
--- /dev/null
@@ -0,0 +1,104 @@
+/* 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 "libbb.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) MAIN_EXTERNALLY_VISIBLE;
+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;
+       const char *extract_dir;
+       int need_args;
+
+       /* 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;
+
+#if ENABLE_FEATURE_SEAMLESS_GZ
+       llist_add_to(&(ar_archive->accept), (char*)"data.tar.gz");
+       llist_add_to(&control_tar_llist, (char*)"control.tar.gz");
+#endif
+
+#if ENABLE_FEATURE_SEAMLESS_BZ2
+       llist_add_to(&(ar_archive->accept), (char*)"data.tar.bz2");
+       llist_add_to(&control_tar_llist, (char*)"control.tar.bz2");
+#endif
+
+       opt_complementary = "c--efXx:e--cfXx:f--ceXx:X--cefx:x--cefX";
+       opt = getopt32(argv, "cefXx");
+       argv += optind;
+       argc -= optind;
+
+       if (opt & DPKG_DEB_OPT_CONTENTS) {
+               tar_archive->action_header = header_verbose_list;
+       }
+       extract_dir = NULL;
+       need_args = 1;
+       if (opt & DPKG_DEB_OPT_CONTROL) {
+               ar_archive->accept = control_tar_llist;
+               tar_archive->action_data = data_extract_all;
+               if (1 == argc) {
+                       extract_dir = "./DEBIAN";
+               } else {
+                       need_args++;
+               }
+       }
+       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), (char*)"./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;
+               need_args = 2;
+       }
+
+       if (need_args != argc) {
+               bb_show_usage();
+       }
+
+       tar_archive->src_fd = ar_archive->src_fd = xopen(argv[0], O_RDONLY);
+
+       /* Work out where to extract the files */
+       /* 2nd argument is a dir name */
+       if (argv[1]) {
+               extract_dir = argv[1];
+       }
+       if (extract_dir) {
+               mkdir(extract_dir, 0777); /* bb_make_directory(extract_dir, 0777, 0) */
+               xchdir(extract_dir);
+       }
+
+       /* Do it */
+       unpack_ar_archive(ar_archive);
+
+       /* Cleanup */
+       if (ENABLE_FEATURE_CLEAN_UP)
+               close(ar_archive->src_fd);
+
+       return EXIT_SUCCESS;
+}
diff --git a/archival/gzip.c b/archival/gzip.c
new file mode 100644 (file)
index 0000000..a76e1d3
--- /dev/null
@@ -0,0 +1,2096 @@
+/* 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 <cpw@unix.asb.com>
+ * "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 <andersen@codepoet.org> 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.
+ */
+
+/* big objects in bss:
+ * 00000020 b bl_count
+ * 00000074 b base_length
+ * 00000078 b base_dist
+ * 00000078 b static_dtree
+ * 0000009c b bl_tree
+ * 000000f4 b dyn_dtree
+ * 00000100 b length_code
+ * 00000200 b dist_code
+ * 0000023d b depth
+ * 00000400 b flag_buf
+ * 0000047a b heap
+ * 00000480 b static_ltree
+ * 000008f4 b dyn_ltree
+ */
+
+/* TODO: full support for -v for DESKTOP
+ * "/usr/bin/gzip -v a bogus aa" should say:
+a:       85.1% -- replaced with a.gz
+gzip: bogus: No such file or directory
+aa:      85.1% -- replaced with aa.gz
+*/
+
+#include "libbb.h"
+#include "unarchive.h"
+
+
+/* ===========================================================================
+ */
+//#define DEBUG 1
+/* 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 SMALL_MEM
+
+#ifndef        INBUFSIZ
+#  ifdef SMALL_MEM
+#    define INBUFSIZ  0x2000   /* input buffer size */
+#  else
+#    define INBUFSIZ  0x8000   /* input buffer size */
+#  endif
+#endif
+
+#ifndef        OUTBUFSIZ
+#  ifdef SMALL_MEM
+#    define OUTBUFSIZ   8192   /* output buffer size */
+#  else
+#    define OUTBUFSIZ  16384   /* output buffer size */
+#  endif
+#endif
+
+#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
+
+/* 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.
+ */
+
+#ifndef MAX_PATH_LEN
+#  define MAX_PATH_LEN   1024  /* max pathname length */
+#endif
+
+#define seekable()    0        /* force sequential output */
+#define translate_eol 0        /* no option -a yet */
+
+#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".
+ */
+
+#ifdef MAX_EXT_CHARS
+#  define MAX_SUFFIX  MAX_EXT_CHARS
+#else
+#  define MAX_SUFFIX  30
+#endif
+
+
+/* ===========================================================================
+ * 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
+
+#define HASH_SIZE (unsigned)(1<<HASH_BITS)
+#define HASH_MASK (HASH_SIZE-1)
+#define WMASK     (WSIZE-1)
+/* HASH_SIZE and WSIZE must be powers of two */
+#ifndef TOO_FAR
+#  define TOO_FAR 4096
+#endif
+/* Matches of length 3 are discarded if their distance exceeds TOO_FAR */
+
+
+/* ===========================================================================
+ * These types are not really 'char', 'short' and 'long'
+ */
+typedef uint8_t uch;
+typedef uint16_t ush;
+typedef uint32_t ulg;
+typedef int32_t lng;
+
+typedef ush Pos;
+typedef unsigned IPos;
+/* A Pos is an index in the character window. We use short instead of int to
+ * save space in the various tables. IPos is used only for parameter passing.
+ */
+
+enum {
+       WINDOW_SIZE = 2 * WSIZE,
+/* window size, 2*WSIZE except for MMAP or BIG_MEM, where it is the
+ * input file length plus MIN_LOOKAHEAD.
+ */
+
+       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.
+ */
+};
+
+
+struct globals {
+
+       lng block_start;
+
+/* window position at the beginning of the current output block. Gets
+ * negative when the window is moved backwards.
+ */
+       unsigned ins_h; /* hash index of string to be inserted */
+
+#define H_SHIFT  ((HASH_BITS+MIN_MATCH-1) / MIN_MATCH)
+/* Number of bits by which ins_h and del_h must be shifted at each
+ * input step. It must be such that after MIN_MATCH steps, the oldest
+ * byte no longer takes part in the hash key, that is:
+ * H_SHIFT * MIN_MATCH >= HASH_BITS
+ */
+
+       unsigned 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.
+ */
+
+       unsigned strstart;      /* start of string to insert */
+       unsigned match_start;   /* start of matching string */
+       unsigned lookahead;     /* number of valid bytes ahead in window */
+
+/* ===========================================================================
+ */
+#define DECLARE(type, array, size) \
+       type * array
+#define ALLOC(type, array, size) \
+       array = xzalloc((size_t)(((size)+1L)/2) * 2*sizeof(type));
+#define FREE(array) \
+       do { free(array); array = NULL; } while (0)
+
+       /* global buffers */
+
+       /* buffer for literals or lengths */
+       /* DECLARE(uch, l_buf, LIT_BUFSIZE); */
+       DECLARE(uch, l_buf, INBUFSIZ);
+
+       DECLARE(ush, d_buf, DIST_BUFSIZE);
+       DECLARE(uch, outbuf, OUTBUFSIZ);
+
+/* Sliding window. Input bytes are read into the second half of the window,
+ * and move to the first half later to keep a dictionary of at least WSIZE
+ * bytes. With this organization, matches are limited to a distance of
+ * WSIZE-MAX_MATCH bytes, but this ensures that IO is always
+ * performed with a length multiple of the block size. Also, it limits
+ * the window size to 64K, which is quite useful on MSDOS.
+ * To do: limit the window size to WSIZE+BSZ if SMALL_MEM (the code would
+ * be less efficient).
+ */
+       DECLARE(uch, window, 2L * WSIZE);
+
+/* Link to older string with same hash index. To limit the size of this
+ * array to 64K, this link is maintained only for the last 32K strings.
+ * An index in this array is thus a window index modulo 32K.
+ */
+       /* DECLARE(Pos, prev, WSIZE); */
+       DECLARE(ush, prev, 1L << BITS);
+
+/* Heads of the hash chains or 0. */
+       /* DECLARE(Pos, head, 1<<HASH_BITS); */
+#define head (G1.prev + WSIZE) /* hash head (see deflate.c) */
+
+/* number of input bytes */
+       ulg isize;              /* only 32 bits stored in .gz file */
+
+/* bbox always use stdin/stdout */
+#define ifd STDIN_FILENO       /* input file descriptor */
+#define ofd STDOUT_FILENO      /* output file descriptor */
+
+#ifdef DEBUG
+       unsigned insize;        /* valid bytes in l_buf */
+#endif
+       unsigned outcnt;        /* bytes in output buffer */
+
+       smallint eofile;        /* flag set at end of input file */
+
+/* ===========================================================================
+ * Local data used by the "bit string" routines.
+ */
+
+       unsigned short bi_buf;
+
+/* Output buffer. bits are inserted starting at the bottom (least significant
+ * bits).
+ */
+
+#undef BUF_SIZE
+#define BUF_SIZE (8 * sizeof(G1.bi_buf))
+/* Number of bits used within bi_buf. (bi_buf might be implemented on
+ * more than 16 bits on some systems.)
+ */
+
+       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
+
+       uint32_t *crc_32_tab;
+       uint32_t crc;   /* shift register contents */
+};
+
+#define G1 (*(ptr_to_globals - 1))
+
+
+/* ===========================================================================
+ * Write the output buffer outbuf[0..outcnt-1] and update bytes_out.
+ * (used for the compressed data only)
+ */
+static void flush_outbuf(void)
+{
+       if (G1.outcnt == 0)
+               return;
+
+       xwrite(ofd, (char *) G1.outbuf, G1.outcnt);
+       G1.outcnt = 0;
+}
+
+
+/* ===========================================================================
+ */
+/* put_8bit is used for the compressed output */
+#define put_8bit(c) \
+do { \
+       G1.outbuf[G1.outcnt++] = (c); \
+       if (G1.outcnt == OUTBUFSIZ) flush_outbuf(); \
+} while (0)
+
+/* Output a 16 bit value, lsb first */
+static void put_16bit(ush w)
+{
+       if (G1.outcnt < OUTBUFSIZ - 2) {
+               G1.outbuf[G1.outcnt++] = w;
+               G1.outbuf[G1.outcnt++] = w >> 8;
+       } else {
+               put_8bit(w);
+               put_8bit(w >> 8);
+       }
+}
+
+static void put_32bit(ulg n)
+{
+       put_16bit(n);
+       put_16bit(n >> 16);
+}
+
+/* ===========================================================================
+ * 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)
+{
+       uint32_t c = G1.crc;
+       while (n) {
+               c = G1.crc_32_tab[(uch)(c ^ *s++)] ^ (c >> 8);
+               n--;
+       }
+       G1.crc = c;
+       return c;
+}
+
+
+/* ===========================================================================
+ * 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 unsigned file_read(void *buf, unsigned size)
+{
+       unsigned len;
+
+       Assert(G1.insize == 0, "l_buf not empty");
+
+       len = safe_read(ifd, buf, size);
+       if (len == (unsigned)(-1) || len == 0)
+               return len;
+
+       updcrc(buf, len);
+       G1.isize += len;
+       return len;
+}
+
+
+/* ===========================================================================
+ * 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");
+       G1.bits_sent += 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 (G1.bi_valid > (int) BUF_SIZE - length) {
+               G1.bi_buf |= (value << G1.bi_valid);
+               put_16bit(G1.bi_buf);
+               G1.bi_buf = (ush) value >> (BUF_SIZE - G1.bi_valid);
+               G1.bi_valid += length - BUF_SIZE;
+       } else {
+               G1.bi_buf |= value << G1.bi_valid;
+               G1.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;
+
+       while (1) {
+               res |= code & 1;
+               if (--len <= 0) return res;
+               code >>= 1;
+               res <<= 1;
+       }
+}
+
+
+/* ===========================================================================
+ * Write out any remaining bits in an incomplete byte.
+ */
+static void bi_windup(void)
+{
+       if (G1.bi_valid > 8) {
+               put_16bit(G1.bi_buf);
+       } else if (G1.bi_valid > 0) {
+               put_8bit(G1.bi_buf);
+       }
+       G1.bi_buf = 0;
+       G1.bi_valid = 0;
+#ifdef DEBUG
+       G1.bits_sent = (G1.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_16bit(len);
+               put_16bit(~len);
+#ifdef DEBUG
+               G1.bits_sent += 2 * 16;
+#endif
+       }
+#ifdef DEBUG
+       G1.bits_sent += (ulg) len << 3;
+#endif
+       while (len--) {
+               put_8bit(*buf++);
+       }
+}
+
+
+/* ===========================================================================
+ * 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 = WINDOW_SIZE - G1.lookahead - G1.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) -1) {
+               /* Very unlikely, but possible on 16 bit machine if strstart == 0
+                * and lookahead == 1 (input done one byte at time)
+                */
+               more--;
+       } else if (G1.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 == 2 * WSIZE, "no sliding with BIG_MEM");
+
+               memcpy(G1.window, G1.window + WSIZE, WSIZE);
+               G1.match_start -= WSIZE;
+               G1.strstart -= WSIZE;   /* we now have strstart >= MAX_DIST: */
+
+               G1.block_start -= WSIZE;
+
+               for (n = 0; n < HASH_SIZE; n++) {
+                       m = head[n];
+                       head[n] = (Pos) (m >= WSIZE ? m - WSIZE : 0);
+               }
+               for (n = 0; n < WSIZE; n++) {
+                       m = G1.prev[n];
+                       G1.prev[n] = (Pos) (m >= WSIZE ? m - WSIZE : 0);
+                       /* 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 (!G1.eofile) {
+               n = file_read(G1.window + G1.strstart + G1.lookahead, more);
+               if (n == 0 || n == (unsigned) -1) {
+                       G1.eofile = 1;
+               } else {
+                       G1.lookahead += n;
+               }
+       }
+}
+
+
+/* ===========================================================================
+ * Set match_start to the longest match starting at the given string and
+ * return its length. Matches shorter or equal to prev_length are discarded,
+ * in which case the result is equal to prev_length and match_start is
+ * garbage.
+ * IN assertions: cur_match is the head of the hash chain for the current
+ *   string (strstart) and its distance is <= MAX_DIST, and prev_length >= 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 = G1.window + G1.strstart;    /* current string */
+       uch *match;     /* matched string */
+       int len;        /* length of current match */
+       int best_len = G1.prev_length;  /* best match length so far */
+       IPos limit = G1.strstart > (IPos) MAX_DIST ? G1.strstart - (IPos) MAX_DIST : 0;
+       /* 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 = G1.window + G1.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 (G1.prev_length >= good_match) {
+               chain_length >>= 2;
+       }
+       Assert(G1.strstart <= WINDOW_SIZE - MIN_LOOKAHEAD, "insufficient lookahead");
+
+       do {
+               Assert(cur_match < G1.strstart, "no future");
+               match = G1.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) {
+                       G1.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 = G1.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(G1.window + match, G1.window + start, length) != 0) {
+               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 {
+                       fputc(G1.window[start++], stderr);
+               } while (--length != 0);
+       }
+}
+#else
+#  define check_match(start, match, length) ((void)0)
+#endif
+
+
+/* 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()
+ *          Allocate the match buffer, initialize the various tables [and save
+ *          the location of the internal file attribute (ascii/binary) and
+ *          method (DEFLATE/STORE) -- deleted in bbox]
+ *
+ *      void ct_tally(int dist, int lc);
+ *          Save the match info and tally the frequency counts.
+ *
+ *      ulg 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.
+ */
+
+#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 */
+
+/* extra bits for each length code */
+static const uint8_t extra_lbits[LENGTH_CODES] ALIGN1 = {
+       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 uint8_t extra_dbits[D_CODES] ALIGN1 = {
+       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 uint8_t extra_blbits[BL_CODES] ALIGN1 = {
+       0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 3, 7 };
+
+/* number of codes at each bit length for an optimal tree */
+static const uint8_t bl_order[BL_CODES] ALIGN1 = {
+       16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 };
+
+#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.
+ */
+#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) */
+
+/* ===========================================================================
+*/
+/* 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 */
+
+typedef struct tree_desc {
+       ct_data *dyn_tree;      /* the dynamic tree */
+       ct_data *static_tree;   /* corresponding static tree or NULL */
+       const uint8_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;
+
+struct globals2 {
+
+       ush heap[HEAP_SIZE];     /* heap used to build the Huffman trees */
+       int heap_len;            /* number of elements in the heap */
+       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.
+ */
+
+       ct_data dyn_ltree[HEAP_SIZE];   /* literal and length tree */
+       ct_data dyn_dtree[2 * D_CODES + 1];     /* distance tree */
+
+       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).
+ */
+
+       ct_data static_dtree[D_CODES];
+
+/* The static distance tree. (Actually a trivial tree since all codes use
+ * 5 bits.)
+ */
+
+       ct_data bl_tree[2 * BL_CODES + 1];
+
+/* Huffman tree for the bit lengths */
+
+       tree_desc l_desc;
+       tree_desc d_desc;
+       tree_desc bl_desc;
+
+       ush bl_count[MAX_BITS + 1];
+
+/* The lengths of the bit length codes are sent in order of decreasing
+ * probability, to avoid transmitting the lengths for unused bit length codes.
+ */
+
+       uch depth[2 * L_CODES + 1];
+
+/* Depth of each subtree used as tie breaker for trees of equal frequency */
+
+       uch length_code[MAX_MATCH - MIN_MATCH + 1];
+
+/* length code for each normalized match length (0 == MIN_MATCH) */
+
+       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.
+ */
+
+       int base_length[LENGTH_CODES];
+
+/* First normalized length for each code (0 = MIN_MATCH) */
+
+       int base_dist[D_CODES];
+
+/* First normalized distance for each code (0 = distance of 1) */
+
+       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.
+ */
+
+       unsigned last_lit;       /* running index in l_buf */
+       unsigned last_dist;      /* running index in d_buf */
+       unsigned last_flags;     /* running index in flag_buf */
+       uch flags;               /* current flags not yet saved in flag_buf */
+       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.
+ */
+
+       ulg opt_len;             /* bit length of current block with optimal trees */
+       ulg static_len;          /* bit length of current block with static trees */
+
+       ulg compressed_len;      /* total bit length of compressed file */
+};
+
+#define G2ptr ((struct globals2*)(ptr_to_globals))
+#define G2 (*G2ptr)
+
+
+/* ===========================================================================
+ */
+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);
+
+
+#ifndef DEBUG
+/* Send a code of the given tree. c and tree must not have side effects */
+#  define SEND_CODE(c, tree) send_bits(tree[c].Code, tree[c].Len)
+#else
+#  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 ? G2.dist_code[dist] : G2.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.
+ */
+
+
+/* ===========================================================================
+ * 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++)
+               G2.dyn_ltree[n].Freq = 0;
+       for (n = 0; n < D_CODES; n++)
+               G2.dyn_dtree[n].Freq = 0;
+       for (n = 0; n < BL_CODES; n++)
+               G2.bl_tree[n].Freq = 0;
+
+       G2.dyn_ltree[END_BLOCK].Freq = 1;
+       G2.opt_len = G2.static_len = 0;
+       G2.last_lit = G2.last_dist = G2.last_flags = 0;
+       G2.flags = 0;
+       G2.flag_bit = 1;
+}
+
+
+/* ===========================================================================
+ * 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).
+ */
+
+/* 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 && G2.depth[n] <= G2.depth[m]))
+
+static void pqdownheap(ct_data * tree, int k)
+{
+       int v = G2.heap[k];
+       int j = k << 1;         /* left son of k */
+
+       while (j <= G2.heap_len) {
+               /* Set j to the smallest of the two sons: */
+               if (j < G2.heap_len && SMALLER(tree, G2.heap[j + 1], G2.heap[j]))
+                       j++;
+
+               /* Exit if v is smaller than both sons */
+               if (SMALLER(tree, v, G2.heap[j]))
+                       break;
+
+               /* Exchange v with the smallest son */
+               G2.heap[k] = G2.heap[j];
+               k = j;
+
+               /* And continue down the tree, setting j to the left son of k */
+               j <<= 1;
+       }
+       G2.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 uint8_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++)
+               G2.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[G2.heap[G2.heap_max]].Len = 0;     /* root of the heap */
+
+       for (h = G2.heap_max + 1; h < HEAP_SIZE; h++) {
+               n = G2.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 */
+
+               G2.bl_count[bits]++;
+               xbits = 0;
+               if (n >= base)
+                       xbits = extra[n - base];
+               f = tree[n].Freq;
+               G2.opt_len += (ulg) f *(bits + xbits);
+
+               if (stree)
+                       G2.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 (G2.bl_count[bits] == 0)
+                       bits--;
+               G2.bl_count[bits]--;    /* move one leaf down the tree */
+               G2.bl_count[bits + 1] += 2;     /* move one overflow item as its brother */
+               G2.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 = G2.bl_count[bits];
+               while (n != 0) {
+                       m = G2.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));
+                               G2.opt_len += ((int32_t) bits - tree[m].Len) * tree[m].Freq;
+                               tree[m].Len = 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 + G2.bl_count[bits - 1]) << 1;
+       }
+       /* Check that the bit counts in bl_count are consistent. The last code
+        * must be all ones.
+        */
+       Assert(code + G2.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 != G2.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.
+ */
+
+/* Remove the smallest element from the heap and recreate the heap with
+ * one less element. Updates heap and heap_len. */
+
+#define SMALLEST 1
+/* Index within the heap array of least frequent node in the Huffman tree */
+
+#define PQREMOVE(tree, top) \
+do { \
+       top = G2.heap[SMALLEST]; \
+       G2.heap[SMALLEST] = G2.heap[G2.heap_len--]; \
+       pqdownheap(tree, SMALLEST); \
+} while (0)
+
+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.
+        */
+       G2.heap_len = 0;
+       G2.heap_max = HEAP_SIZE;
+
+       for (n = 0; n < elems; n++) {
+               if (tree[n].Freq != 0) {
+                       G2.heap[++G2.heap_len] = max_code = n;
+                       G2.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 (G2.heap_len < 2) {
+               int new = G2.heap[++G2.heap_len] = (max_code < 2 ? ++max_code : 0);
+
+               tree[new].Freq = 1;
+               G2.depth[new] = 0;
+               G2.opt_len--;
+               if (stree)
+                       G2.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 = G2.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 = G2.heap[SMALLEST];  /* m = node of next least frequency */
+
+               G2.heap[--G2.heap_max] = n;     /* keep the nodes sorted by frequency */
+               G2.heap[--G2.heap_max] = m;
+
+               /* Create a new node father of n and m */
+               tree[node].Freq = tree[n].Freq + tree[m].Freq;
+               G2.depth[node] = MAX(G2.depth[n], G2.depth[m]) + 1;
+               tree[n].Dad = tree[m].Dad = (ush) node;
+#ifdef DUMP_BL_TREE
+               if (tree == G2.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 */
+               G2.heap[SMALLEST] = node++;
+               pqdownheap(tree, SMALLEST);
+
+       } while (G2.heap_len >= 2);
+
+       G2.heap[--G2.heap_max] = G2.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 = 0xffff; /* guard */
+
+       for (n = 0; n <= max_code; n++) {
+               curlen = nextlen;
+               nextlen = tree[n + 1].Len;
+               if (++count < max_count && curlen == nextlen)
+                       continue;
+
+               if (count < min_count) {
+                       G2.bl_tree[curlen].Freq += count;
+               } else if (curlen != 0) {
+                       if (curlen != prevlen)
+                               G2.bl_tree[curlen].Freq++;
+                       G2.bl_tree[REP_3_6].Freq++;
+               } else if (count <= 10) {
+                       G2.bl_tree[REPZ_3_10].Freq++;
+               } else {
+                       G2.bl_tree[REPZ_11_138].Freq++;
+               }
+               count = 0;
+               prevlen = curlen;
+
+               max_count = 7;
+               min_count = 4;
+               if (nextlen == 0) {
+                       max_count = 138;
+                       min_count = 3;
+               } else if (curlen == nextlen) {
+                       max_count = 6;
+                       min_count = 3;
+               }
+       }
+}
+
+
+/* ===========================================================================
+ * 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, G2.bl_tree);
+                       } while (--count);
+               } else if (curlen != 0) {
+                       if (curlen != prevlen) {
+                               SEND_CODE(curlen, G2.bl_tree);
+                               count--;
+                       }
+                       Assert(count >= 3 && count <= 6, " 3_6?");
+                       SEND_CODE(REP_3_6, G2.bl_tree);
+                       send_bits(count - 3, 2);
+               } else if (count <= 10) {
+                       SEND_CODE(REPZ_3_10, G2.bl_tree);
+                       send_bits(count - 3, 3);
+               } else {
+                       SEND_CODE(REPZ_11_138, G2.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(G2.dyn_ltree, G2.l_desc.max_code);
+       scan_tree(G2.dyn_dtree, G2.d_desc.max_code);
+
+       /* Build the bit length tree: */
+       build_tree(&G2.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 (G2.bl_tree[bl_order[max_blindex]].Len != 0)
+                       break;
+       }
+       /* Update opt_len to include the bit length tree and counts */
+       G2.opt_len += 3 * (max_blindex + 1) + 5 + 5 + 4;
+       Tracev((stderr, "\ndyn trees: dyn %ld, stat %ld", G2.opt_len, G2.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(G2.bl_tree[bl_order[rank]].Len, 3);
+       }
+       Tracev((stderr, "\nbl tree: sent %ld", G1.bits_sent));
+
+       send_tree((ct_data *) G2.dyn_ltree, lcodes - 1);        /* send the literal tree */
+       Tracev((stderr, "\nlit tree: sent %ld", G1.bits_sent));
+
+       send_tree((ct_data *) G2.dyn_dtree, dcodes - 1);        /* send the distance tree */
+       Tracev((stderr, "\ndist tree: sent %ld", G1.bits_sent));
+}
+
+
+/* ===========================================================================
+ * 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)
+{
+       G1.l_buf[G2.last_lit++] = lc;
+       if (dist == 0) {
+               /* lc is the unmatched char */
+               G2.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"
+               );
+
+               G2.dyn_ltree[G2.length_code[lc] + LITERALS + 1].Freq++;
+               G2.dyn_dtree[D_CODE(dist)].Freq++;
+
+               G1.d_buf[G2.last_dist++] = dist;
+               G2.flags |= G2.flag_bit;
+       }
+       G2.flag_bit <<= 1;
+
+       /* Output the flags if they fill a byte: */
+       if ((G2.last_lit & 7) == 0) {
+               G2.flag_buf[G2.last_flags++] = G2.flags;
+               G2.flags = 0;
+               G2.flag_bit = 1;
+       }
+       /* Try to guess if it is profitable to stop the current block here */
+       if ((G2.last_lit & 0xfff) == 0) {
+               /* Compute an upper bound for the compressed length */
+               ulg out_length = G2.last_lit * 8L;
+               ulg in_length = (ulg) G1.strstart - G1.block_start;
+               int dcode;
+
+               for (dcode = 0; dcode < D_CODES; dcode++) {
+                       out_length += G2.dyn_dtree[dcode].Freq * (5L + extra_dbits[dcode]);
+               }
+               out_length >>= 3;
+               Trace((stderr,
+                          "\nlast_lit %u, last_dist %u, in %ld, out ~%ld(%ld%%) ",
+                          G2.last_lit, G2.last_dist, in_length, out_length,
+                          100L - out_length * 100L / in_length));
+               if (G2.last_dist < G2.last_lit / 2 && out_length < in_length / 2)
+                       return 1;
+       }
+       return (G2.last_lit == LIT_BUFSIZE - 1 || G2.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 (G2.last_lit != 0) do {
+               if ((lx & 7) == 0)
+                       flag = G2.flag_buf[fx++];
+               lc = G1.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 = G2.length_code[lc];
+                       SEND_CODE(code + LITERALS + 1, ltree);  /* send the length code */
+                       extra = extra_lbits[code];
+                       if (extra != 0) {
+                               lc -= G2.base_length[code];
+                               send_bits(lc, extra);   /* send the extra length bits */
+                       }
+                       dist = G1.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 -= G2.base_dist[code];
+                               send_bits(dist, extra); /* send the extra distance bits */
+                       }
+               }                       /* literal or match pair ? */
+               flag >>= 1;
+       } while (lx < G2.last_lit);
+
+       SEND_CODE(END_BLOCK, ltree);
+}
+
+
+/* ===========================================================================
+ * 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 */
+
+       G2.flag_buf[G2.last_flags] = G2.flags;   /* Save the flags for the last 8 items */
+
+       /* Construct the literal and distance trees */
+       build_tree(&G2.l_desc);
+       Tracev((stderr, "\nlit data: dyn %ld, stat %ld", G2.opt_len, G2.static_len));
+
+       build_tree(&G2.d_desc);
+       Tracev((stderr, "\ndist data: dyn %ld, stat %ld", G2.opt_len, G2.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 = (G2.opt_len + 3 + 7) >> 3;
+       static_lenb = (G2.static_len + 3 + 7) >> 3;
+
+       Trace((stderr,
+                  "\nopt %lu(%lu) stat %lu(%lu) stored %lu lit %u dist %u ",
+                  opt_lenb, G2.opt_len, static_lenb, G2.static_len, stored_len,
+                  G2.last_lit, G2.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 && G2.compressed_len == 0L && seekable()) {
+               /* Since LIT_BUFSIZE <= 2*WSIZE, the input data must be there: */
+               if (buf == NULL)
+                       bb_error_msg("block vanished");
+
+               copy_block(buf, (unsigned) stored_len, 0);      /* without header */
+               G2.compressed_len = stored_len << 3;
+
+       } else if (stored_len + 4 <= opt_lenb && buf != NULL) {
+               /* 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 */
+               G2.compressed_len = (G2.compressed_len + 3 + 7) & ~7L;
+               G2.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 *) G2.static_ltree, (ct_data *) G2.static_dtree);
+               G2.compressed_len += 3 + G2.static_len;
+       } else {
+               send_bits((DYN_TREES << 1) + eof, 3);
+               send_all_trees(G2.l_desc.max_code + 1, G2.d_desc.max_code + 1,
+                                          max_blindex + 1);
+               compress_block((ct_data *) G2.dyn_ltree, (ct_data *) G2.dyn_dtree);
+               G2.compressed_len += 3 + G2.opt_len;
+       }
+       Assert(G2.compressed_len == G1.bits_sent, "bad compressed size");
+       init_block();
+
+       if (eof) {
+               bi_windup();
+               G2.compressed_len += 7; /* align on byte boundary */
+       }
+       Tracev((stderr, "\ncomprlen %lu(%lu) ", G2.compressed_len >> 3,
+                       G2.compressed_len - 7 * eof));
+
+       return G2.compressed_len >> 3;
+}
+
+
+/* ===========================================================================
+ * 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)<<H_SHIFT) ^ (c)) & HASH_MASK)
+
+
+/* ===========================================================================
+ * 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.
+ *
+ * Processes a new input file and return its compressed length. Sets
+ * the compressed length, crc, deflate flags and internal file
+ * attributes.
+ */
+
+/* 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( \
+               G1.block_start >= 0L \
+                       ? (char*)&G1.window[(unsigned)G1.block_start] \
+                       : (char*)NULL, \
+               (ulg)G1.strstart - G1.block_start, \
+               (eof) \
+       )
+
+/* Insert string s in the dictionary and set match_head to the previous head
+ * of the hash chain (the most recent string with same hash key). Return
+ * the previous length of the hash chain.
+ * IN  assertion: all calls to to INSERT_STRING are made with consecutive
+ *    input characters and the first MIN_MATCH bytes of s are valid
+ *    (except for the last MIN_MATCH-1 bytes of the input file). */
+#define INSERT_STRING(s, match_head) \
+do { \
+       UPDATE_HASH(G1.ins_h, G1.window[(s) + MIN_MATCH-1]); \
+       G1.prev[(s) & WMASK] = match_head = head[G1.ins_h]; \
+       head[G1.ins_h] = (s); \
+} while (0)
+
+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 (G1.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(G1.strstart, hash_head);
+
+               /* Find the longest match, discarding those <= prev_length.
+                */
+               G1.prev_length = match_length;
+               prev_match = G1.match_start;
+               match_length = MIN_MATCH - 1;
+
+               if (hash_head != 0 && G1.prev_length < max_lazy_match
+                && G1.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 > G1.lookahead)
+                               match_length = G1.lookahead;
+
+                       /* Ignore a length 3 match if it is too distant: */
+                       if (match_length == MIN_MATCH && G1.strstart - G1.match_start > TOO_FAR) {
+                               /* If prev_match is also MIN_MATCH, G1.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 (G1.prev_length >= MIN_MATCH && match_length <= G1.prev_length) {
+                       check_match(G1.strstart - 1, prev_match, G1.prev_length);
+                       flush = ct_tally(G1.strstart - 1 - prev_match, G1.prev_length - MIN_MATCH);
+
+                       /* Insert in hash table all strings up to the end of the match.
+                        * strstart-1 and strstart are already inserted.
+                        */
+                       G1.lookahead -= G1.prev_length - 1;
+                       G1.prev_length -= 2;
+                       do {
+                               G1.strstart++;
+                               INSERT_STRING(G1.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 (--G1.prev_length != 0);
+                       match_available = 0;
+                       match_length = MIN_MATCH - 1;
+                       G1.strstart++;
+                       if (flush) {
+                               FLUSH_BLOCK(0);
+                               G1.block_start = G1.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", G1.window[G1.strstart - 1]));
+                       if (ct_tally(0, G1.window[G1.strstart - 1])) {
+                               FLUSH_BLOCK(0);
+                               G1.block_start = G1.strstart;
+                       }
+                       G1.strstart++;
+                       G1.lookahead--;
+               } else {
+                       /* There is no previous match to compare with, wait for
+                        * the next step to decide.
+                        */
+                       match_available = 1;
+                       G1.strstart++;
+                       G1.lookahead--;
+               }
+               Assert(G1.strstart <= G1.isize && lookahead <= G1.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 (G1.lookahead < MIN_LOOKAHEAD && !G1.eofile)
+                       fill_window();
+       }
+       if (match_available)
+               ct_tally(0, G1.window[G1.strstart - 1]);
+
+       return FLUSH_BLOCK(1);  /* eof */
+}
+
+
+/* ===========================================================================
+ * Initialize the bit string routines.
+ */
+static void bi_init(void)
+{
+       G1.bi_buf = 0;
+       G1.bi_valid = 0;
+#ifdef DEBUG
+       G1.bits_sent = 0L;
+#endif
+}
+
+
+/* ===========================================================================
+ * Initialize the "longest match" routines for a new file
+ */
+static void lm_init(ush * flagsp)
+{
+       unsigned j;
+
+       /* Initialize the hash table. */
+       memset(head, 0, HASH_SIZE * sizeof(*head));
+       /* prev will be initialized on the fly */
+
+       /* speed options for the general purpose bit flag */
+       *flagsp |= 2;   /* FAST 4, SLOW 2 */
+       /* ??? reduce max_chain_length for binary files */
+
+       G1.strstart = 0;
+       G1.block_start = 0L;
+
+       G1.lookahead = file_read(G1.window,
+                       sizeof(int) <= 2 ? (unsigned) WSIZE : 2 * WSIZE);
+
+       if (G1.lookahead == 0 || G1.lookahead == (unsigned) -1) {
+               G1.eofile = 1;
+               G1.lookahead = 0;
+               return;
+       }
+       G1.eofile = 0;
+       /* Make sure that we always have enough lookahead. This is important
+        * if input comes from a device such as a tty.
+        */
+       while (G1.lookahead < MIN_LOOKAHEAD && !G1.eofile)
+               fill_window();
+
+       G1.ins_h = 0;
+       for (j = 0; j < MIN_MATCH - 1; j++)
+               UPDATE_HASH(G1.ins_h, G1.window[j]);
+       /* If lookahead < MIN_MATCH, ins_h is garbage, but this is
+        * not important since only literal bytes will be emitted.
+        */
+}
+
+
+/* ===========================================================================
+ * Allocate the match buffer, initialize the various tables and save the
+ * location of the internal file attribute (ascii/binary) and method
+ * (DEFLATE/STORE).
+ * One callsite in zip()
+ */
+static void ct_init(void)
+{
+       int n;                          /* iterates over tree elements */
+       int length;                     /* length value */
+       int code;                       /* code value */
+       int dist;                       /* distance index */
+
+       G2.compressed_len = 0L;
+
+#ifdef NOT_NEEDED
+       if (G2.static_dtree[0].Len != 0)
+               return;                 /* ct_init already called */
+#endif
+
+       /* Initialize the mapping length (0..255) -> length code (0..28) */
+       length = 0;
+       for (code = 0; code < LENGTH_CODES - 1; code++) {
+               G2.base_length[code] = length;
+               for (n = 0; n < (1 << extra_lbits[code]); n++) {
+                       G2.length_code[length++] = 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:
+        */
+       G2.length_code[length - 1] = code;
+
+       /* Initialize the mapping dist (0..32K) -> dist code (0..29) */
+       dist = 0;
+       for (code = 0; code < 16; code++) {
+               G2.base_dist[code] = dist;
+               for (n = 0; n < (1 << extra_dbits[code]); n++) {
+                       G2.dist_code[dist++] = code;
+               }
+       }
+       Assert(dist == 256, "ct_init: dist != 256");
+       dist >>= 7;                     /* from now on, all distances are divided by 128 */
+       for (; code < D_CODES; code++) {
+               G2.base_dist[code] = dist << 7;
+               for (n = 0; n < (1 << (extra_dbits[code] - 7)); n++) {
+                       G2.dist_code[256 + dist++] = code;
+               }
+       }
+       Assert(dist == 256, "ct_init: 256+dist != 512");
+
+       /* Construct the codes of the static literal tree */
+       /* already zeroed - it's in bss
+       for (n = 0; n <= MAX_BITS; n++)
+               G2.bl_count[n] = 0; */
+
+       n = 0;
+       while (n <= 143) {
+               G2.static_ltree[n++].Len = 8;
+               G2.bl_count[8]++;
+       }
+       while (n <= 255) {
+               G2.static_ltree[n++].Len = 9;
+               G2.bl_count[9]++;
+       }
+       while (n <= 279) {
+               G2.static_ltree[n++].Len = 7;
+               G2.bl_count[7]++;
+       }
+       while (n <= 287) {
+               G2.static_ltree[n++].Len = 8;
+               G2.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 *) G2.static_ltree, L_CODES + 1);
+
+       /* The static distance tree is trivial: */
+       for (n = 0; n < D_CODES; n++) {
+               G2.static_dtree[n].Len = 5;
+               G2.static_dtree[n].Code = bi_reverse(n, 5);
+       }
+
+       /* Initialize the first block of the first file: */
+       init_block();
+}
+
+
+/* ===========================================================================
+ * Deflate in to out.
+ * IN assertions: the input and output buffers are cleared.
+ */
+
+static void zip(ulg time_stamp)
+{
+       ush deflate_flags = 0;  /* pkzip -es, -en or -ex equivalent */
+
+       G1.outcnt = 0;
+
+       /* Write the header to the gzip file. See algorithm.doc for the format */
+       /* magic header for gzip files: 1F 8B */
+       /* compression method: 8 (DEFLATED) */
+       /* general flags: 0 */
+       put_32bit(0x00088b1f);
+       put_32bit(time_stamp);
+
+       /* Write deflated file to zip file */
+       G1.crc = ~0;
+
+       bi_init();
+       ct_init();
+       lm_init(&deflate_flags);
+
+       put_8bit(deflate_flags);        /* extra flags */
+       put_8bit(3);    /* OS identifier = 3 (Unix) */
+
+       deflate();
+
+       /* Write the crc and uncompressed size */
+       put_32bit(~G1.crc);
+       put_32bit(G1.isize);
+
+       flush_outbuf();
+}
+
+
+/* ======================================================================== */
+static
+char* make_new_name_gzip(char *filename)
+{
+       return xasprintf("%s.gz", filename);
+}
+
+static
+USE_DESKTOP(long long) int pack_gzip(unpack_info_t *info UNUSED_PARAM)
+{
+       struct stat s;
+
+       /* Clear input and output buffers */
+       G1.outcnt = 0;
+#ifdef DEBUG
+       G1.insize = 0;
+#endif
+       G1.isize = 0;
+
+       /* Reinit G2.xxx */
+       memset(&G2, 0, sizeof(G2));
+       G2.l_desc.dyn_tree     = G2.dyn_ltree;
+       G2.l_desc.static_tree  = G2.static_ltree;
+       G2.l_desc.extra_bits   = extra_lbits;
+       G2.l_desc.extra_base   = LITERALS + 1;
+       G2.l_desc.elems        = L_CODES;
+       G2.l_desc.max_length   = MAX_BITS;
+       //G2.l_desc.max_code     = 0;
+       G2.d_desc.dyn_tree     = G2.dyn_dtree;
+       G2.d_desc.static_tree  = G2.static_dtree;
+       G2.d_desc.extra_bits   = extra_dbits;
+       //G2.d_desc.extra_base   = 0;
+       G2.d_desc.elems        = D_CODES;
+       G2.d_desc.max_length   = MAX_BITS;
+       //G2.d_desc.max_code     = 0;
+       G2.bl_desc.dyn_tree    = G2.bl_tree;
+       //G2.bl_desc.static_tree = NULL;
+       G2.bl_desc.extra_bits  = extra_blbits,
+       //G2.bl_desc.extra_base  = 0;
+       G2.bl_desc.elems       = BL_CODES;
+       G2.bl_desc.max_length  = MAX_BL_BITS;
+       //G2.bl_desc.max_code    = 0;
+
+       s.st_ctime = 0;
+       fstat(STDIN_FILENO, &s);
+       zip(s.st_ctime);
+       return 0;
+}
+
+/*
+ * Linux kernel build uses gzip -d -n. We accept and ignore it.
+ * Man page says:
+ * -n --no-name
+ * gzip: do not save the original file name and time stamp.
+ * (The original name is always saved if the name had to be truncated.)
+ * gunzip: do not restore the original file name/time even if present
+ * (remove only the gzip suffix from the compressed file name).
+ * This option is the default when decompressing.
+ * -N --name
+ * gzip: always save the original file name and time stamp (this is the default)
+ * gunzip: restore the original file name and time stamp if present.
+ */
+
+int gzip_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+#if ENABLE_GUNZIP
+int gzip_main(int argc, char **argv)
+#else
+int gzip_main(int argc UNUSED_PARAM, char **argv)
+#endif
+{
+       unsigned opt;
+
+       /* Must match bbunzip's constants OPT_STDOUT, OPT_FORCE! */
+       opt = getopt32(argv, "cfv" USE_GUNZIP("dt") "q123456789n");
+#if ENABLE_GUNZIP /* gunzip_main may not be visible... */
+       if (opt & 0x18) // -d and/or -t
+               return gunzip_main(argc, argv);
+#endif
+       option_mask32 &= 0x7; /* ignore -q, -0..9 */
+       //if (opt & 0x1) // -c
+       //if (opt & 0x2) // -f
+       //if (opt & 0x4) // -v
+       argv += optind;
+
+       SET_PTR_TO_GLOBALS(xzalloc(sizeof(struct globals) + sizeof(struct globals2))
+                       + sizeof(struct globals));
+       barrier();
+
+       /* Allocate all global buffers (for DYN_ALLOC option) */
+       ALLOC(uch, G1.l_buf, INBUFSIZ);
+       ALLOC(uch, G1.outbuf, OUTBUFSIZ);
+       ALLOC(ush, G1.d_buf, DIST_BUFSIZE);
+       ALLOC(uch, G1.window, 2L * WSIZE);
+       ALLOC(ush, G1.prev, 1L << BITS);
+
+       /* Initialise the CRC32 table */
+       G1.crc_32_tab = crc32_filltable(NULL, 0);
+
+       return bbunpack(argv, make_new_name_gzip, pack_gzip);
+}
diff --git a/archival/libunarchive/Kbuild b/archival/libunarchive/Kbuild
new file mode 100644 (file)
index 0000000..364f917
--- /dev/null
@@ -0,0 +1,51 @@
+# Makefile for busybox
+#
+# Copyright (C) 1999-2004 by Erik Andersen <andersen@codepoet.org>
+#
+# 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 \
+\
+       seek_by_read.o \
+       seek_by_jump.o \
+\
+       data_align.o \
+       find_list_entry.o \
+       init_handle.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_GUNZIP)                    += decompress_unzip.o
+lib-$(CONFIG_RPM2CPIO)                  += decompress_unzip.o get_header_cpio.o
+lib-$(CONFIG_RPM)                       += open_transformer.o decompress_unzip.o get_header_cpio.o
+lib-$(CONFIG_TAR)                       += get_header_tar.o
+lib-$(CONFIG_UNCOMPRESS)                += decompress_uncompress.o
+lib-$(CONFIG_UNZIP)                     += decompress_unzip.o
+lib-$(CONFIG_FEATURE_SEAMLESS_Z)        += open_transformer.o decompress_uncompress.o
+lib-$(CONFIG_FEATURE_SEAMLESS_GZ)       += open_transformer.o decompress_unzip.o get_header_tar_gz.o
+lib-$(CONFIG_FEATURE_SEAMLESS_BZ2)      += open_transformer.o decompress_bunzip2.o get_header_tar_bz2.o
+lib-$(CONFIG_FEATURE_SEAMLESS_LZMA)     += open_transformer.o decompress_unlzma.o get_header_tar_lzma.o
+lib-$(CONFIG_FEATURE_COMPRESS_USAGE)    += decompress_bunzip2.o
diff --git a/archival/libunarchive/data_align.c b/archival/libunarchive/data_align.c
new file mode 100644 (file)
index 0000000..9f2e843
--- /dev/null
@@ -0,0 +1,15 @@
+/* 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 FAST_FUNC data_align(archive_handle_t *archive_handle, unsigned boundary)
+{
+       unsigned skip_amount = (boundary - (archive_handle->offset % boundary)) % boundary;
+
+       archive_handle->seek(archive_handle, skip_amount);
+       archive_handle->offset += skip_amount;
+}
diff --git a/archival/libunarchive/data_extract_all.c b/archival/libunarchive/data_extract_all.c
new file mode 100644 (file)
index 0000000..8b1ee2a
--- /dev/null
@@ -0,0 +1,148 @@
+/* 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 FAST_FUNC 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->ah_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->ah_flags & ARCHIVE_EXTRACT_UNCONDITIONAL) {
+               /* Remove the 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 %s",
+                                       file_header->name);
+               }
+       }
+       else if (archive_handle->ah_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->ah_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_target)
+        && (file_header->size == 0)
+       ) {
+               /* hard link */
+               res = link(file_header->link_target, file_header->name);
+               if ((res == -1) && !(archive_handle->ah_flags & ARCHIVE_EXTRACT_QUIET)) {
+                       bb_perror_msg("cannot create %slink "
+                                       "from %s to %s", "hard",
+                                       file_header->name,
+                                       file_header->link_target);
+               }
+       } 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_exact_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 ((res == -1)
+                        && (errno != EISDIR) /* btw, Linux doesn't return this */
+                        && (errno != EEXIST)
+                        && !(archive_handle->ah_flags & ARCHIVE_EXTRACT_QUIET)
+                       ) {
+                               bb_perror_msg("cannot make dir %s", file_header->name);
+                       }
+                       break;
+               case S_IFLNK:
+                       /* Symlink */
+                       res = symlink(file_header->link_target, file_header->name);
+                       if ((res == -1)
+                        && !(archive_handle->ah_flags & ARCHIVE_EXTRACT_QUIET)
+                       ) {
+                               bb_perror_msg("cannot create %slink "
+                                       "from %s to %s", "sym",
+                                       file_header->name,
+                                       file_header->link_target);
+                       }
+                       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->ah_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->ah_flags & ARCHIVE_NOPRESERVE_OWN)) {
+#if ENABLE_FEATURE_TAR_UNAME_GNAME
+               uid_t uid = file_header->uid;
+               gid_t gid = file_header->gid;
+
+               if (file_header->uname) {
+                       struct passwd *pwd = getpwnam(file_header->uname);
+                       if (pwd) uid = pwd->pw_uid;
+               }
+               if (file_header->gname) {
+                       struct group *grp = getgrnam(file_header->gname);
+                       if (grp) gid = grp->gr_gid;
+               }
+               lchown(file_header->name, uid, gid);
+#else
+               lchown(file_header->name, file_header->uid, file_header->gid);
+#endif
+       }
+       if ((file_header->mode & S_IFMT) != S_IFLNK) {
+               /* uclibc has no lchmod, glibc is even stranger -
+                * it has lchmod which seems to do nothing!
+                * so we use chmod... */
+               if (!(archive_handle->ah_flags & ARCHIVE_NOPRESERVE_PERM)) {
+                       chmod(file_header->name, file_header->mode);
+               }
+               /* same for utime */
+               if (archive_handle->ah_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 (file)
index 0000000..1d74e03
--- /dev/null
@@ -0,0 +1,17 @@
+/* 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 FAST_FUNC data_extract_to_buffer(archive_handle_t *archive_handle)
+{
+       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 (file)
index 0000000..a3efea1
--- /dev/null
@@ -0,0 +1,14 @@
+/* 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 FAST_FUNC data_extract_to_stdout(archive_handle_t *archive_handle)
+{
+       bb_copyfd_exact_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 (file)
index 0000000..438750f
--- /dev/null
@@ -0,0 +1,12 @@
+/* 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 FAST_FUNC 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 (file)
index 0000000..b53720f
--- /dev/null
@@ -0,0 +1,724 @@
+/* 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_SHORT_WRITE              (-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 sentinel 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
+ * Found the most used member:
+ *  cat this_file.c | sed -e 's/"/ /g' -e "s/'/ /g" | xargs -n1 \
+ *  | grep 'bd->' | sed 's/^.*bd->/bd->/' | sort | $PAGER
+ * and moved it (inbufBitCount) to offset 0.
+ */
+struct bunzip_data {
+       /* I/O tracking data (file handles, buffers, positions, etc.) */
+       unsigned inbufBitCount, inbufBits;
+       int in_fd, out_fd, inbufCount, inbufPos /*, outbufPos*/;
+       unsigned char *inbuf /*,*outbuf*/;
+
+       /* State for interrupting output loop */
+       int writeCopies, writePos, writeRunCountdown, writeCount, writeCurrent;
+
+       /* The CRC values stored in the block header and calculated from the data */
+       uint32_t headerCRC, totalCRC, writeCRC;
+
+       /* Intermediate buffer and its size (in bytes) */
+       unsigned *dbuf, dbufSize;
+
+       /* For I/O error handling */
+       jmp_buf jmpbuf;
+
+       /* Big things go last (register-relative addressing can be larger for big offsets) */
+       uint32_t crc32Table[256];
+       unsigned char selectors[32768];                 /* nSelectors=15 bits */
+       struct group_data groups[MAX_GROUPS];   /* Huffman coding tables */
+};
+/* typedef struct bunzip_data bunzip_data; -- done in .h file */
+
+
+/* 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 get_bits(bunzip_data *bd, int bits_wanted)
+{
+       unsigned 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 ((int)(bd->inbufBitCount) < bits_wanted) {
+
+               /* If we need to read more data from file into byte buffer, do so */
+               if (bd->inbufPos == bd->inbufCount) {
+                       /* if "no input fd" case: in_fd == -1, read fails, we jump */
+                       bd->inbufCount = read(bd->in_fd, bd->inbuf, IOBUF_SIZE);
+                       if (bd->inbufCount <= 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 << bd->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 << bits_wanted) - 1);
+
+       return bits;
+}
+
+/* Unpacks the next block and sets up for the inverse burrows-wheeler step. */
+static int get_next_block(bunzip_data *bd)
+{
+       struct group_data *hufGroup;
+       int dbufCount, nextSym, dbufSize, groupCount, *base, *limit, selector,
+               i, j, k, t, runPos, symCount, symTotal, nSelectors, byteCount[256];
+       unsigned char uc, symToByte[256], mtfSymbol[256], *selectors;
+       unsigned *dbuf, origPtr;
+
+       dbuf = bd->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;
+       origPtr = get_bits(bd, 24);
+       if ((int)origPtr > 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.) */
+       nSelectors = get_bits(bd, 15);
+       if (!nSelectors) return RETVAL_DATA_ERROR;
+       for (i = 0; i < groupCount; i++) mtfSymbol[i] = i;
+       for (i = 0; i < nSelectors; i++) {
+
+               /* Get next value */
+               for (j = 0; get_bits(bd, 1); j++)
+                       if (j >= 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 < groupCount; j++) {
+               unsigned char length[MAX_SYMBOLS];
+               /* 8 bits is ALMOST enough for temp[], see below */
+               unsigned temp[MAX_HUFCODE_BITS+1];
+               int minLen, maxLen, pp;
+
+               /* Read Huffman code lengths for each symbol.  They're stored in
+                  a way similar to mtf; record a starting value for the first symbol,
+                  and an offset from the previous value for everys symbol after that.
+                  (Subtracting 1 before the loop and then adding it back at the end is
+                  an optimization that makes the test inside the loop simpler: symbol
+                  length 0 becomes negative, so an unsigned inequality catches it.) */
+               t = get_bits(bd, 5) - 1;
+               for (i = 0; i < symCount; i++) {
+                       for (;;) {
+                               if ((unsigned)t > (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; t < symCount; t++)
+                               if (length[t] == i)
+                                       hufGroup->permute[pp++] = t;
+               }
+
+               /* Count symbols coded for at each bit length */
+               /* NB: in pathological cases, temp[8] can end ip being 256.
+                * That's why uint8_t is too small for temp[]. */
+               for (i = 0; i < symCount; i++) temp[length[i]]++;
+
+               /* Calculate limit[] (the largest symbol-coding value at each bit
+                * length, which is (previous limit<<1)+symbols at this level), and
+                * base[] (number of symbols to ignore at each bit length, which is
+                * limit minus the cumulative count of symbols coded for already). */
+               pp = t = 0;
+               for (i = minLen; i < maxLen; i++) {
+                       pp += temp[i];
+
+                       /* We read the largest possible symbol size and then unget bits
+                          after determining how many we need, and those extra bits could
+                          be set to anything.  (They're noise from future symbols.)  At
+                          each level we're really only interested in the first few bits,
+                          so here we set all the trailing to-be-ignored bits to 1 so they
+                          don't affect the value>limit[length] comparison. */
+                       limit[i] = (pp << (maxLen - i)) - 1;
+                       pp <<= 1;
+                       t += temp[i];
+                       base[i+1] = pp - t;
+               }
+               limit[maxLen+1] = INT_MAX; /* Sentinel 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 */
+       memset(byteCount, 0, sizeof(byteCount)); /* smaller, maybe slower? */
+       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 ((int)(bd->inbufBitCount) < hufGroup->maxLen) {
+                       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 << hufGroup->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)
+                       return RETVAL_DATA_ERROR;
+               j = (j >> (hufGroup->maxLen - i)) - base[i];
+               if ((unsigned)j >= 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)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; i++) {
+               uc = (unsigned char)(dbuf[i] & 0xff);
+               dbuf[byteCount[uc]] |= (i << 8);
+               byteCount[uc]++;
+       }
+
+       /* Decode first byte by hand to initialize "previous" byte.  Note that it
+          doesn't get output, and if the first three characters are identical
+          it doesn't qualify as a run (hence writeRunCountdown=5). */
+       if (dbufCount) {
+               if ((int)origPtr >= 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.
+*/
+int FAST_FUNC read_bunzip(bunzip_data *bd, char *outbuf, int len)
+{
+       const unsigned *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. */
+
+/* Because bunzip2 is used for help text unpacking, and because bb_show_usage()
+   should work for NOFORK applets too, we must be extremely careful to not leak
+   any allocations! */
+int FAST_FUNC start_bunzip(bunzip_data **bdp, int in_fd, const unsigned char *inbuf,
+                                               int len)
+{
+       bunzip_data *bd;
+       unsigned i;
+       enum {
+               BZh0 = ('B' << 24) + ('Z' << 16) + ('h' << 8) + '0',
+               h0 = ('h' << 8) + '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 */
+       bd->in_fd = in_fd;
+       if (-1 == in_fd) {
+               /* in this case, bd->inbuf is read-only */
+               bd->inbuf = (void*)inbuf; /* cast away const-ness */
+               bd->inbufCount = len;
+       } else
+               bd->inbuf = (unsigned char *)(bd + 1);
+
+       /* Init the CRC32 table (big endian) */
+       crc32_filltable(bd->crc32Table, 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']." */
+       /* Update: now caller verifies 1st two bytes, makes .gz/.bz2
+        * integration easier */
+       /* was: */
+       /* i = get_bits(bd, 32); */
+       /* if ((unsigned)(i - BZh0 - 1) >= 9) return RETVAL_NOT_BZIP_DATA; */
+       i = get_bits(bd, 16);
+       if ((unsigned)(i - h0 - 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->dbufSize = 100000 * (i - h0);
+
+       /* Cannot use xmalloc - may leak bd in NOFORK case! */
+       bd->dbuf = malloc_or_warn(bd->dbufSize * sizeof(int));
+       if (!bd->dbuf) {
+               free(bd);
+               xfunc_die();
+       }
+       return RETVAL_OK;
+}
+
+void FAST_FUNC dealloc_bunzip(bunzip_data *bd)
+{
+       free(bd->dbuf);
+       free(bd);
+}
+
+
+/* Decompress src_fd to dst_fd.  Stops at end of bzip data, not end of file. */
+USE_DESKTOP(long long) int FAST_FUNC
+unpack_bz2_stream(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, NULL, 0);
+       if (!i) {
+               for (;;) {
+                       i = read_bunzip(bd, outbuf, IOBUF_SIZE);
+                       if (i <= 0) break;
+                       if (i != full_write(dst_fd, outbuf, i)) {
+                               i = RETVAL_SHORT_WRITE;
+                               break;
+                       }
+                       USE_DESKTOP(total_written += i;)
+               }
+       }
+
+       /* Check CRC and release memory */
+
+       if (i == RETVAL_LAST_BLOCK) {
+               if (bd->headerCRC != bd->totalCRC) {
+                       bb_error_msg("CRC error");
+               } else {
+                       i = RETVAL_OK;
+               }
+       } else if (i == RETVAL_SHORT_WRITE) {
+               bb_error_msg("short write");
+       } else {
+               bb_error_msg("bunzip error %d", i);
+       }
+       dealloc_bunzip(bd);
+       free(outbuf);
+
+       return i ? i : USE_DESKTOP(total_written) + 0;
+}
+
+USE_DESKTOP(long long) int FAST_FUNC
+unpack_bz2_stream_prime(int src_fd, int dst_fd)
+{
+       unsigned char magic[2];
+       xread(src_fd, magic, 2);
+       if (magic[0] != 'B' || magic[1] != 'Z') {
+               bb_error_msg_and_die("invalid magic");
+       }
+       return unpack_bz2_stream(src_fd, dst_fd);
+}
+
+#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;
+       char c;
+
+       int i = unpack_bz2_stream_prime(0, 1);
+       if (i < 0)
+               fprintf(stderr, "%s\n", bunzip_errors[-i]);
+       else if (read(STDIN_FILENO, &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 (file)
index 0000000..fe1491e
--- /dev/null
@@ -0,0 +1,307 @@
+/* vi: set sw=4 ts=4: */
+/* 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 "libbb.h"
+#include "unarchive.h"
+
+
+/* Default input buffer size */
+#define        IBUFSIZ 2048
+
+/* Default output buffer size */
+#define        OBUFSIZ 2048
+
+/* Defines for third byte of header */
+#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 compression 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 HBITS      17   /* 50% occupancy */
+#define HSIZE      (1<<HBITS)
+#define HMASK      (HSIZE-1)    /* unused */
+#define HPRIME     9941         /* unused */
+#define BITS       16
+#define BITS_STR   "16"
+#undef  MAXSEG_64K              /* unused */
+#define MAXCODE(n) (1L << (n))
+
+#define htabof(i)               htab[i]
+#define codetabof(i)            codetab[i]
+#define tab_prefixof(i)         codetabof(i)
+#define tab_suffixof(i)         ((unsigned char *)(htab))[i]
+#define de_stack                ((unsigned char *)&(htab[HSIZE-1]))
+#define clear_tab_prefixof()    memset(codetab, 0, 256)
+
+/*
+ * Decompress stdin to stdout.  This routine adapts to the codes in the
+ * file building the "string" table on-the-fly; requiring no table to
+ * be stored in the compressed file.
+ */
+
+USE_DESKTOP(long long) int FAST_FUNC
+unpack_Z_stream(int fd_in, int fd_out)
+{
+       USE_DESKTOP(long long total_written = 0;)
+       USE_DESKTOP(long long) int retval = -1;
+       unsigned char *stackp;
+       long code;
+       int finchar;
+       long oldcode;
+       long incode;
+       int inbits;
+       int posbits;
+       int outpos;
+       int insize;
+       int bitmask;
+       long free_ent;
+       long maxcode;
+       long maxmaxcode;
+       int n_bits;
+       int rsize = 0;
+       unsigned char *inbuf; /* were eating insane amounts of stack - */
+       unsigned char *outbuf; /* bad for some embedded targets */
+       unsigned char *htab;
+       unsigned short *codetab;
+
+       /* Hmm, these were statics - why?! */
+       /* user settable max # bits/code */
+       int maxbits; /* = BITS; */
+       /* block compress mode -C compatible with 2.0 */
+       int block_mode; /* = BLOCK_MODE; */
+
+       inbuf = xzalloc(IBUFSIZ + 64);
+       outbuf = xzalloc(OBUFSIZ + 2048);
+       htab = xzalloc(HSIZE);  /* wsn't zeroed out before, maybe can xmalloc? */
+       codetab = xzalloc(HSIZE * sizeof(codetab[0]));
+
+       insize = 0;
+
+       /* xread isn't good here, we have to return - caller may want
+        * to do some cleanup (e.g. delete incomplete unpacked file etc) */
+       if (full_read(fd_in, inbuf, 1) != 1) {
+               bb_error_msg("short read");
+               goto err;
+       }
+
+       maxbits = inbuf[0] & BIT_MASK;
+       block_mode = inbuf[0] & BLOCK_MODE;
+       maxmaxcode = MAXCODE(maxbits);
+
+       if (maxbits > BITS) {
+               bb_error_msg("compressed with %d bits, can only handle "
+                               BITS_STR" bits", maxbits);
+               goto err;
+       }
+
+       n_bits = INIT_BITS;
+       maxcode = MAXCODE(INIT_BITS) - 1;
+       bitmask = (1 << INIT_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(); - done by xzalloc */
+
+       for (code = 255; code >= 0; --code) {
+               tab_suffixof(code) = (unsigned char) code;
+       }
+
+       do {
+ resetbuf:
+               {
+                       int i;
+                       int e;
+                       int o;
+
+                       o = posbits >> 3;
+                       e = insize - o;
+
+                       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);
+//error check??
+                       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)));
+                               n_bits = INIT_BITS;
+                               maxcode = MAXCODE(INIT_BITS) - 1;
+                               bitmask = (1 << INIT_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");
+                                       goto err;
+                               }
+
+                               *--stackp = (unsigned char) finchar;
+                               code = oldcode;
+                       }
+
+                       /* Generate output characters in reverse order */
+                       while ((long) code >= (long) 256) {
+                               *--stackp = tab_suffixof(code);
+                               code = tab_prefixof(code);
+                       }
+
+                       finchar = tab_suffixof(code);
+                       *--stackp = (unsigned char) finchar;
+
+                       /* And put them out in forward order */
+                       {
+                               int i;
+
+                               i = de_stack - stackp;
+                               if (outpos + i >= OBUFSIZ) {
+                                       do {
+                                               if (i > OBUFSIZ - outpos) {
+                                                       i = OBUFSIZ - outpos;
+                                               }
+
+                                               if (i > 0) {
+                                                       memcpy(outbuf + outpos, stackp, i);
+                                                       outpos += i;
+                                               }
+
+                                               if (outpos >= OBUFSIZ) {
+                                                       full_write(fd_out, outbuf, outpos);
+//error check??
+                                                       USE_DESKTOP(total_written += outpos;)
+                                                       outpos = 0;
+                                               }
+                                               stackp += i;
+                                               i = de_stack - stackp;
+                                       } while (i > 0);
+                               } else {
+                                       memcpy(outbuf + outpos, stackp, i);
+                                       outpos += i;
+                               }
+                       }
+
+                       /* Generate the new entry. */
+                       code = free_ent;
+                       if (code < 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) {
+               full_write(fd_out, outbuf, outpos);
+//error check??
+               USE_DESKTOP(total_written += outpos;)
+       }
+
+       retval = USE_DESKTOP(total_written) + 0;
+ err:
+       free(inbuf);
+       free(outbuf);
+       free(htab);
+       free(codetab);
+       return retval;
+}
diff --git a/archival/libunarchive/decompress_unlzma.c b/archival/libunarchive/decompress_unlzma.c
new file mode 100644 (file)
index 0000000..2cfcd9b
--- /dev/null
@@ -0,0 +1,503 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Small lzma deflate implementation.
+ * Copyright (C) 2006  Aurelien Jacobs <aurel@gnuage.org>
+ *
+ * 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"
+
+#if ENABLE_FEATURE_LZMA_FAST
+#  define speed_inline ALWAYS_INLINE
+#else
+#  define speed_inline
+#endif
+
+
+typedef struct {
+       int fd;
+       uint8_t *ptr;
+
+/* Was keeping rc on stack in unlzma and separately allocating buffer,
+ * but with "buffer 'attached to' allocated rc" code is smaller: */
+       /* uint8_t *buffer; */
+#define RC_BUFFER ((uint8_t*)(rc+1))
+
+       uint8_t *buffer_end;
+
+/* Had provisions for variable buffer, but we don't need it here */
+       /* int buffer_size; */
+#define RC_BUFFER_SIZE 0x10000
+
+       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)
+{
+       int buffer_size = safe_read(rc->fd, RC_BUFFER, RC_BUFFER_SIZE);
+       if (buffer_size <= 0)
+               bb_error_msg_and_die("unexpected EOF");
+       rc->ptr = RC_BUFFER;
+       rc->buffer_end = RC_BUFFER + buffer_size;
+}
+
+/* Called once */
+static rc_t* rc_init(int fd) /*, int buffer_size) */
+{
+       int i;
+       rc_t *rc;
+
+       rc = xmalloc(sizeof(*rc) + RC_BUFFER_SIZE);
+
+       rc->fd = fd;
+       /* 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++;
+       }
+       return rc;
+}
+
+/* Called once  */
+static ALWAYS_INLINE void rc_free(rc_t *rc)
+{
+       free(rc);
+}
+
+/* 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 ALWAYS_INLINE void rc_normalize(rc_t *rc)
+{
+       if (rc->range < (1 << RC_TOP_BITS)) {
+               rc_do_normalize(rc);
+       }
+}
+
+/* rc_is_bit_0 is called 9 times */
+/* Why rc_is_bit_0_helper exists?
+ * Because we want to always expose (rc->code < rc->bound) to optimizer.
+ * Thus rc_is_bit_0 is always inlined, and rc_is_bit_0_helper is inlined
+ * only if we compile for speed.
+ */
+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 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 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;
+
+
+/* #defines will force compiler to compute/optimize each one with each usage.
+ * Have heart and use enum instead. */
+enum {
+       LZMA_BASE_SIZE = 1846,
+       LZMA_LIT_SIZE  = 768,
+
+       LZMA_NUM_POS_BITS_MAX = 4,
+
+       LZMA_LEN_NUM_LOW_BITS  = 3,
+       LZMA_LEN_NUM_MID_BITS  = 3,
+       LZMA_LEN_NUM_HIGH_BITS = 8,
+
+       LZMA_LEN_CHOICE     = 0,
+       LZMA_LEN_CHOICE_2   = (LZMA_LEN_CHOICE + 1),
+       LZMA_LEN_LOW        = (LZMA_LEN_CHOICE_2 + 1),
+       LZMA_LEN_MID        = (LZMA_LEN_LOW \
+                             + (1 << (LZMA_NUM_POS_BITS_MAX + LZMA_LEN_NUM_LOW_BITS))),
+       LZMA_LEN_HIGH       = (LZMA_LEN_MID \
+                             + (1 << (LZMA_NUM_POS_BITS_MAX + LZMA_LEN_NUM_MID_BITS))),
+       LZMA_NUM_LEN_PROBS  = (LZMA_LEN_HIGH + (1 << LZMA_LEN_NUM_HIGH_BITS)),
+
+       LZMA_NUM_STATES     = 12,
+       LZMA_NUM_LIT_STATES = 7,
+
+       LZMA_START_POS_MODEL_INDEX = 4,
+       LZMA_END_POS_MODEL_INDEX   = 14,
+       LZMA_NUM_FULL_DISTANCES    = (1 << (LZMA_END_POS_MODEL_INDEX >> 1)),
+
+       LZMA_NUM_POS_SLOT_BITS = 6,
+       LZMA_NUM_LEN_TO_POS_STATES = 4,
+
+       LZMA_NUM_ALIGN_BITS = 4,
+
+       LZMA_MATCH_MIN_LEN  = 2,
+
+       LZMA_IS_MATCH       = 0,
+       LZMA_IS_REP         = (LZMA_IS_MATCH + (LZMA_NUM_STATES << LZMA_NUM_POS_BITS_MAX)),
+       LZMA_IS_REP_G0      = (LZMA_IS_REP + LZMA_NUM_STATES),
+       LZMA_IS_REP_G1      = (LZMA_IS_REP_G0 + LZMA_NUM_STATES),
+       LZMA_IS_REP_G2      = (LZMA_IS_REP_G1 + LZMA_NUM_STATES),
+       LZMA_IS_REP_0_LONG  = (LZMA_IS_REP_G2 + LZMA_NUM_STATES),
+       LZMA_POS_SLOT       = (LZMA_IS_REP_0_LONG \
+                             + (LZMA_NUM_STATES << LZMA_NUM_POS_BITS_MAX)),
+       LZMA_SPEC_POS       = (LZMA_POS_SLOT \
+                             + (LZMA_NUM_LEN_TO_POS_STATES << LZMA_NUM_POS_SLOT_BITS)),
+       LZMA_ALIGN          = (LZMA_SPEC_POS \
+                             + LZMA_NUM_FULL_DISTANCES - LZMA_END_POS_MODEL_INDEX),
+       LZMA_LEN_CODER      = (LZMA_ALIGN + (1 << LZMA_NUM_ALIGN_BITS)),
+       LZMA_REP_LEN_CODER  = (LZMA_LEN_CODER + LZMA_NUM_LEN_PROBS),
+       LZMA_LITERAL        = (LZMA_REP_LEN_CODER + LZMA_NUM_LEN_PROBS),
+};
+
+
+USE_DESKTOP(long long) int FAST_FUNC
+unpack_lzma_stream(int src_fd, int dst_fd)
+{
+       USE_DESKTOP(long long total_written = 0;)
+       lzma_header_t header;
+       int lc, pb, lp;
+       uint32_t pos_state_mask;
+       uint32_t literal_pos_mask;
+       uint32_t pos;
+       uint16_t *p;
+       uint16_t *prob;
+       uint16_t *prob_lit;
+       int num_bits;
+       int num_probs;
+       rc_t *rc;
+       int i, mi;
+       uint8_t *buffer;
+       uint8_t previous_byte = 0;
+       size_t buffer_pos = 0, global_pos = 0;
+       int len = 0;
+       int state = 0;
+       uint32_t rep0 = 1, rep1 = 1, rep2 = 1, rep3 = 1;
+
+       xread(src_fd, &header, sizeof(header));
+
+       if (header.pos >= (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 = rc_init(src_fd); /*, RC_BUFFER_SIZE); */
+
+       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;
+                                       bit ^= (rc_get_bit(rc, prob_lit, &mi) << 8); /* 0x100 or 0 */
+                                       if (bit)
+                                               break;
+                               } while (mi < 0x100);
+                       }
+                       while (mi < 0x100) {
+                               prob_lit = prob + mi;
+                               rc_get_bit(rc, prob_lit, &mi);
+                       }
+
+                       state -= 3;
+                       if (state < 4-3)
+                               state = 0;
+                       if (state >= 10-3)
+                               state -= 6-3;
+
+                       previous_byte = (uint8_t) mi;
+#if ENABLE_FEATURE_LZMA_FAST
+ one_byte1:
+                       buffer[buffer_pos++] = previous_byte;
+                       if (buffer_pos == header.dict_size) {
+                               buffer_pos = 0;
+                               global_pos += header.dict_size;
+                               if (full_write(dst_fd, buffer, header.dict_size) != (ssize_t)header.dict_size)
+                                       goto bad;
+                               USE_DESKTOP(total_written += header.dict_size;)
+                       }
+#else
+                       len = 1;
+                       goto one_byte2;
+#endif
+               } 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;
+#if ENABLE_FEATURE_LZMA_FAST
+                                               pos = buffer_pos - rep0;
+                                               while (pos >= header.dict_size)
+                                                       pos += header.dict_size;
+                                               previous_byte = buffer[pos];
+                                               goto one_byte1;
+#else
+                                               len = 1;
+                                               goto string;
+#endif
+                                       } 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;
+ SKIP_FEATURE_LZMA_FAST(string:)
+                       do {
+                               pos = buffer_pos - rep0;
+                               while (pos >= header.dict_size)
+                                       pos += header.dict_size;
+                               previous_byte = buffer[pos];
+ SKIP_FEATURE_LZMA_FAST(one_byte2:)
+                               buffer[buffer_pos++] = previous_byte;
+                               if (buffer_pos == header.dict_size) {
+                                       buffer_pos = 0;
+                                       global_pos += header.dict_size;
+                                       if (full_write(dst_fd, buffer, header.dict_size) != (ssize_t)header.dict_size)
+                                               goto bad;
+                                       USE_DESKTOP(total_written += header.dict_size;)
+                               }
+                               len--;
+                       } while (len != 0 && buffer_pos < header.dst_size);
+               }
+       }
+
+       {
+               SKIP_DESKTOP(int total_written = 0; /* success */)
+               USE_DESKTOP(total_written += buffer_pos;)
+               if (full_write(dst_fd, buffer, buffer_pos) != (ssize_t)buffer_pos) {
+ bad:
+                       total_written = -1; /* failure */
+               }
+               rc_free(rc);
+               free(p);
+               free(buffer);
+               return total_written;
+       }
+}
diff --git a/archival/libunarchive/decompress_unzip.c b/archival/libunarchive/decompress_unzip.c
new file mode 100644 (file)
index 0000000..8696925
--- /dev/null
@@ -0,0 +1,1252 @@
+/* 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 <sr1@inf.tu-dresden.de>
+ * based on gzip sources
+ *
+ * Adjusted further by Erik Andersen <andersen@codepoet.org> 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 <setjmp.h>
+#include "libbb.h"
+#include "unarchive.h"
+
+typedef struct huft_t {
+       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_t *t;       /* pointer to next level of table */
+       } v;
+} huft_t;
+
+enum {
+       /* gunzip_window size--must be a power of two, and
+        * at least 32K for zip's deflate method */
+       GUNZIP_WSIZE = 0x8000,
+       /* If BMAX needs to be larger than 16, then h and x[] should be ulg. */
+       BMAX = 16,      /* maximum bit length of any code (16 for explode) */
+       N_MAX = 288,    /* maximum number of codes in any set */
+};
+
+
+/* This is somewhat complex-looking arrangement, but it allows
+ * to place decompressor state either in bss or in
+ * malloc'ed space simply by changing #defines below.
+ * Sizes on i386:
+ * text    data     bss     dec     hex
+ * 5256       0     108    5364    14f4 - bss
+ * 4915       0       0    4915    1333 - malloc
+ */
+#define STATE_IN_BSS 0
+#define STATE_IN_MALLOC 1
+
+
+typedef struct state_t {
+       off_t gunzip_bytes_out; /* number of output bytes */
+       uint32_t gunzip_crc;
+
+       int gunzip_src_fd;
+       unsigned gunzip_outbuf_count; /* bytes in output buffer */
+
+       unsigned char *gunzip_window;
+
+       uint32_t *gunzip_crc_table;
+
+       /* bitbuffer */
+       unsigned gunzip_bb; /* bit buffer */
+       unsigned char gunzip_bk; /* bits in bit buffer */
+
+       /* input (compressed) data */
+       unsigned char *bytebuffer;      /* buffer itself */
+       off_t to_read;                  /* compressed bytes to read (unzip only, -1 for gunzip) */
+//     unsigned bytebuffer_max;        /* buffer size */
+       unsigned bytebuffer_offset;     /* buffer position */
+       unsigned bytebuffer_size;       /* how much data is there (size <= max) */
+
+       /* private data of inflate_codes() */
+       unsigned inflate_codes_ml; /* masks for bl and bd bits */
+       unsigned inflate_codes_md; /* masks for bl and bd bits */
+       unsigned inflate_codes_bb; /* bit buffer */
+       unsigned inflate_codes_k; /* number of bits in bit buffer */
+       unsigned inflate_codes_w; /* current gunzip_window position */
+       huft_t *inflate_codes_tl;
+       huft_t *inflate_codes_td;
+       unsigned inflate_codes_bl;
+       unsigned inflate_codes_bd;
+       unsigned inflate_codes_nn; /* length and index for copy */
+       unsigned inflate_codes_dd;
+
+       smallint resume_copy;
+
+       /* private data of inflate_get_next_window() */
+       smallint method; /* method == -1 for stored, -2 for codes */
+       smallint need_another_block;
+       smallint end_reached;
+
+       /* private data of inflate_stored() */
+       unsigned inflate_stored_n;
+       unsigned inflate_stored_b;
+       unsigned inflate_stored_k;
+       unsigned inflate_stored_w;
+
+       const char *error_msg;
+       jmp_buf error_jmp;
+} state_t;
+#define gunzip_bytes_out    (S()gunzip_bytes_out   )
+#define gunzip_crc          (S()gunzip_crc         )
+#define gunzip_src_fd       (S()gunzip_src_fd      )
+#define gunzip_outbuf_count (S()gunzip_outbuf_count)
+#define gunzip_window       (S()gunzip_window      )
+#define gunzip_crc_table    (S()gunzip_crc_table   )
+#define gunzip_bb           (S()gunzip_bb          )
+#define gunzip_bk           (S()gunzip_bk          )
+#define to_read             (S()to_read            )
+// #define bytebuffer_max   (S()bytebuffer_max     )
+// Both gunzip and unzip can use constant buffer size now (16k):
+#define bytebuffer_max      0x4000
+#define bytebuffer          (S()bytebuffer         )
+#define bytebuffer_offset   (S()bytebuffer_offset  )
+#define bytebuffer_size     (S()bytebuffer_size    )
+#define inflate_codes_ml    (S()inflate_codes_ml   )
+#define inflate_codes_md    (S()inflate_codes_md   )
+#define inflate_codes_bb    (S()inflate_codes_bb   )
+#define inflate_codes_k     (S()inflate_codes_k    )
+#define inflate_codes_w     (S()inflate_codes_w    )
+#define inflate_codes_tl    (S()inflate_codes_tl   )
+#define inflate_codes_td    (S()inflate_codes_td   )
+#define inflate_codes_bl    (S()inflate_codes_bl   )
+#define inflate_codes_bd    (S()inflate_codes_bd   )
+#define inflate_codes_nn    (S()inflate_codes_nn   )
+#define inflate_codes_dd    (S()inflate_codes_dd   )
+#define resume_copy         (S()resume_copy        )
+#define method              (S()method             )
+#define need_another_block  (S()need_another_block )
+#define end_reached         (S()end_reached        )
+#define inflate_stored_n    (S()inflate_stored_n   )
+#define inflate_stored_b    (S()inflate_stored_b   )
+#define inflate_stored_k    (S()inflate_stored_k   )
+#define inflate_stored_w    (S()inflate_stored_w   )
+#define error_msg           (S()error_msg          )
+#define error_jmp           (S()error_jmp          )
+
+/* This is a generic part */
+#if STATE_IN_BSS /* Use global data segment */
+#define DECLARE_STATE /*nothing*/
+#define ALLOC_STATE /*nothing*/
+#define DEALLOC_STATE ((void)0)
+#define S() state.
+#define PASS_STATE /*nothing*/
+#define PASS_STATE_ONLY /*nothing*/
+#define STATE_PARAM /*nothing*/
+#define STATE_PARAM_ONLY void
+static state_t state;
+#endif
+
+#if STATE_IN_MALLOC /* Use malloc space */
+#define DECLARE_STATE state_t *state
+#define ALLOC_STATE (state = xzalloc(sizeof(*state)))
+#define DEALLOC_STATE free(state)
+#define S() state->
+#define PASS_STATE state,
+#define PASS_STATE_ONLY state
+#define STATE_PARAM state_t *state,
+#define STATE_PARAM_ONLY state_t *state
+#endif
+
+
+static const uint16_t mask_bits[] ALIGN2 = {
+       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 uint16_t cplens[] ALIGN2 = {
+       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 uint8_t cplext[] ALIGN1 = {
+       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 uint16_t cpdist[] ALIGN2 = {
+       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 uint8_t cpdext[] ALIGN1 = {
+       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 uint8_t border[] ALIGN1 = {
+       16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15
+};
+
+
+/*
+ * 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 void huft_free(huft_t *p)
+{
+       huft_t *q;
+
+       /* Go through linked list, freeing from the malloced (t[-1]) address. */
+       while (p) {
+               q = (--p)->v.t;
+               free(p);
+               p = q;
+       }
+}
+
+static void huft_free_all(STATE_PARAM_ONLY)
+{
+       huft_free(inflate_codes_tl);
+       huft_free(inflate_codes_td);
+       inflate_codes_tl = NULL;
+       inflate_codes_td = NULL;
+}
+
+static void abort_unzip(STATE_PARAM_ONLY) NORETURN;
+static void abort_unzip(STATE_PARAM_ONLY)
+{
+       huft_free_all(PASS_STATE_ONLY);
+       longjmp(error_jmp, 1);
+}
+
+static unsigned fill_bitbuffer(STATE_PARAM unsigned bitbuffer, unsigned *current, const unsigned required)
+{
+       while (*current < required) {
+               if (bytebuffer_offset >= bytebuffer_size) {
+                       unsigned sz = bytebuffer_max - 4;
+                       if (to_read >= 0 && to_read < sz) /* unzip only */
+                               sz = to_read;
+                       /* Leave the first 4 bytes empty so we can always unwind the bitbuffer
+                        * to the front of the bytebuffer */
+                       bytebuffer_size = safe_read(gunzip_src_fd, &bytebuffer[4], sz);
+                       if ((int)bytebuffer_size < 1) {
+                               error_msg = "unexpected end of file";
+                               abort_unzip(PASS_STATE_ONLY);
+                       }
+                       if (to_read >= 0) /* unzip only */
+                               to_read -= bytebuffer_size;
+                       bytebuffer_size += 4;
+                       bytebuffer_offset = 4;
+               }
+               bitbuffer |= ((unsigned) bytebuffer[bytebuffer_offset]) << *current;
+               bytebuffer_offset++;
+               *current += 8;
+       }
+       return bitbuffer;
+}
+
+
+/* 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) - in this case stores NULL in *t.
+ *
+ * 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(const unsigned *b, const unsigned n,
+                          const unsigned s, const unsigned short *d,
+                          const unsigned char *e, huft_t **t, unsigned *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;
+
+       *t = NULL;
+
+       /* Generate counts for each bit length */
+       memset(c, 0, sizeof(c));
+       p = (unsigned *) b; /* cast allows us to reuse p for pointing to 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 */
+               *m = 0;
+               return 2;
+       }
+
+       /* Find minimum and maximum length, bound *m by those */
+       for (j = 1; (c[j] == 0) && (j <= BMAX); j++)
+               continue;
+       k = j; /* minimum code length */
+       for (i = BMAX; (c[i] == 0) && i; i--)
+               continue;
+       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) {
+               y -= c[j];
+               if (y < 0)
+                       return 2; /* bad input: more codes than bits */
+       }
+       y -= c[i];
+       if (y < 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 */
+               j += *p++;
+               *xp++ = j;
+       }
+
+       /* Make a table of values in order of bit lengths */
+       p = (unsigned *) b;
+       i = 0;
+       do {
+               j = *p++;
+               if (j != 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] = NULL;    /* just to keep compilers happy */
+       q = 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 = g - w;
+                               z = z > *m ? *m : z; /* upper limit on table size */
+                               j = k - w;
+                               f = 1 << j;
+                               if (f > 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 */
+                                               f <<= 1;
+                                               if (f <= *++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 = 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 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[]
+ */
+/* called once from inflate_block */
+
+/* map formerly local static variables to globals */
+#define ml inflate_codes_ml
+#define md inflate_codes_md
+#define bb inflate_codes_bb
+#define k  inflate_codes_k
+#define w  inflate_codes_w
+#define tl inflate_codes_tl
+#define td inflate_codes_td
+#define bl inflate_codes_bl
+#define bd inflate_codes_bd
+#define nn inflate_codes_nn
+#define dd inflate_codes_dd
+static void inflate_codes_setup(STATE_PARAM unsigned my_bl, unsigned my_bd)
+{
+       bl = my_bl;
+       bd = my_bd;
+       /* make local copies of globals */
+       bb = 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];
+}
+/* called once from inflate_get_next_window */
+static int inflate_codes(STATE_PARAM_ONLY)
+{
+       unsigned e;     /* table entry flag/number of extra bits */
+       huft_t *t;      /* pointer to table entry */
+
+       if (resume_copy)
+               goto do_copy;
+
+       while (1) {                     /* do until end of block */
+               bb = fill_bitbuffer(PASS_STATE bb, &k, bl);
+               t = tl + ((unsigned) bb & ml);
+               e = t->e;
+               if (e > 16)
+                       do {
+                               if (e == 99)
+                                       abort_unzip(PASS_STATE_ONLY);;
+                               bb >>= t->b;
+                               k -= t->b;
+                               e -= 16;
+                               bb = fill_bitbuffer(PASS_STATE bb, &k, e);
+                               t = t->v.t + ((unsigned) bb & mask_bits[e]);
+                               e = t->e;
+                       } while (e > 16);
+               bb >>= 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 */
+                       bb = fill_bitbuffer(PASS_STATE bb, &k, e);
+                       nn = t->v.n + ((unsigned) bb & mask_bits[e]);
+                       bb >>= e;
+                       k -= e;
+
+                       /* decode distance of block to copy */
+                       bb = fill_bitbuffer(PASS_STATE bb, &k, bd);
+                       t = td + ((unsigned) bb & md);
+                       e = t->e;
+                       if (e > 16)
+                               do {
+                                       if (e == 99)
+                                               abort_unzip(PASS_STATE_ONLY);
+                                       bb >>= t->b;
+                                       k -= t->b;
+                                       e -= 16;
+                                       bb = fill_bitbuffer(PASS_STATE bb, &k, e);
+                                       t = t->v.t + ((unsigned) bb & mask_bits[e]);
+                                       e = t->e;
+                               } while (e > 16);
+                       bb >>= t->b;
+                       k -= t->b;
+                       bb = fill_bitbuffer(PASS_STATE bb, &k, e);
+                       dd = w - t->v.n - ((unsigned) bb & mask_bits[e]);
+                       bb >>= e;
+                       k -= e;
+
+                       /* do the copy */
+ do_copy:
+                       do {
+                               /* Was: nn -= (e = (e = GUNZIP_WSIZE - ((dd &= GUNZIP_WSIZE - 1) > w ? dd : w)) > nn ? nn : e); */
+                               /* Who wrote THAT?? rewritten as: */
+                               dd &= GUNZIP_WSIZE - 1;
+                               e = GUNZIP_WSIZE - (dd > w ? dd : w);
+                               if (e > nn) e = nn;
+                               nn -= e;
+
+                               /* copy to new buffer to prevent possible overwrite */
+                               if (w - dd >= e) {      /* (this test assumes unsigned comparison) */
+                                       memcpy(gunzip_window + w, gunzip_window + dd, e);
+                                       w += e;
+                                       dd += e;
+                               } else {
+                                       /* do it slow to avoid memcpy() overlap */
+                                       /* !NOMEMCPY */
+                                       do {
+                                               gunzip_window[w++] = gunzip_window[dd++];
+                                       } while (--e);
+                               }
+                               if (w == GUNZIP_WSIZE) {
+                                       gunzip_outbuf_count = w;
+                                       resume_copy = (nn != 0);
+                                       //flush_gunzip_window();
+                                       w = 0;
+                                       return 1;
+                               }
+                       } while (nn);
+                       resume_copy = 0;
+               }
+       }
+
+       /* restore the globals from the locals */
+       gunzip_outbuf_count = w;        /* restore global gunzip_window pointer */
+       gunzip_bb = bb;                 /* 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 (tl and td), return */
+       huft_free_all(PASS_STATE_ONLY);
+
+       /* done */
+       return 0;
+}
+#undef ml
+#undef md
+#undef bb
+#undef k
+#undef w
+#undef tl
+#undef td
+#undef bl
+#undef bd
+#undef nn
+#undef dd
+
+
+/* called once from inflate_block */
+static void inflate_stored_setup(STATE_PARAM int my_n, int my_b, int my_k)
+{
+       inflate_stored_n = my_n;
+       inflate_stored_b = my_b;
+       inflate_stored_k = my_k;
+       /* initialize gunzip_window position */
+       inflate_stored_w = gunzip_outbuf_count;
+}
+/* called once from inflate_get_next_window */
+static int inflate_stored(STATE_PARAM_ONLY)
+{
+       /* read and output the compressed data */
+       while (inflate_stored_n--) {
+               inflate_stored_b = fill_bitbuffer(PASS_STATE inflate_stored_b, &inflate_stored_k, 8);
+               gunzip_window[inflate_stored_w++] = (unsigned char) inflate_stored_b;
+               if (inflate_stored_w == GUNZIP_WSIZE) {
+                       gunzip_outbuf_count = inflate_stored_w;
+                       //flush_gunzip_window();
+                       inflate_stored_w = 0;
+                       inflate_stored_b >>= 8;
+                       inflate_stored_k -= 8;
+                       return 1; /* We have a block */
+               }
+               inflate_stored_b >>= 8;
+               inflate_stored_k -= 8;
+       }
+
+       /* restore the globals from the locals */
+       gunzip_outbuf_count = inflate_stored_w;         /* restore global gunzip_window pointer */
+       gunzip_bb = inflate_stored_b;   /* restore global bit buffer */
+       gunzip_bk = inflate_stored_k;
+       return 0; /* Finished */
+}
+
+
+/*
+ * decompress an inflated block
+ * e: last block flag
+ *
+ * GLOBAL VARIABLES: bb, kk,
+ */
+/* Return values: -1 = inflate_stored, -2 = inflate_codes */
+/* One callsite in inflate_get_next_window */
+static int inflate_block(STATE_PARAM smallint *e)
+{
+       unsigned ll[286 + 30];  /* literal/length and distance code lengths */
+       unsigned t;     /* block type */
+       unsigned b;     /* bit buffer */
+       unsigned 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(PASS_STATE b, &k, 1);
+       *e = b & 1;
+       b >>= 1;
+       k -= 1;
+
+       /* read in block type */
+       b = fill_bitbuffer(PASS_STATE b, &k, 2);
+       t = (unsigned) b & 3;
+       b >>= 2;
+       k -= 2;
+
+       /* restore the global bit buffer */
+       gunzip_bb = b;
+       gunzip_bk = k;
+
+       /* Do we see block type 1 often? Yes!
+        * TODO: fix performance problem (see below) */
+       //bb_error_msg("blktype %d", t);
+
+       /* inflate that block type */
+       switch (t) {
+       case 0: /* Inflate stored */
+       {
+               unsigned n;     /* number of bytes in block */
+               unsigned b_stored;      /* bit buffer */
+               unsigned 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(PASS_STATE b_stored, &k_stored, 16);
+               n = ((unsigned) b_stored & 0xffff);
+               b_stored >>= 16;
+               k_stored -= 16;
+
+               b_stored = fill_bitbuffer(PASS_STATE b_stored, &k_stored, 16);
+               if (n != (unsigned) ((~b_stored) & 0xffff)) {
+                       abort_unzip(PASS_STATE_ONLY);   /* error in compressed data */
+               }
+               b_stored >>= 16;
+               k_stored -= 16;
+
+               inflate_stored_setup(PASS_STATE n, b_stored, k_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. TODO */
+       {
+               int i;                  /* temporary variable */
+               unsigned bl;            /* lookup bits for tl */
+               unsigned bd;            /* lookup bits for td */
+               /* gcc 4.2.1 is too dumb to reuse stackspace. Moved up... */
+               //unsigned ll[288];     /* length list for huft_build */
+
+               /* set up literal table */
+               for (i = 0; i < 144; i++)
+                       ll[i] = 8;
+               for (; i < 256; i++)
+                       ll[i] = 9;
+               for (; i < 280; i++)
+                       ll[i] = 7;
+               for (; i < 288; i++) /* make a complete, but wrong code set */
+                       ll[i] = 8;
+               bl = 7;
+               huft_build(ll, 288, 257, cplens, cplext, &inflate_codes_tl, &bl);
+               /* huft_build() never return nonzero - we use known data */
+
+               /* set up distance table */
+               for (i = 0; i < 30; i++) /* make an incomplete code set */
+                       ll[i] = 5;
+               bd = 5;
+               huft_build(ll, 30, 0, cpdist, cpdext, &inflate_codes_td, &bd);
+
+               /* set up data for inflate_codes() */
+               inflate_codes_setup(PASS_STATE bl, bd);
+
+               /* huft_free code moved into inflate_codes */
+
+               return -2;
+       }
+       case 2: /* Inflate dynamic */
+       {
+               enum { dbits = 6 };     /* bits in base distance lookup table */
+               enum { lbits = 9 };     /* bits in base literal/length lookup table */
+
+               huft_t *td;             /* distance code table */
+               unsigned i;             /* temporary variables */
+               unsigned j;
+               unsigned l;             /* last length */
+               unsigned m;             /* mask for bit lengths table */
+               unsigned n;             /* number of lengths to get */
+               unsigned bl;            /* lookup bits for tl */
+               unsigned bd;            /* lookup bits for td */
+               unsigned nb;            /* number of bit length codes */
+               unsigned nl;            /* number of literal/length codes */
+               unsigned nd;            /* number of distance codes */
+
+               //unsigned ll[286 + 30];/* literal/length and distance code lengths */
+               unsigned b_dynamic;     /* bit buffer */
+               unsigned 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(PASS_STATE b_dynamic, &k_dynamic, 5);
+               nl = 257 + ((unsigned) b_dynamic & 0x1f);       /* number of literal/length codes */
+
+               b_dynamic >>= 5;
+               k_dynamic -= 5;
+               b_dynamic = fill_bitbuffer(PASS_STATE b_dynamic, &k_dynamic, 5);
+               nd = 1 + ((unsigned) b_dynamic & 0x1f); /* number of distance codes */
+
+               b_dynamic >>= 5;
+               k_dynamic -= 5;
+               b_dynamic = fill_bitbuffer(PASS_STATE b_dynamic, &k_dynamic, 4);
+               nb = 4 + ((unsigned) b_dynamic & 0xf);  /* number of bit length codes */
+
+               b_dynamic >>= 4;
+               k_dynamic -= 4;
+               if (nl > 286 || nd > 30)
+                       abort_unzip(PASS_STATE_ONLY);   /* bad lengths */
+
+               /* read in bit-length-code lengths */
+               for (j = 0; j < nb; j++) {
+                       b_dynamic = fill_bitbuffer(PASS_STATE b_dynamic, &k_dynamic, 3);
+                       ll[border[j]] = (unsigned) 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, &inflate_codes_tl, &bl);
+               if (i != 0) {
+                       abort_unzip(PASS_STATE_ONLY); //return i;       /* incomplete code set */
+               }
+
+               /* read in literal and distance code lengths */
+               n = nl + nd;
+               m = mask_bits[bl];
+               i = l = 0;
+               while ((unsigned) i < n) {
+                       b_dynamic = fill_bitbuffer(PASS_STATE b_dynamic, &k_dynamic, (unsigned)bl);
+                       td = inflate_codes_tl + ((unsigned) b_dynamic & m);
+                       j = td->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(PASS_STATE b_dynamic, &k_dynamic, 2);
+                               j = 3 + ((unsigned) b_dynamic & 3);
+                               b_dynamic >>= 2;
+                               k_dynamic -= 2;
+                               if ((unsigned) i + j > n) {
+                                       abort_unzip(PASS_STATE_ONLY); //return 1;
+                               }
+                               while (j--) {
+                                       ll[i++] = l;
+                               }
+                       } else if (j == 17) {   /* 3 to 10 zero length codes */
+                               b_dynamic = fill_bitbuffer(PASS_STATE b_dynamic, &k_dynamic, 3);
+                               j = 3 + ((unsigned) b_dynamic & 7);
+                               b_dynamic >>= 3;
+                               k_dynamic -= 3;
+                               if ((unsigned) i + j > n) {
+                                       abort_unzip(PASS_STATE_ONLY); //return 1;
+                               }
+                               while (j--) {
+                                       ll[i++] = 0;
+                               }
+                               l = 0;
+                       } else {        /* j == 18: 11 to 138 zero length codes */
+                               b_dynamic = fill_bitbuffer(PASS_STATE b_dynamic, &k_dynamic, 7);
+                               j = 11 + ((unsigned) b_dynamic & 0x7f);
+                               b_dynamic >>= 7;
+                               k_dynamic -= 7;
+                               if ((unsigned) i + j > n) {
+                                       abort_unzip(PASS_STATE_ONLY); //return 1;
+                               }
+                               while (j--) {
+                                       ll[i++] = 0;
+                               }
+                               l = 0;
+                       }
+               }
+
+               /* free decoding table for trees */
+               huft_free(inflate_codes_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;
+
+               i = huft_build(ll, nl, 257, cplens, cplext, &inflate_codes_tl, &bl);
+               if (i != 0)
+                       abort_unzip(PASS_STATE_ONLY);
+               bd = dbits;
+               i = huft_build(ll + nl, nd, 0, cpdist, cpdext, &inflate_codes_td, &bd);
+               if (i != 0)
+                       abort_unzip(PASS_STATE_ONLY);
+
+               /* set up data for inflate_codes() */
+               inflate_codes_setup(PASS_STATE bl, bd);
+
+               /* huft_free code moved into inflate_codes */
+
+               return -2;
+       }
+       default:
+               abort_unzip(PASS_STATE_ONLY);
+       }
+}
+
+/* Two callsites, both in inflate_get_next_window */
+static void calculate_gunzip_crc(STATE_PARAM_ONLY)
+{
+       unsigned 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;
+}
+
+/* One callsite in inflate_unzip_internal */
+static int inflate_get_next_window(STATE_PARAM_ONLY)
+{
+       gunzip_outbuf_count = 0;
+
+       while (1) {
+               int ret;
+
+               if (need_another_block) {
+                       if (end_reached) {
+                               calculate_gunzip_crc(PASS_STATE_ONLY);
+                               end_reached = 0;
+                               /* NB: need_another_block is still set */
+                               return 0; /* Last block */
+                       }
+                       method = inflate_block(PASS_STATE &end_reached);
+                       need_another_block = 0;
+               }
+
+               switch (method) {
+               case -1:
+                       ret = inflate_stored(PASS_STATE_ONLY);
+                       break;
+               case -2:
+                       ret = inflate_codes(PASS_STATE_ONLY);
+                       break;
+               default: /* cannot happen */
+                       abort_unzip(PASS_STATE_ONLY);
+               }
+
+               if (ret == 1) {
+                       calculate_gunzip_crc(PASS_STATE_ONLY);
+                       return 1; /* more data left */
+               }
+               need_another_block = 1; /* end of that block */
+       }
+       /* Doesnt get here */
+}
+
+
+/* Called from unpack_gz_stream() and inflate_unzip() */
+static USE_DESKTOP(long long) int
+inflate_unzip_internal(STATE_PARAM int in, int out)
+{
+       USE_DESKTOP(long long) int n = 0;
+       ssize_t nwrote;
+
+       /* 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;
+
+       /* (re) initialize state */
+       method = -1;
+       need_another_block = 1;
+       resume_copy = 0;
+       gunzip_bk = 0;
+       gunzip_bb = 0;
+
+       /* Create the crc table */
+       gunzip_crc_table = crc32_filltable(NULL, 0);
+       gunzip_crc = ~0;
+
+       error_msg = "corrupted data";
+       if (setjmp(error_jmp)) {
+               /* Error from deep inside zip machinery */
+               n = -1;
+               goto ret;
+       }
+
+       while (1) {
+               int r = inflate_get_next_window(PASS_STATE_ONLY);
+               nwrote = full_write(out, gunzip_window, gunzip_outbuf_count);
+               if (nwrote != (ssize_t)gunzip_outbuf_count) {
+                       bb_perror_msg("write");
+                       n = -1;
+                       goto ret;
+               }
+               USE_DESKTOP(n += nwrote;)
+               if (r == 0) break;
+       }
+
+       /* 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;
+       }
+ ret:
+       /* Cleanup */
+       free(gunzip_window);
+       free(gunzip_crc_table);
+       return n;
+}
+
+
+/* External entry points */
+
+/* For unzip */
+
+USE_DESKTOP(long long) int FAST_FUNC
+inflate_unzip(inflate_unzip_result *res, off_t compr_size, int in, int out)
+{
+       USE_DESKTOP(long long) int n;
+       DECLARE_STATE;
+
+       ALLOC_STATE;
+
+       to_read = compr_size;
+//     bytebuffer_max = 0x8000;
+       bytebuffer_offset = 4;
+       bytebuffer = xmalloc(bytebuffer_max);
+       n = inflate_unzip_internal(PASS_STATE in, out);
+       free(bytebuffer);
+
+       res->crc = gunzip_crc;
+       res->bytes_out = gunzip_bytes_out;
+       DEALLOC_STATE;
+       return n;
+}
+
+
+/* For gunzip */
+
+/* helpers first */
+
+/* Top up the input buffer with at least n bytes. */
+static int top_up(STATE_PARAM unsigned n)
+{
+       int count = bytebuffer_size - bytebuffer_offset;
+
+       if (count < (int)n) {
+               memmove(bytebuffer, &bytebuffer[bytebuffer_offset], count);
+               bytebuffer_offset = 0;
+               bytebuffer_size = full_read(gunzip_src_fd, &bytebuffer[count], bytebuffer_max - count);
+               if ((int)bytebuffer_size < 0) {
+                       bb_error_msg("read error");
+                       return 0;
+               }
+               bytebuffer_size += count;
+               if (bytebuffer_size < n)
+                       return 0;
+       }
+       return 1;
+}
+
+static uint16_t buffer_read_le_u16(STATE_PARAM_ONLY)
+{
+       uint16_t res;
+#if BB_LITTLE_ENDIAN
+       move_from_unaligned16(res, &bytebuffer[bytebuffer_offset]);
+#else
+       res = bytebuffer[bytebuffer_offset];
+       res |= bytebuffer[bytebuffer_offset + 1] << 8;
+#endif
+       bytebuffer_offset += 2;
+       return res;
+}
+
+static uint32_t buffer_read_le_u32(STATE_PARAM_ONLY)
+{
+       uint32_t res;
+#if BB_LITTLE_ENDIAN
+       move_from_unaligned32(res, &bytebuffer[bytebuffer_offset]);
+#else
+       res = bytebuffer[bytebuffer_offset];
+       res |= bytebuffer[bytebuffer_offset + 1] << 8;
+       res |= bytebuffer[bytebuffer_offset + 2] << 16;
+       res |= bytebuffer[bytebuffer_offset + 3] << 24;
+#endif
+       bytebuffer_offset += 4;
+       return res;
+}
+
+static int check_header_gzip(STATE_PARAM unpack_info_t *info)
+{
+       union {
+               unsigned char raw[8];
+               struct {
+                       uint8_t gz_method;
+                       uint8_t flags;
+                       uint32_t mtime;
+                       uint8_t xtra_flags_UNUSED;
+                       uint8_t os_flags_UNUSED;
+               } __attribute__((packed)) formatted;
+       } header;
+       struct BUG_header {
+               char BUG_header[sizeof(header) == 8 ? 1 : -1];
+       };
+
+       /*
+        * Rewind bytebuffer. We use the beginning because the header has 8
+        * bytes, leaving enough for unwinding afterwards.
+        */
+       bytebuffer_size -= bytebuffer_offset;
+       memmove(bytebuffer, &bytebuffer[bytebuffer_offset], bytebuffer_size);
+       bytebuffer_offset = 0;
+
+       if (!top_up(PASS_STATE 8))
+               return 0;
+       memcpy(header.raw, &bytebuffer[bytebuffer_offset], 8);
+       bytebuffer_offset += 8;
+
+       /* Check the compression method */
+       if (header.formatted.gz_method != 8) {
+               return 0;
+       }
+
+       if (header.formatted.flags & 0x04) {
+               /* bit 2 set: extra field present */
+               unsigned extra_short;
+
+               if (!top_up(PASS_STATE 2))
+                       return 0;
+               extra_short = buffer_read_le_u16(PASS_STATE_ONLY);
+               if (!top_up(PASS_STATE extra_short))
+                       return 0;
+               /* Ignore extra field */
+               bytebuffer_offset += extra_short;
+       }
+
+       /* Discard original name and file comment if any */
+       /* bit 3 set: original file name present */
+       /* bit 4 set: file comment present */
+       if (header.formatted.flags & 0x18) {
+               while (1) {
+                       do {
+                               if (!top_up(PASS_STATE 1))
+                                       return 0;
+                       } while (bytebuffer[bytebuffer_offset++] != 0);
+                       if ((header.formatted.flags & 0x18) != 0x18)
+                               break;
+                       header.formatted.flags &= ~0x18;
+               }
+       }
+
+       if (info)
+               info->mtime = SWAP_LE32(header.formatted.mtime);
+
+       /* Read the header checksum */
+       if (header.formatted.flags & 0x02) {
+               if (!top_up(PASS_STATE 2))
+                       return 0;
+               bytebuffer_offset += 2;
+       }
+       return 1;
+}
+
+USE_DESKTOP(long long) int FAST_FUNC
+unpack_gz_stream_with_info(int in, int out, unpack_info_t *info)
+{
+       uint32_t v32;
+       USE_DESKTOP(long long) int n;
+       DECLARE_STATE;
+
+       n = 0;
+
+       ALLOC_STATE;
+       to_read = -1;
+//     bytebuffer_max = 0x8000;
+       bytebuffer = xmalloc(bytebuffer_max);
+       gunzip_src_fd = in;
+
+ again:
+       if (!check_header_gzip(PASS_STATE info)) {
+               bb_error_msg("corrupted data");
+               n = -1;
+               goto ret;
+       }
+       n += inflate_unzip_internal(PASS_STATE in, out);
+       if (n < 0)
+               goto ret;
+
+       if (!top_up(PASS_STATE 8)) {
+               bb_error_msg("corrupted data");
+               n = -1;
+               goto ret;
+       }
+
+       /* Validate decompression - crc */
+       v32 = buffer_read_le_u32(PASS_STATE_ONLY);
+       if ((~gunzip_crc) != v32) {
+               bb_error_msg("crc error");
+               n = -1;
+               goto ret;
+       }
+
+       /* Validate decompression - size */
+       v32 = buffer_read_le_u32(PASS_STATE_ONLY);
+       if ((uint32_t)gunzip_bytes_out != v32) {
+               bb_error_msg("incorrect length");
+               n = -1;
+       }
+
+       if (!top_up(PASS_STATE 2))
+               goto ret; /* EOF */
+
+       if (bytebuffer[bytebuffer_offset] == 0x1f
+        && bytebuffer[bytebuffer_offset + 1] == 0x8b
+       ) {
+               bytebuffer_offset += 2;
+               goto again;
+       }
+       /* GNU gzip says: */
+       /*bb_error_msg("decompression OK, trailing garbage ignored");*/
+
+ ret:
+       free(bytebuffer);
+       DEALLOC_STATE;
+       return n;
+}
+
+USE_DESKTOP(long long) int FAST_FUNC
+unpack_gz_stream(int in, int out)
+{
+       return unpack_gz_stream_with_info(in, out, NULL);
+}
diff --git a/archival/libunarchive/filter_accept_all.c b/archival/libunarchive/filter_accept_all.c
new file mode 100644 (file)
index 0000000..21f9c5c
--- /dev/null
@@ -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 "libbb.h"
+#include "unarchive.h"
+
+/* Accept any non-null name, its not really a filter at all */
+char FAST_FUNC 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 (file)
index 0000000..afa0b4c
--- /dev/null
@@ -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 "libbb.h"
+#include "unarchive.h"
+
+/*
+ * Accept names that are in the accept list, ignoring reject list.
+ */
+char FAST_FUNC 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 (file)
index 0000000..f1de4e8
--- /dev/null
@@ -0,0 +1,51 @@
+/* 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 "libbb.h"
+#include "unarchive.h"
+
+/* Built and used only if ENABLE_DPKG || ENABLE_DPKG_DEB */
+
+/*
+ * 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 FAST_FUNC 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;
+
+               /* Find extension */
+               name_ptr = strrchr(archive_handle->file_header->name, '.');
+               if (!name_ptr)
+                       return EXIT_FAILURE;
+               name_ptr++;
+
+               /* Modify the subarchive handler based on the extension */
+               if (ENABLE_FEATURE_SEAMLESS_GZ
+                && strcmp(name_ptr, "gz") == 0
+               ) {
+                       archive_handle->action_data_subarchive = get_header_tar_gz;
+                       return EXIT_SUCCESS;
+               }
+               if (ENABLE_FEATURE_SEAMLESS_BZ2
+                && strcmp(name_ptr, "bz2") == 0
+               ) {
+                       archive_handle->action_data_subarchive = get_header_tar_bz2;
+                       return EXIT_SUCCESS;
+               }
+               if (ENABLE_FEATURE_SEAMLESS_LZMA
+                && strcmp(name_ptr, "lzma") == 0
+               ) {
+                       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 (file)
index 0000000..aa601e1
--- /dev/null
@@ -0,0 +1,36 @@
+/* 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 "libbb.h"
+#include "unarchive.h"
+
+/*
+ * Accept names that are in the accept list and not in the reject list
+ */
+char FAST_FUNC filter_accept_reject_list(archive_handle_t *archive_handle)
+{
+       const char *key;
+       const llist_t *reject_entry;
+       const llist_t *accept_entry;
+
+       key = archive_handle->file_header->name;
+
+       /* If the key is in a reject list fail */
+       reject_entry = find_list_entry2(archive_handle->reject, key);
+       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 (file)
index 0000000..bc7bc64
--- /dev/null
@@ -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 <fnmatch.h>
+#include "libbb.h"
+#include "unarchive.h"
+
+/* Find a string in a shell pattern list */
+const llist_t* FAST_FUNC 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* FAST_FUNC 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 than 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 (file)
index 0000000..d476a9d
--- /dev/null
@@ -0,0 +1,127 @@
+/* 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 FAST_FUNC get_header_ar(archive_handle_t *archive_handle)
+{
+       int err;
+       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;
+#if ENABLE_FEATURE_AR_LONG_FILENAMES
+       static char *ar_long_names;
+       static unsigned 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");
+
+       /* FIXME: more thorough routine would be in order here */
+       /* (we have something like that in tar) */
+       /* but for now we are lax. This code works because */
+       /* on misformatted numbers bb_strtou returns all-ones */
+       typed->mode = err = bb_strtou(ar.formatted.mode, NULL, 8);
+       if (err == -1) bb_error_msg_and_die("invalid ar header");
+       typed->mtime = err = bb_strtou(ar.formatted.date, NULL, 10);
+       if (err == -1) bb_error_msg_and_die("invalid ar header");
+       typed->uid = err = bb_strtou(ar.formatted.uid, NULL, 10);
+       if (err == -1) bb_error_msg_and_die("invalid ar header");
+       typed->gid = err = bb_strtou(ar.formatted.gid, NULL, 10);
+       if (err == -1) bb_error_msg_and_die("invalid ar header");
+       typed->size = err = bb_strtou(ar.formatted.size, NULL, 10);
+       if (err == -1) bb_error_msg_and_die("invalid ar header");
+
+       /* long filenames have '/' as the first character */
+       if (ar.formatted.name[0] == '/') {
+#if ENABLE_FEATURE_AR_LONG_FILENAMES
+               unsigned long_offset;
+
+               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 */
+               }
+
+               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 */
+               }
+
+               /* The number after the '/' indicates the offset in the ar data section
+                * (saved in variable long_name) that conatains the real filename */
+               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 ENABLE_DPKG || ENABLE_DPKG_DEB
+               if (archive_handle->sub_archive) {
+                       while (archive_handle->action_data_subarchive(archive_handle->sub_archive) == EXIT_SUCCESS)
+                               continue;
+               } else
+#endif
+                       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 (file)
index 0000000..302f122
--- /dev/null
@@ -0,0 +1,182 @@
+/* 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_t {
+       struct hardlinks_t *next;
+       int inode; /* TODO: must match maj/min too! */
+       int mode ;
+       int mtime; /* These three are useful only in corner case */
+       int uid  ; /* of hardlinks with zero size body */
+       int gid  ;
+       char name[1];
+} hardlinks_t;
+
+char FAST_FUNC get_header_cpio(archive_handle_t *archive_handle)
+{
+       file_header_t *file_header = archive_handle->file_header;
+       char cpio_header[110];
+       int namesize;
+       int major, minor, nlink, mode, inode;
+       unsigned size, uid, gid, mtime;
+
+#define hardlinks_to_create (*(hardlinks_t **)(&archive_handle->ah_priv[0]))
+#define created_hardlinks   (*(hardlinks_t **)(&archive_handle->ah_priv[1]))
+#define block_count         (archive_handle->ah_priv[2])
+//     if (!archive_handle->ah_priv_inited) {
+//             archive_handle->ah_priv_inited = 1;
+//             hardlinks_to_create = NULL;
+//             created_hardlinks = NULL;
+//     }
+
+       /* There can be padding before archive header */
+       data_align(archive_handle, 4);
+
+       size = full_read(archive_handle->src_fd, cpio_header, 110);
+       if (size == 0) {
+               goto create_hardlinks;
+       }
+       if (size != 110) {
+               bb_error_msg_and_die("short read");
+       }
+       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");
+       }
+
+       if (sscanf(cpio_header + 6,
+                       "%8x" "%8x" "%8x" "%8x"
+                       "%8x" "%8x" "%8x" /*maj,min:*/ "%*16c"
+                       /*rmaj,rmin:*/"%8x" "%8x" "%8x" /*chksum: "%*8c"*/,
+                       &inode, &mode, &uid, &gid,
+                       &nlink, &mtime, &size,
+                       &major, &minor, &namesize) != 10)
+               bb_error_msg_and_die("damaged cpio file");
+       file_header->mode = mode;
+       file_header->uid = uid;
+       file_header->gid = gid;
+       file_header->mtime = mtime;
+       file_header->size = size;
+
+       namesize &= 0x1fff; /* paranoia: limit names to 8k chars */
+       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. ">> 9" divides by 512 */
+               block_count = (void*)(ptrdiff_t) ((archive_handle->offset + 511) >> 9);
+               goto create_hardlinks;
+       }
+
+       file_header->link_target = NULL;
+       if (S_ISLNK(file_header->mode)) {
+               file_header->size &= 0x1fff; /* paranoia: limit names to 8k chars */
+               file_header->link_target = xzalloc(file_header->size + 1);
+               xread(archive_handle->src_fd, file_header->link_target, file_header->size);
+               archive_handle->offset += file_header->size;
+               file_header->size = 0; /* Stop possible seeks in future */
+       }
+
+// TODO: data_extract_all can't deal with hardlinks to non-files...
+// when fixed, change S_ISREG to !S_ISDIR here
+
+       if (nlink > 1 && S_ISREG(file_header->mode)) {
+               hardlinks_t *new = xmalloc(sizeof(*new) + namesize);
+               new->inode = inode;
+               new->mode  = mode ;
+               new->mtime = mtime;
+               new->uid   = uid  ;
+               new->gid   = gid  ;
+               strcpy(new->name, file_header->name);
+               /* Put file on a linked list for later */
+               if (size == 0) {
+                       new->next = hardlinks_to_create;
+                       hardlinks_to_create = new;
+                       return EXIT_SUCCESS; /* Skip this one */
+                       /* TODO: this breaks cpio -t (it does not show hardlinks) */
+               }
+               new->next = created_hardlinks;
+               created_hardlinks = new;
+       }
+       file_header->device = makedev(major, minor);
+
+       if (archive_handle->filter(archive_handle) == EXIT_SUCCESS) {
+               archive_handle->action_data(archive_handle);
+               archive_handle->action_header(file_header);
+       } else {
+               data_skip(archive_handle);
+       }
+
+       archive_handle->offset += file_header->size;
+
+       free(file_header->link_target);
+       free(file_header->name);
+       file_header->link_target = NULL;
+       file_header->name = NULL;
+
+       return EXIT_SUCCESS;
+
+ create_hardlinks:
+       free(file_header->link_target);
+       free(file_header->name);
+
+       while (hardlinks_to_create) {
+               hardlinks_t *cur;
+               hardlinks_t *make_me = hardlinks_to_create;
+
+               hardlinks_to_create = make_me->next;
+
+               memset(file_header, 0, sizeof(*file_header));
+               file_header->mtime = make_me->mtime;
+               file_header->name = make_me->name;
+               file_header->mode = make_me->mode;
+               file_header->uid = make_me->uid;
+               file_header->gid = make_me->gid;
+               /*file_header->size = 0;*/
+               /*file_header->link_target = NULL;*/
+
+               /* Try to find a file we are hardlinked to */
+               cur = created_hardlinks;
+               while (cur) {
+                       /* TODO: must match maj/min too! */
+                       if (cur->inode == make_me->inode) {
+                               file_header->link_target = cur->name;
+                                /* link_target != NULL, size = 0: "I am a hardlink" */
+                               if (archive_handle->filter(archive_handle) == EXIT_SUCCESS)
+                                       archive_handle->action_data(archive_handle);
+                               free(make_me);
+                               goto next_link;
+                       }
+                       cur = cur->next;
+               }
+               /* Oops... no file with such inode was created... do it now
+                * (happens when hardlinked files are empty (zero length)) */
+               if (archive_handle->filter(archive_handle) == EXIT_SUCCESS)
+                       archive_handle->action_data(archive_handle);
+               /* Move to the list of created hardlinked files */
+               make_me->next = created_hardlinks;
+               created_hardlinks = make_me;
+ next_link: ;
+       }
+
+       while (created_hardlinks) {
+               hardlinks_t *p = created_hardlinks;
+               created_hardlinks = p->next;
+               free(p);
+       }
+
+       return EXIT_FAILURE; /* "No more files to process" */
+}
diff --git a/archival/libunarchive/get_header_tar.c b/archival/libunarchive/get_header_tar.c
new file mode 100644 (file)
index 0000000..443052f
--- /dev/null
@@ -0,0 +1,449 @@
+/* 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"
+
+/*
+ * GNU tar uses "base-256 encoding" for very large numbers (>8 billion).
+ * Encoding is binary, with highest bit always set as a marker
+ * and sign in next-highest bit:
+ * 80 00 .. 00 - zero
+ * bf ff .. ff - largest positive number
+ * ff ff .. ff - minus 1
+ * c0 00 .. 00 - smallest negative number
+ *
+ * We expect it only in size field, where negative numbers don't make sense.
+ */
+static off_t getBase256_len12(const char *str)
+{
+       off_t value;
+       int len;
+
+       /* if (*str & 0x40) error; - caller prevents this */
+
+       if (sizeof(off_t) >= 12) {
+               /* Probably 128-bit (16 byte) off_t. Can be optimized. */
+               len = 12;
+               value = *str++ & 0x3f;
+               while (--len)
+                       value = (value << 8) + (unsigned char) *str++;
+               return value;
+       }
+
+#ifdef CHECK_FOR_OVERFLOW
+       /* Can be optimized to eat 32-bit chunks */
+       char c = *str++ & 0x3f;
+       len = 12;
+       while (1) {
+               if (c)
+                       bb_error_msg_and_die("overflow in base-256 encoded file size");
+               if (--len == sizeof(off_t))
+                       break;
+               c = *str++;
+       }
+#else
+       str += (12 - sizeof(off_t));
+#endif
+
+/* Now str points to sizeof(off_t) least significant bytes.
+ *
+ * Example of tar file with 8914993153 (0x213600001) byte file.
+ * Field starts at offset 7c:
+ * 00070  30 30 30 00 30 30 30 30  30 30 30 00 80 00 00 00  |000.0000000.....|
+ * 00080  00 00 00 02 13 60 00 01  31 31 31 32 30 33 33 36  |.....`..11120336|
+ *
+ * str is at offset 80 or 84 now (64-bit or 32-bit off_t).
+ * We (ab)use the fact that value happens to be aligned,
+ * and fetch it in one go:
+ */
+       if (sizeof(off_t) == 8) {
+               value = *(off_t*)str;
+               value = SWAP_BE64(value);
+       } else if (sizeof(off_t) == 4) {
+               value = *(off_t*)str;
+               value = SWAP_BE32(value);
+       } else {
+               value = 0;
+               len = sizeof(off_t);
+               while (--len)
+                       value = (value << 8) + (unsigned char) *str++;
+       }
+       return value;
+}
+
+/* NB: _DESTROYS_ str[len] character! */
+static unsigned long long getOctal(char *str, int len)
+{
+       unsigned long long v;
+       /* NB: leading spaces are allowed. Using strtoull to handle that.
+        * The downside is that we accept e.g. "-123" too :)
+        */
+       str[len] = '\0';
+       v = strtoull(str, &str, 8);
+       if (*str && (!ENABLE_FEATURE_TAR_OLDGNU_COMPATIBILITY || *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 FAST_FUNC get_header_tar(archive_handle_t *archive_handle)
+{
+       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 */
+               /* POSIX:   "ustar" NUL "00" */
+               /* GNU tar: "ustar  " NUL */
+               /* Normally it's defined as magic[6] followed by
+                * version[2], but we put them together to simplify code
+                */
+               char magic[8];      /* 257-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 i, sum_u, sum;
+#if ENABLE_FEATURE_TAR_OLDSUN_COMPATIBILITY
+       int sum_s;
+#endif
+       int parse_names;
+
+       /* Our "private data" */
+#define p_end (*(smallint *)(&archive_handle->ah_priv[0]))
+#if ENABLE_FEATURE_TAR_GNU_EXTENSIONS
+#define p_longname (*(char* *)(&archive_handle->ah_priv[1]))
+#define p_linkname (*(char* *)(&archive_handle->ah_priv[2]))
+#else
+#define p_longname 0
+#define p_linkname 0
+#endif
+//     if (!archive_handle->ah_priv_inited) {
+//             archive_handle->ah_priv_inited = 1;
+//             p_end = 0;
+//             USE_FEATURE_TAR_GNU_EXTENSIONS(p_longname = NULL;)
+//             USE_FEATURE_TAR_GNU_EXTENSIONS(p_linkname = NULL;)
+//     }
+
+       if (sizeof(tar) != 512)
+               BUG_tar_header_size();
+
+#if ENABLE_FEATURE_TAR_GNU_EXTENSIONS
+ again:
+#endif
+       /* Align header */
+       data_align(archive_handle, 512);
+
+ again_after_align:
+
+#if ENABLE_DESKTOP || ENABLE_FEATURE_TAR_AUTODETECT
+       /* to prevent misdetection of bz2 sig */
+       *(uint32_t*)(&tar) = 0;
+       i = full_read(archive_handle->src_fd, &tar, 512);
+       /* If GNU tar sees EOF in above read, it says:
+        * "tar: A lone zero block at N", where N = kilobyte
+        * where EOF was met (not EOF block, actual EOF!),
+        * and exits with EXIT_SUCCESS.
+        * We will mimic exit(EXIT_SUCCESS), although we will not mimic
+        * the message and we don't check whether we indeed
+        * saw zero block directly before this. */
+       if (i == 0) {
+               xfunc_error_retval = 0;
+ short_read:
+               bb_error_msg_and_die("short read");
+       }
+       if (i != 512) {
+               USE_FEATURE_TAR_AUTODETECT(goto autodetect;)
+               goto short_read;
+       }
+
+#else
+       i = 512;
+       xread(archive_handle->src_fd, &tar, i);
+#endif
+       archive_handle->offset += i;
+
+       /* If there is no filename its an empty header */
+       if (tar.name[0] == 0 && tar.prefix[0] == 0) {
+               if (p_end) {
+                       /* 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)
+                               continue;
+                       return EXIT_FAILURE;
+               }
+               p_end = 1;
+               return EXIT_SUCCESS;
+       }
+       p_end = 0;
+
+       /* Check header has valid magic, "ustar" is for the proper tar,
+        * five NULs are for the old tar format  */
+       if (strncmp(tar.magic, "ustar", 5) != 0
+        && (!ENABLE_FEATURE_TAR_OLDGNU_COMPATIBILITY
+            || memcmp(tar.magic, "\0\0\0\0", 5) != 0)
+       ) {
+#if ENABLE_FEATURE_TAR_AUTODETECT
+               char FAST_FUNC (*get_header_ptr)(archive_handle_t *);
+
+ autodetect:
+               /* tar gz/bz autodetect: check for gz/bz2 magic.
+                * If we see the magic, and it is the very first block,
+                * we can switch to get_header_tar_gz/bz2/lzma().
+                * Needs seekable fd. I wish recv(MSG_PEEK) works
+                * on any fd... */
+#if ENABLE_FEATURE_SEAMLESS_GZ
+               if (tar.name[0] == 0x1f && tar.name[1] == (char)0x8b) { /* gzip */
+                       get_header_ptr = get_header_tar_gz;
+               } else
+#endif
+#if ENABLE_FEATURE_SEAMLESS_BZ2
+               if (tar.name[0] == 'B' && tar.name[1] == 'Z'
+                && tar.name[2] == 'h' && isdigit(tar.name[3])
+               ) { /* bzip2 */
+                       get_header_ptr = get_header_tar_bz2;
+               } else
+#endif
+                       goto err;
+               /* Two different causes for lseek() != 0:
+                * unseekable fd (would like to support that too, but...),
+                * or not first block (false positive, it's not .gz/.bz2!) */
+               if (lseek(archive_handle->src_fd, -i, SEEK_CUR) != 0)
+                       goto err;
+               while (get_header_ptr(archive_handle) == EXIT_SUCCESS)
+                       continue;
+               return EXIT_FAILURE;
+ err:
+#endif /* FEATURE_TAR_AUTODETECT */
+               bb_error_msg_and_die("invalid tar magic");
+       }
+
+       /* Do checksum on headers.
+        * POSIX says that checksum is done on unsigned bytes, but
+        * Sun and HP-UX gets it wrong... more details in
+        * GNU tar source. */
+#if ENABLE_FEATURE_TAR_OLDSUN_COMPATIBILITY
+       sum_s = ' ' * sizeof(tar.chksum);
+#endif
+       sum_u = ' ' * sizeof(tar.chksum);
+       for (i = 0; i < 148; i++) {
+               sum_u += ((unsigned char*)&tar)[i];
+#if ENABLE_FEATURE_TAR_OLDSUN_COMPATIBILITY
+               sum_s += ((signed char*)&tar)[i];
+#endif
+       }
+       for (i = 156; i < 512; i++) {
+               sum_u += ((unsigned char*)&tar)[i];
+#if ENABLE_FEATURE_TAR_OLDSUN_COMPATIBILITY
+               sum_s += ((signed char*)&tar)[i];
+#endif
+       }
+#if ENABLE_FEATURE_TAR_OLDGNU_COMPATIBILITY
+       sum = strtoul(tar.chksum, &cp, 8);
+       if ((*cp && *cp != ' ')
+        || (sum_u != sum USE_FEATURE_TAR_OLDSUN_COMPATIBILITY(&& sum_s != sum))
+       ) {
+               bb_error_msg_and_die("invalid tar header checksum");
+       }
+#else
+       /* This field does not need special treatment (getOctal) */
+       sum = xstrtoul(tar.chksum, 8);
+       if (sum_u != sum USE_FEATURE_TAR_OLDSUN_COMPATIBILITY(&& sum_s != sum)) {
+               bb_error_msg_and_die("invalid tar header checksum");
+       }
+#endif
+
+       /* 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]) {
+               char t = tar.prefix[0];
+               /* we trash prefix[0] here, but we DO need it later! */
+               unsigned minor = GET_OCTAL(tar.devminor);
+               unsigned major = GET_OCTAL(tar.devmajor);
+               file_header->device = makedev(major, minor);
+               tar.prefix[0] = t;
+       }
+       file_header->link_target = NULL;
+       if (!p_linkname && parse_names && tar.linkname[0]) {
+               file_header->link_target = xstrndup(tar.linkname, sizeof(tar.linkname));
+               /* FIXME: what if we have non-link object with link_target? */
+               /* Will link_target be free()ed? */
+       }
+#if ENABLE_FEATURE_TAR_UNAME_GNAME
+       file_header->uname = tar.uname[0] ? xstrndup(tar.uname, sizeof(tar.uname)) : NULL;
+       file_header->gname = tar.gname[0] ? xstrndup(tar.gname, sizeof(tar.gname)) : NULL;
+#endif
+       file_header->mtime = GET_OCTAL(tar.mtime);
+       /* Size field: handle GNU tar's "base256 encoding" */
+       file_header->size = (*tar.size & 0xc0) == 0x80 /* positive base256? */
+                       ? getBase256_len12(tar.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 (!p_longname && parse_names) {
+               /* we trash mode[0] here, it's ok */
+               //tar.name[sizeof(tar.name)] = '\0'; - gcc 4.3.0 would complain
+               tar.mode[0] = '\0';
+               if (tar.prefix[0]) {
+                       /* and padding[0] */
+                       //tar.prefix[sizeof(tar.prefix)] = '\0'; - gcc 4.3.0 would complain
+                       tar.padding[0] = '\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, '/')) {
+                       goto set_dir;
+               }
+#endif
+               file_header->mode |= S_IFREG;
+               break;
+       case '2':
+               file_header->mode |= S_IFLNK;
+               /* have seen tarballs with size field containing
+                * the size of the link target's name */
+ size0:
+               file_header->size = 0;
+               break;
+       case '3':
+               file_header->mode |= S_IFCHR;
+               goto size0; /* paranoia */
+       case '4':
+               file_header->mode |= S_IFBLK;
+               goto size0;
+       case '5':
+ USE_FEATURE_TAR_OLDGNU_COMPATIBILITY(set_dir:)
+               file_header->mode |= S_IFDIR;
+               goto size0;
+       case '6':
+               file_header->mode |= S_IFIFO;
+               goto size0;
+#if ENABLE_FEATURE_TAR_GNU_EXTENSIONS
+       case 'L':
+               /* free: paranoia: tar with several consecutive longnames */
+               free(p_longname);
+               /* For paranoia reasons we allocate extra NUL char */
+               p_longname = xzalloc(file_header->size + 1);
+               /* We read ASCIZ string, including NUL */
+               xread(archive_handle->src_fd, p_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(p_linkname);
+               p_linkname = xzalloc(file_header->size + 1);
+               xread(archive_handle->src_fd, p_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 (p_longname) {
+               file_header->name = p_longname;
+               p_longname = NULL;
+       }
+       if (p_linkname) {
+               file_header->link_target = p_linkname;
+               p_linkname = NULL;
+       }
+#endif
+       if (strncmp(file_header->name, "/../"+1, 3) == 0
+        || 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 it's 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->ah_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_target);
+       /* Do not free(file_header->name)! (why?) */
+#if ENABLE_FEATURE_TAR_UNAME_GNAME
+       free(file_header->uname);
+       free(file_header->gname);
+#endif
+       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 (file)
index 0000000..615bbba
--- /dev/null
@@ -0,0 +1,21 @@
+/* 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"
+
+char FAST_FUNC get_header_tar_bz2(archive_handle_t *archive_handle)
+{
+       /* Can't lseek over pipes */
+       archive_handle->seek = seek_by_read;
+
+       open_transformer(archive_handle->src_fd, unpack_bz2_stream_prime, "bunzip2");
+       archive_handle->offset = 0;
+       while (get_header_tar(archive_handle) == EXIT_SUCCESS)
+               continue;
+
+       /* 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 (file)
index 0000000..e88b720
--- /dev/null
@@ -0,0 +1,36 @@
+/* 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"
+
+char FAST_FUNC get_header_tar_gz(archive_handle_t *archive_handle)
+{
+#if BB_MMU
+       unsigned char magic[2];
+#endif
+
+       /* Can't lseek over pipes */
+       archive_handle->seek = seek_by_read;
+
+       /* Check gzip magic only if open_transformer will invoke unpack_gz_stream (MMU case).
+        * Otherwise, it will invoke an external helper "gunzip -cf" (NOMMU case) which will
+        * need the header. */
+#if BB_MMU
+       xread(archive_handle->src_fd, &magic, 2);
+       /* Can skip this check, but error message will be less clear */
+       if ((magic[0] != 0x1f) || (magic[1] != 0x8b)) {
+               bb_error_msg_and_die("invalid gzip magic");
+       }
+#endif
+
+       open_transformer(archive_handle->src_fd, unpack_gz_stream, "gunzip");
+       archive_handle->offset = 0;
+       while (get_header_tar(archive_handle) == EXIT_SUCCESS)
+               continue;
+
+       /* 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 (file)
index 0000000..03b1b79
--- /dev/null
@@ -0,0 +1,24 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Small lzma deflate implementation.
+ * Copyright (C) 2006  Aurelien Jacobs <aurel@gnuage.org>
+ *
+ * Licensed under GPL v2, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.h"
+#include "unarchive.h"
+
+char FAST_FUNC get_header_tar_lzma(archive_handle_t *archive_handle)
+{
+       /* Can't lseek over pipes */
+       archive_handle->seek = seek_by_read;
+
+       open_transformer(archive_handle->src_fd, unpack_lzma_stream, "unlzma");
+       archive_handle->offset = 0;
+       while (get_header_tar(archive_handle) == EXIT_SUCCESS)
+               continue;
+
+       /* 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 (file)
index 0000000..6ec2df3
--- /dev/null
@@ -0,0 +1,11 @@
+/* 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 FAST_FUNC 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 (file)
index 0000000..a97a9ce
--- /dev/null
@@ -0,0 +1,10 @@
+/* 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 FAST_FUNC header_skip(const file_header_t *file_header UNUSED_PARAM)
+{
+}
diff --git a/archival/libunarchive/header_verbose_list.c b/archival/libunarchive/header_verbose_list.c
new file mode 100644 (file)
index 0000000..dc31003
--- /dev/null
@@ -0,0 +1,58 @@
+/* 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 FAST_FUNC header_verbose_list(const file_header_t *file_header)
+{
+       struct tm *mtime = localtime(&(file_header->mtime));
+
+#if ENABLE_FEATURE_TAR_UNAME_GNAME
+       char uid[8];
+       char gid[8];
+       char *user = file_header->uname;
+       char *group = file_header->gname;
+
+       if (user == NULL) {
+               snprintf(uid, sizeof(uid), "%u", (unsigned)file_header->uid);
+               user = uid;
+       }
+       if (group == NULL) {
+               snprintf(gid, sizeof(gid), "%u", (unsigned)file_header->gid);
+               group = gid;
+       }
+       printf("%s %s/%s %9"OFF_FMT"u %4u-%02u-%02u %02u:%02u:%02u %s",
+               bb_mode_string(file_header->mode),
+               user,
+               group,
+               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);
+#else /* !FEATURE_TAR_UNAME_GNAME */
+       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);
+#endif /* FEATURE_TAR_UNAME_GNAME */
+
+       if (file_header->link_target) {
+               printf(" -> %s", file_header->link_target);
+       }
+       bb_putchar('\n');
+}
diff --git a/archival/libunarchive/init_handle.c b/archival/libunarchive/init_handle.c
new file mode 100644 (file)
index 0000000..ff7d484
--- /dev/null
@@ -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* FAST_FUNC 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 (file)
index 0000000..42fdd96
--- /dev/null
@@ -0,0 +1,64 @@
+/* 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"
+
+/* transformer(), more than meets the eye */
+/*
+ * On MMU machine, the transform_prog is removed by macro magic
+ * in include/unarchive.h. On NOMMU, transformer is removed.
+ */
+void FAST_FUNC open_transformer(int fd,
+       USE_DESKTOP(long long) int FAST_FUNC (*transformer)(int src_fd, int dst_fd),
+       const char *transform_prog)
+{
+       struct fd_pair fd_pipe;
+       int pid;
+
+       xpiped_pair(fd_pipe);
+
+#if BB_MMU
+       pid = fork();
+       if (pid == -1)
+               bb_perror_msg_and_die("vfork" + 1);
+#else
+       pid = vfork();
+       if (pid == -1)
+               bb_perror_msg_and_die("vfork");
+#endif
+
+       if (pid == 0) {
+               /* child process */
+               close(fd_pipe.rd); /* we don't want to read from the parent */
+               // FIXME: error check?
+#if BB_MMU
+               transformer(fd, fd_pipe.wr);
+               if (ENABLE_FEATURE_CLEAN_UP) {
+                       close(fd_pipe.wr); /* send EOF */
+                       close(fd);
+               }
+               /* must be _exit! bug was actually seen here */
+               _exit(EXIT_SUCCESS);
+#else
+               {
+                       char *argv[4];
+                       xmove_fd(fd, 0);
+                       xmove_fd(fd_pipe.wr, 1);
+                       argv[0] = (char*)transform_prog;
+                       argv[1] = (char*)"-cf";
+                       argv[2] = (char*)"-";
+                       argv[3] = NULL;
+                       BB_EXECVP(transform_prog, argv);
+                       bb_perror_msg_and_die("can't exec %s", transform_prog);
+               }
+#endif
+               /* notreached */
+       }
+
+       /* parent process */
+       close(fd_pipe.wr); /* don't want to write to the child */
+       xmove_fd(fd_pipe.rd, fd);
+}
diff --git a/archival/libunarchive/seek_by_jump.c b/archival/libunarchive/seek_by_jump.c
new file mode 100644 (file)
index 0000000..0a259c9
--- /dev/null
@@ -0,0 +1,19 @@
+/* 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 FAST_FUNC seek_by_jump(const archive_handle_t *archive_handle, unsigned amount)
+{
+       if (amount
+        && lseek(archive_handle->src_fd, (off_t) amount, SEEK_CUR) == (off_t) -1
+       ) {
+               if (errno == ESPIPE)
+                       seek_by_read(archive_handle, amount);
+               else
+                       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 (file)
index 0000000..2326a75
--- /dev/null
@@ -0,0 +1,16 @@
+/* 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"
+
+/*  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 FAST_FUNC seek_by_read(const archive_handle_t *archive_handle, unsigned jump_size)
+{
+       if (jump_size)
+               bb_copyfd_exact_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 (file)
index 0000000..dc2eec2
--- /dev/null
@@ -0,0 +1,21 @@
+/* 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 FAST_FUNC unpack_ar_archive(archive_handle_t *ar_archive)
+{
+       char magic[7];
+
+       xread(ar_archive->src_fd, magic, 7);
+       if (strncmp(magic, "!<arch>", 7) != 0) {
+               bb_error_msg_and_die("invalid ar magic");
+       }
+       ar_archive->offset += 7;
+
+       while (get_header_ar(ar_archive) == EXIT_SUCCESS)
+               continue;
+}
diff --git a/archival/rpm.c b/archival/rpm.c
new file mode 100644 (file)
index 0000000..4c36067
--- /dev/null
@@ -0,0 +1,407 @@
+/* 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 "libbb.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 TAG_NAME                1000
+#define TAG_VERSION             1001
+#define TAG_RELEASE             1002
+#define TAG_SUMMARY             1004
+#define TAG_DESCRIPTION         1005
+#define TAG_BUILDTIME           1006
+#define TAG_BUILDHOST           1007
+#define TAG_SIZE                1009
+#define TAG_VENDOR              1011
+#define TAG_LICENSE             1014
+#define TAG_PACKAGER            1015
+#define TAG_GROUP               1016
+#define TAG_URL                 1020
+#define TAG_PREIN               1023
+#define TAG_POSTIN              1024
+#define TAG_FILEFLAGS           1037
+#define TAG_FILEUSERNAME        1039
+#define TAG_FILEGROUPNAME       1040
+#define TAG_SOURCERPM           1044
+#define TAG_PREINPROG           1085
+#define TAG_POSTINPROG          1086
+#define TAG_PREFIXS             1098
+#define TAG_DIRINDEXES          1116
+#define TAG_BASENAMES           1117
+#define TAG_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_getstr(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) MAIN_EXTERNALLY_VISIBLE;
+int rpm_main(int argc, char **argv)
+{
+       int opt = 0, func = 0, rpm_fd, offset;
+       const int pagesize = getpagesize();
+
+       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) bb_show_usage();
+                       func = rpm_query;
+                       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();
+               }
+       }
+       argv += optind;
+       //argc -= optind;
+       if (!argv[0]) bb_show_usage();
+
+       while (*argv) {
+               rpm_fd = xopen(*argv++, O_RDONLY);
+               mytags = rpm_gettags(rpm_fd, &tagcount);
+               if (!mytags)
+                       bb_error_msg_and_die("error reading rpm header");
+               offset = xlseek(rpm_fd, 0, SEEK_CUR);
+               /* Mimimum is one page */
+               map = mmap(0, offset > pagesize ? (offset + offset % pagesize) : pagesize, PROT_READ, MAP_PRIVATE, rpm_fd, 0);
+
+               if (func & rpm_install) {
+                       /* Backup any config files */
+                       loop_through_files(TAG_BASENAMES, fileaction_dobackup);
+                       /* Extact the archive */
+                       extract_cpio_gz(rpm_fd);
+                       /* Set the correct file uid/gid's */
+                       loop_through_files(TAG_BASENAMES, fileaction_setowngrp);
+               }
+               else if ((func & (rpm_query|rpm_query_package)) == (rpm_query|rpm_query_package)) {
+                       if (!(func & (rpm_query_info|rpm_query_list))) {
+                               /* If just a straight query, just give package name */
+                               printf("%s-%s-%s\n", rpm_getstr(TAG_NAME, 0), rpm_getstr(TAG_VERSION, 0), rpm_getstr(TAG_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_getstr(TAG_NAME, 0), rpm_getstr(TAG_PREFIXS, 0) ? rpm_getstr(TAG_PREFIXS, 0) : "(not relocateable)");
+                               printf("Version     : %-34sVendor: %s\n", rpm_getstr(TAG_VERSION, 0), rpm_getstr(TAG_VENDOR, 0) ? rpm_getstr(TAG_VENDOR, 0) : "(none)");
+                               bdate_time = rpm_getint(TAG_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_getstr(TAG_RELEASE, 0), bdatestring);
+                               printf("Install date: %-30sBuild Host: %s\n", "(not installed)", rpm_getstr(TAG_BUILDHOST, 0));
+                               printf("Group       : %-30sSource RPM: %s\n", rpm_getstr(TAG_GROUP, 0), rpm_getstr(TAG_SOURCERPM, 0));
+                               printf("Size        : %-33dLicense: %s\n", rpm_getint(TAG_SIZE, 0), rpm_getstr(TAG_LICENSE, 0));
+                               printf("URL         : %s\n", rpm_getstr(TAG_URL, 0));
+                               printf("Summary     : %s\n", rpm_getstr(TAG_SUMMARY, 0));
+                               printf("Description :\n%s\n", rpm_getstr(TAG_DESCRIPTION, 0));
+                       }
+                       if (func & rpm_query_list) {
+                               int count, it, flags;
+                               count = rpm_getcount(TAG_BASENAMES);
+                               for (it = 0; it < count; it++) {
+                                       flags = rpm_getint(TAG_FILEFLAGS, it);
+                                       switch (func & (rpm_query_list_doc|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|RPMFILE_DOC))) continue;
+                                               break;
+                                       }
+                                       printf("%s%s\n",
+                                               rpm_getstr(TAG_DIRNAMES, rpm_getint(TAG_DIRINDEXES, it)),
+                                               rpm_getstr(TAG_BASENAMES, it));
+                               }
+                       }
+               }
+               free(mytags);
+       }
+       return 0;
+}
+
+static void extract_cpio_gz(int fd)
+{
+       archive_handle_t *archive_handle;
+       unsigned char magic[2];
+#if BB_MMU
+       USE_DESKTOP(long long) int FAST_FUNC (*xformer)(int src_fd, int dst_fd);
+       enum { xformer_prog = 0 };
+#else
+       enum { xformer = 0 };
+       const char *xformer_prog;
+#endif
+
+       /* Initialize */
+       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->ah_flags = ARCHIVE_PRESERVE_DATE | ARCHIVE_CREATE_LEADING_DIRS
+               /* compat: overwrite existing files.
+                * try "rpm -i foo.src.rpm" few times in a row -
+                * standard rpm will not complain.
+                * (TODO? real rpm creates "file;1234" and then renames it) */
+               | ARCHIVE_EXTRACT_UNCONDITIONAL;
+       archive_handle->src_fd = fd;
+       /*archive_handle->offset = 0; - init_handle() did it */
+
+// TODO: open_zipped does the same
+
+       xread(archive_handle->src_fd, &magic, 2);
+#if BB_MMU
+       xformer = unpack_gz_stream;
+#else
+       xformer_prog = "gunzip";
+#endif
+       if (magic[0] != 0x1f || magic[1] != 0x8b) {
+               if (!ENABLE_FEATURE_SEAMLESS_BZ2
+                || magic[0] != 'B' || magic[1] != 'Z'
+               ) {
+                       bb_error_msg_and_die("no gzip"
+                               USE_FEATURE_SEAMLESS_BZ2("/bzip2")
+                               " magic");
+               }
+#if BB_MMU
+               xformer = unpack_bz2_stream;
+#else
+               xformer_prog = "bunzip2";
+#endif
+       } else {
+#if !BB_MMU
+               /* NOMMU version of open_transformer execs an external unzipper that should
+                * have the file position at the start of the file */
+               xlseek(archive_handle->src_fd, 0, SEEK_SET);
+#endif
+       }
+
+       xchdir("/"); /* Install RPM's to root */
+       open_transformer(archive_handle->src_fd, xformer, xformer_prog);
+       archive_handle->offset = 0;
+       while (get_header_cpio(archive_handle) == EXIT_SUCCESS)
+               continue;
+}
+
+
+static rpm_index **rpm_gettags(int fd, int *num_tags)
+{
+       /* We should never need mode than 200, and realloc later */
+       rpm_index **tags = xzalloc(200 * sizeof(tags[0]));
+       int pass, tagindex = 0;
+
+       xlseek(fd, 96, SEEK_CUR); /* Seek past the unused lead */
+
+       /* 1st pass is the signature headers, 2nd is the main stuff */
+       for (pass = 0; pass < 2; pass++) {
+               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;
+               struct BUG_header {
+                       char BUG_header[sizeof(header) == 16 ? 1 : -1];
+               };
+               rpm_index *tmpindex;
+               int storepos;
+
+               xread(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 = xlseek(fd,0,SEEK_CUR) + header.entries * 16;
+
+               while (header.entries--) {
+                       tmpindex = tags[tagindex++] = xmalloc(sizeof(*tmpindex));
+                       xread(fd, tmpindex, sizeof(*tmpindex));
+                       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;
+               }
+               xlseek(fd, header.size, SEEK_CUR); /* Seek past store */
+               /* Skip padding to 8 byte boundary after reading signature headers */
+               if (pass == 0)
+                       xlseek(fd, (8 - (xlseek(fd,0,SEEK_CUR) % 8)) % 8, SEEK_CUR);
+       }
+       tags = xrealloc(tags, tagindex * sizeof(tags[0])); /* 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;
+       return found[0]->count;
+}
+
+static char *rpm_getstr(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;
+       }
+       return NULL;
+}
+
+static int rpm_getint(int tag, int itemindex)
+{
+       rpm_index **found;
+       int *tmpint; /* NB: using int8_t* would be easier to code */
+
+       /* 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) {
+               tmpint = (int *) ((char *) tmpint + itemindex*4);
+               /*return ntohl(*tmpint);*/
+               /* int can be != int32_t */
+               return ntohl(*(int32_t*)tmpint);
+       }
+       if (found[0]->type == RPM_INT16_TYPE) {
+               tmpint = (int *) ((char *) tmpint + itemindex*2);
+               /* ??? read int, and THEN ntohs() it?? */
+               /*return ntohs(*tmpint);*/
+               return ntohs(*(int16_t*)tmpint);
+       }
+       if (found[0]->type == RPM_INT8_TYPE) {
+               tmpint = (int *) ((char *) tmpint + itemindex);
+               /* ??? why we don't read byte here??? */
+               /*return ntohs(*tmpint);*/
+               return *(int8_t*)tmpint;
+       }
+       return -1;
+}
+
+static void fileaction_dobackup(char *filename, int fileref)
+{
+       struct stat oldfile;
+       int stat_res;
+       char *newname;
+       if (rpm_getint(TAG_FILEFLAGS, fileref) & RPMFILE_CONFIG) {
+               /* Only need to backup config files */
+               stat_res = lstat(filename, &oldfile);
+               if (stat_res == 0 && S_ISREG(oldfile.st_mode)) {
+                       /* File already exists  - really should check MD5's etc to see if different */
+                       newname = xasprintf("%s.rpmorig", filename);
+                       copy_file(filename, newname, FILEUTILS_RECUR | FILEUTILS_PRESERVE_STATUS);
+                       remove_file(filename, FILEUTILS_RECUR | FILEUTILS_FORCE);
+                       free(newname);
+               }
+       }
+}
+
+static void fileaction_setowngrp(char *filename, int fileref)
+{
+       /* real rpm warns: "user foo does not exist - using <you>" */
+       struct passwd *pw = getpwnam(rpm_getstr(TAG_FILEUSERNAME, fileref));
+       int uid = pw ? pw->pw_uid : getuid(); /* or euid? */
+       struct group *gr = getgrnam(rpm_getstr(TAG_FILEGROUPNAME, fileref));
+       int gid = gr ? gr->gr_gid : getgid();
+       chown(filename, uid, gid);
+}
+
+static void loop_through_files(int filetag, void (*fileaction)(char *filename, int fileref))
+{
+       int count = 0;
+       while (rpm_getstr(filetag, count)) {
+               char* filename = xasprintf("%s%s",
+                       rpm_getstr(TAG_DIRNAMES, rpm_getint(TAG_DIRINDEXES, count)),
+                       rpm_getstr(TAG_BASENAMES, count));
+               fileaction(filename, count++);
+               free(filename);
+       }
+}
diff --git a/archival/rpm2cpio.c b/archival/rpm2cpio.c
new file mode 100644 (file)
index 0000000..ee93871
--- /dev/null
@@ -0,0 +1,89 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Mini rpm2cpio implementation for busybox
+ *
+ * Copyright (C) 2001 by Laurence Anderson
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+#include "libbb.h"
+#include "unarchive.h"
+
+#define RPM_MAGIC "\355\253\356\333"
+#define RPM_HEADER_MAGIC "\216\255\350"
+
+struct rpm_lead {
+       unsigned char magic[4];
+       uint8_t major, minor;
+       uint16_t type;
+       uint16_t archnum;
+       char name[66];
+       uint16_t osnum;
+       uint16_t signature_type;
+       char reserved[16];
+};
+
+struct rpm_header {
+       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) */
+};
+
+static void skip_header(int rpm_fd)
+{
+       struct rpm_header header;
+
+       xread(rpm_fd, &header, sizeof(struct rpm_header));
+       if (strncmp((char *) &header.magic, RPM_HEADER_MAGIC, 3) != 0) {
+               bb_error_msg_and_die("invalid RPM header magic"); /* Invalid magic */
+       }
+       if (header.version != 1) {
+               bb_error_msg_and_die("unsupported RPM header version"); /* This program only supports v1 headers */
+       }
+       header.entries = ntohl(header.entries);
+       header.size = ntohl(header.size);
+       lseek (rpm_fd, 16 * header.entries, SEEK_CUR); /* Seek past index entries */
+       lseek (rpm_fd, header.size, SEEK_CUR); /* Seek past store */
+}
+
+/* No getopt required */
+int rpm2cpio_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int rpm2cpio_main(int argc, char **argv)
+{
+       struct rpm_lead lead;
+       int rpm_fd;
+       unsigned char magic[2];
+
+       if (argc == 1) {
+               rpm_fd = STDIN_FILENO;
+       } else {
+               rpm_fd = xopen(argv[1], O_RDONLY);
+       }
+
+       xread(rpm_fd, &lead, sizeof(struct rpm_lead));
+       if (strncmp((char *) &lead.magic, RPM_MAGIC, 4) != 0) {
+               bb_error_msg_and_die("invalid RPM magic"); /* Just check the magic, the rest is irrelevant */
+       }
+
+       /* Skip the signature header */
+       skip_header(rpm_fd);
+       lseek(rpm_fd, (8 - (lseek(rpm_fd, 0, SEEK_CUR) % 8)) % 8, SEEK_CUR);
+
+       /* Skip the main header */
+       skip_header(rpm_fd);
+
+       xread(rpm_fd, &magic, 2);
+       if ((magic[0] != 0x1f) || (magic[1] != 0x8b)) {
+               bb_error_msg_and_die("invalid gzip magic");
+       }
+
+       if (unpack_gz_stream(rpm_fd, STDOUT_FILENO) < 0) {
+               bb_error_msg("error inflating");
+       }
+
+       close(rpm_fd);
+
+       return 0;
+}
diff --git a/archival/tar.c b/archival/tar.c
new file mode 100644 (file)
index 0000000..eeaf358
--- /dev/null
@@ -0,0 +1,989 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Mini tar implementation for busybox
+ *
+ * Modified to use common extraction code used by ar, cpio, dpkg-deb, dpkg
+ *  by Glenn McGrath
+ *
+ * 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 <andersen@codepoet.org>
+ *
+ * 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 <fnmatch.h>
+#include "libbb.h"
+#include "unarchive.h"
+
+/* FIXME: Stop using this non-standard feature */
+#ifndef FNM_LEADING_DIR
+#define FNM_LEADING_DIR 0
+#endif
+
+
+#define block_buf bb_common_bufsiz1
+
+
+#if !ENABLE_FEATURE_SEAMLESS_GZ && !ENABLE_FEATURE_SEAMLESS_BZ2
+/* Do not pass gzip flag to writeTarFile() */
+#define writeTarFile(tar_fd, verboseFlag, dereferenceFlag, include, exclude, gzip) \
+       writeTarFile(tar_fd, verboseFlag, dereferenceFlag, include, exclude)
+#endif
+
+
+#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"
+typedef struct TarHeader TarHeader;
+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 */
+       /* POSIX:   "ustar" NUL "00" */
+       /* GNU tar: "ustar  " NUL */
+       /* Normally it's defined as magic[6] followed by
+        * version[2], but we put them together to save code.
+        */
+       char magic[8];            /* 257-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 TAR_BLOCK_SIZE) */
+};
+
+/*
+** 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 */
+typedef struct TarBallInfo TarBallInfo;
+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 */
+};
+
+/* 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;
+       }
+}
+
+/* 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)
+{
+       /* POSIX says that checksum is done on unsigned bytes
+        * (Sun and HP-UX gets it wrong... more details in
+        * GNU tar source) */
+       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<NUL>', pads */
+       /* dir=1: writes 'name', writes '/<NUL>', 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 = xmalloc_readlink_or_warn(fileName);
+               if (!lpath)
+                       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 archive goes to stdout, verbose goes to stderr */
+               if (tbInfo->tarFd == STDOUT_FILENO)
+                       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 FAST_FUNC writeFileToTarball(const char *fileName, struct stat *statbuf,
+                       void *userData, int depth UNUSED_PARAM)
+{
+       struct TarBallInfo *tbInfo = (struct TarBallInfo *) userData;
+       const char *header_name;
+       int inputFileFd = -1;
+
+       /* Strip leading '/' (must be before memorizing hardlink's name) */
+       header_name = fileName;
+       while (header_name[0] == '/') {
+               static smallint warned;
+
+               if (!warned) {
+                       bb_error_msg("removing leading '/' from member names");
+                       warned = 1;
+               }
+               header_name++;
+       }
+
+       if (header_name[0] == '\0')
+               return TRUE;
+
+       /* It is against the rules to archive a socket */
+       if (S_ISSOCK(statbuf->st_mode)) {
+               bb_error_msg("%s: socket ignored", fileName);
+               return TRUE;
+       }
+
+       /*
+        * 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, header_name);
+       }
+
+       /* 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;
+       }
+
+       if (exclude_file(tbInfo->excludeList, header_name))
+               return SKIP;
+
+#if !ENABLE_FEATURE_TAR_GNU_EXTENSIONS
+       if (strlen(header_name) >= NAME_SIZE) {
+               bb_error_msg("names longer than "NAME_SIZE_STR" chars not supported");
+               return TRUE;
+       }
+#endif
+
+       /* 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_or_warn(fileName, O_RDONLY);
+               if (inputFileFd < 0) {
+                       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) {
+               size_t readSize;
+               /* Write the file to the archive. */
+               /* We record size into header first, */
+               /* and then write out file. If file shrinks in between, */
+               /* tar will be corrupted. So we don't allow for that. */
+               /* NB: GNU tar 1.16 warns and pads with zeroes */
+               /* or even seeks back and updates header */
+               bb_copyfd_exact_size(inputFileFd, tbInfo->tarFd, statbuf->st_size);
+               ////off_t readSize;
+               ////readSize = bb_copyfd_size(inputFileFd, tbInfo->tarFd, statbuf->st_size);
+               ////if (readSize != statbuf->st_size && readSize >= 0) {
+               ////    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)statbuf->st_size) & (TAR_BLOCK_SIZE-1);
+               memset(block_buf, 0, readSize);
+               xwrite(tbInfo->tarFd, block_buf, readSize);
+       }
+
+       return TRUE;
+}
+
+#if ENABLE_FEATURE_SEAMLESS_GZ || ENABLE_FEATURE_SEAMLESS_BZ2
+#if !(ENABLE_FEATURE_SEAMLESS_GZ && ENABLE_FEATURE_SEAMLESS_BZ2)
+#define vfork_compressor(tar_fd, gzip) vfork_compressor(tar_fd)
+#endif
+/* Don't inline: vfork scares gcc and pessimizes code */
+static void NOINLINE vfork_compressor(int tar_fd, int gzip)
+{
+       pid_t gzipPid;
+#if ENABLE_FEATURE_SEAMLESS_GZ && ENABLE_FEATURE_SEAMLESS_BZ2
+       const char *zip_exec = (gzip == 1) ? "gzip" : "bzip2";
+#elif ENABLE_FEATURE_SEAMLESS_GZ
+       const char *zip_exec = "gzip";
+#else /* only ENABLE_FEATURE_SEAMLESS_BZ2 */
+       const char *zip_exec = "bzip2";
+#endif
+       // On Linux, vfork never unpauses parent early, although standard
+       // allows for that. Do we want to waste bytes checking for it?
+#define WAIT_FOR_CHILD 0
+       volatile int vfork_exec_errno = 0;
+       struct fd_pair gzipDataPipe;
+#if WAIT_FOR_CHILD
+       struct fd_pair gzipStatusPipe;
+       xpiped_pair(gzipStatusPipe);
+#endif
+       xpiped_pair(gzipDataPipe);
+
+       signal(SIGPIPE, SIG_IGN); /* we only want EPIPE on errors */
+
+#if defined(__GNUC__) && __GNUC__
+       /* Avoid vfork clobbering */
+       (void) &zip_exec;
+#endif
+
+       gzipPid = vfork();
+       if (gzipPid < 0)
+               bb_perror_msg_and_die("vfork");
+
+       if (gzipPid == 0) {
+               /* child */
+               /* NB: close _first_, then move fds! */
+               close(gzipDataPipe.wr);
+#if WAIT_FOR_CHILD
+               close(gzipStatusPipe.rd);
+               /* gzipStatusPipe.wr will close only on exec -
+                * parent waits for this close to happen */
+               fcntl(gzipStatusPipe.wr, F_SETFD, FD_CLOEXEC);
+#endif
+               xmove_fd(gzipDataPipe.rd, 0);
+               xmove_fd(tar_fd, 1);
+               /* exec gzip/bzip2 program/applet */
+               BB_EXECLP(zip_exec, zip_exec, "-f", NULL);
+               vfork_exec_errno = errno;
+               _exit(EXIT_FAILURE);
+       }
+
+       /* parent */
+       xmove_fd(gzipDataPipe.wr, tar_fd);
+       close(gzipDataPipe.rd);
+#if WAIT_FOR_CHILD
+       close(gzipStatusPipe.wr);
+       while (1) {
+               char buf;
+               int n;
+
+               /* Wait until child execs (or fails to) */
+               n = full_read(gzipStatusPipe.rd, &buf, 1);
+               if (n < 0 /* && errno == EAGAIN */)
+                       continue;       /* try it again */
+       }
+       close(gzipStatusPipe.rd);
+#endif
+       if (vfork_exec_errno) {
+               errno = vfork_exec_errno;
+               bb_perror_msg_and_die("cannot exec %s", zip_exec);
+       }
+}
+#endif /* ENABLE_FEATURE_SEAMLESS_GZ || ENABLE_FEATURE_SEAMLESS_BZ2 */
+
+
+/* gcc 4.2.1 inlines it, making code bigger */
+static NOINLINE int writeTarFile(int tar_fd, int verboseFlag,
+       int dereferenceFlag, const llist_t *include,
+       const llist_t *exclude, int gzip)
+{
+       int errorFlag = FALSE;
+       struct TarBallInfo tbInfo;
+
+       tbInfo.hlInfoHead = NULL;
+       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_SEAMLESS_GZ || ENABLE_FEATURE_SEAMLESS_BZ2
+       if (gzip)
+               vfork_compressor(tbInfo.tarFd, gzip);
+#endif
+
+       tbInfo.excludeList = exclude;
+
+       /* Read the directory/files and iterate over them one at a time */
+       while (include) {
+               if (!recursive_action(include->data, ACTION_RECURSE |
+                               (dereferenceFlag ? ACTION_FOLLOWLINKS : 0),
+                               writeFileToTarball, writeFileToTarball, &tbInfo, 0))
+               {
+                       errorFlag = TRUE;
+               }
+               include = include->link;
+       }
+       /* Write two empty blocks to the end of the archive */
+       memset(block_buf, 0, 2*TAR_BLOCK_SIZE);
+       xwrite(tbInfo.tarFd, block_buf, 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 ENABLE_FEATURE_SEAMLESS_GZ || ENABLE_FEATURE_SEAMLESS_BZ2
+       if (gzip) {
+               int status;
+               if (safe_waitpid(-1, &status, 0) == -1)
+                       bb_perror_msg("waitpid");
+               else if (!WIFEXITED(status) || WEXITSTATUS(status))
+                       /* gzip was killed or has exited with nonzero! */
+                       errorFlag = TRUE;
+       }
+#endif
+       return errorFlag;
+}
+#else
+int writeTarFile(int tar_fd, int verboseFlag,
+       int dereferenceFlag, const llist_t *include,
+       const llist_t *exclude, int gzip);
+#endif /* FEATURE_TAR_CREATE */
+
+#if ENABLE_FEATURE_TAR_FROM
+static llist_t *append_file_list_to_list(llist_t *list)
+{
+       FILE *src_stream;
+       char *line;
+       llist_t *newlist = NULL;
+
+       while (list) {
+               src_stream = xfopen_for_read(llist_pop(&list));
+               while ((line = xmalloc_fgetline(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_SEAMLESS_Z
+static char FAST_FUNC 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");
+       }
+
+       open_transformer(archive_handle->src_fd, unpack_Z_stream, "uncompress");
+       archive_handle->offset = 0;
+       while (get_header_tar(archive_handle) == EXIT_SUCCESS)
+               continue;
+
+       /* Can only do one file at a time */
+       return EXIT_FAILURE;
+}
+#else
+#define get_header_tar_Z NULL
+#endif
+
+#ifdef CHECK_FOR_CHILD_EXITCODE
+/* Looks like it isn't needed - tar detects malformed (truncated)
+ * archive if e.g. bunzip2 fails */
+static int child_error;
+
+static void handle_SIGCHLD(int status)
+{
+       /* Actually, 'status' is a signo. We reuse it for other needs */
+
+       /* Wait for any child without blocking */
+       if (wait_any_nohang(&status) < 0)
+               /* wait failed?! I'm confused... */
+               return;
+
+       if (WIFEXITED(status) && WEXITSTATUS(status)==0)
+               /* child exited with 0 */
+               return;
+       /* Cannot happen?
+       if (!WIFSIGNALED(status) && !WIFEXITED(status)) return; */
+       child_error = 1;
+}
+#endif
+
+enum {
+       OPTBIT_KEEP_OLD = 7,
+       USE_FEATURE_TAR_CREATE(   OPTBIT_CREATE      ,)
+       USE_FEATURE_TAR_CREATE(   OPTBIT_DEREFERENCE ,)
+       USE_FEATURE_SEAMLESS_BZ2( OPTBIT_BZIP2       ,)
+       USE_FEATURE_SEAMLESS_LZMA(OPTBIT_LZMA        ,)
+       USE_FEATURE_TAR_FROM(     OPTBIT_INCLUDE_FROM,)
+       USE_FEATURE_TAR_FROM(     OPTBIT_EXCLUDE_FROM,)
+       USE_FEATURE_SEAMLESS_GZ(  OPTBIT_GZIP        ,)
+       USE_FEATURE_SEAMLESS_Z(   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 << OPTBIT_CREATE      )) + 0, // c
+       OPT_DEREFERENCE  = USE_FEATURE_TAR_CREATE(   (1 << OPTBIT_DEREFERENCE )) + 0, // h
+       OPT_BZIP2        = USE_FEATURE_SEAMLESS_BZ2( (1 << OPTBIT_BZIP2       )) + 0, // j
+       OPT_LZMA         = USE_FEATURE_SEAMLESS_LZMA((1 << OPTBIT_LZMA        )) + 0, // a
+       OPT_INCLUDE_FROM = USE_FEATURE_TAR_FROM(     (1 << OPTBIT_INCLUDE_FROM)) + 0, // T
+       OPT_EXCLUDE_FROM = USE_FEATURE_TAR_FROM(     (1 << OPTBIT_EXCLUDE_FROM)) + 0, // X
+       OPT_GZIP         = USE_FEATURE_SEAMLESS_GZ(  (1 << OPTBIT_GZIP        )) + 0, // z
+       OPT_COMPRESS     = USE_FEATURE_SEAMLESS_Z(   (1 << OPTBIT_COMPRESS    )) + 0, // Z
+       OPT_NOPRESERVE_OWN  = 1 << OPTBIT_NOPRESERVE_OWN , // no-same-owner
+       OPT_NOPRESERVE_PERM = 1 << OPTBIT_NOPRESERVE_PERM, // no-same-permissions
+};
+#if ENABLE_FEATURE_TAR_LONG_OPTIONS
+static const char tar_longopts[] ALIGN1 =
+       "list\0"                No_argument       "t"
+       "extract\0"             No_argument       "x"
+       "directory\0"           Required_argument "C"
+       "file\0"                Required_argument "f"
+       "to-stdout\0"           No_argument       "O"
+       "same-permissions\0"    No_argument       "p"
+       "verbose\0"             No_argument       "v"
+       "keep-old\0"            No_argument       "k"
+# if ENABLE_FEATURE_TAR_CREATE
+       "create\0"              No_argument       "c"
+       "dereference\0"         No_argument       "h"
+# endif
+# if ENABLE_FEATURE_SEAMLESS_BZ2
+       "bzip2\0"               No_argument       "j"
+# endif
+# if ENABLE_FEATURE_SEAMLESS_LZMA
+       "lzma\0"                No_argument       "a"
+# endif
+# if ENABLE_FEATURE_TAR_FROM
+       "files-from\0"          Required_argument "T"
+       "exclude-from\0"        Required_argument "X"
+# endif
+# if ENABLE_FEATURE_SEAMLESS_GZ
+       "gzip\0"                No_argument       "z"
+# endif
+# if ENABLE_FEATURE_SEAMLESS_Z
+       "compress\0"            No_argument       "Z"
+# endif
+       "no-same-owner\0"       No_argument       "\xfd"
+       "no-same-permissions\0" No_argument       "\xfe"
+       /* --exclude takes next bit position in option mask, */
+       /* therefore we have to either put it _after_ --no-same-perm */
+       /* or add OPT[BIT]_EXCLUDE before OPT[BIT]_NOPRESERVE_OWN */
+# if ENABLE_FEATURE_TAR_FROM
+       "exclude\0"             Required_argument "\xff"
+# endif
+       ;
+#endif
+
+int tar_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int tar_main(int argc UNUSED_PARAM, char **argv)
+{
+       char FAST_FUNC (*get_header_ptr)(archive_handle_t *) = get_header_tar;
+       archive_handle_t *tar_handle;
+       char *base_dir = NULL;
+       const char *tar_filename = "-";
+       unsigned opt;
+       int verboseFlag = 0;
+#if ENABLE_FEATURE_TAR_LONG_OPTIONS && ENABLE_FEATURE_TAR_FROM
+       llist_t *excludes = NULL;
+#endif
+
+       /* Initialise default values */
+       tar_handle = init_handle();
+       tar_handle->ah_flags = ARCHIVE_CREATE_LEADING_DIRS
+                            | ARCHIVE_PRESERVE_DATE
+                            | ARCHIVE_EXTRACT_UNCONDITIONAL;
+
+       /* Apparently only root's tar preserves perms (see bug 3844) */
+       if (getuid() != 0)
+               tar_handle->ah_flags |= ARCHIVE_NOPRESERVE_PERM;
+
+       /* 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
+#if ENABLE_FEATURE_TAR_LONG_OPTIONS && ENABLE_FEATURE_TAR_FROM
+               "\xff::" // cumulative lists for --exclude
+#endif
+               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_longopts;
+#endif
+       opt = getopt32(argv,
+               "txC:f:Opvk"
+               USE_FEATURE_TAR_CREATE(   "ch"  )
+               USE_FEATURE_SEAMLESS_BZ2( "j"   )
+               USE_FEATURE_SEAMLESS_LZMA("a"   )
+               USE_FEATURE_TAR_FROM(     "T:X:")
+               USE_FEATURE_SEAMLESS_GZ(  "z"   )
+               USE_FEATURE_SEAMLESS_Z(   "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
+#if ENABLE_FEATURE_TAR_LONG_OPTIONS && ENABLE_FEATURE_TAR_FROM
+               , &excludes // --exclude
+#endif
+               , &verboseFlag // combined count for -t and -v
+               , &verboseFlag // combined count for -t and -v
+               );
+       argv += optind;
+
+       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->ah_flags &= ~ARCHIVE_EXTRACT_UNCONDITIONAL;
+
+       if (opt & OPT_NOPRESERVE_OWN)
+               tar_handle->ah_flags |= ARCHIVE_NOPRESERVE_OWN;
+
+       if (opt & OPT_NOPRESERVE_PERM)
+               tar_handle->ah_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);
+#if ENABLE_FEATURE_TAR_LONG_OPTIONS
+       /* Append excludes to reject */
+       while (excludes) {
+               llist_t *next = excludes->link;
+               excludes->link = tar_handle->reject;
+               tar_handle->reject = excludes;
+               excludes = next;
+       }
+#endif
+       tar_handle->accept = append_file_list_to_list(tar_handle->accept);
+#endif
+
+       /* Setup an array of filenames to work with */
+       /* TODO: This is the same as in ar, separate function ? */
+       while (*argv) {
+               /* kill trailing '/' unless the string is just "/" */
+               char *cp = last_char_is(*argv, '/');
+               if (cp > *argv)
+                       *cp = '\0';
+               llist_add_to_end(&tar_handle->accept, *argv);
+               argv++;
+       }
+
+       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;
+               } else {
+                       tar_stream = stdin;
+                       flags = O_RDONLY;
+               }
+
+               if (LONE_DASH(tar_filename)) {
+                       tar_handle->src_fd = fileno(tar_stream);
+                       tar_handle->seek = seek_by_read;
+               } else {
+                       if (ENABLE_FEATURE_TAR_AUTODETECT && flags == O_RDONLY) {
+                               get_header_ptr = get_header_tar;
+                               tar_handle->src_fd = open_zipped(tar_filename);
+                               if (tar_handle->src_fd < 0)
+                                       bb_perror_msg_and_die("can't open '%s'", tar_filename);
+                       } else {
+                               tar_handle->src_fd = xopen(tar_filename, flags);
+                       }
+               }
+       }
+
+       if (base_dir)
+               xchdir(base_dir);
+
+#ifdef CHECK_FOR_CHILD_EXITCODE
+       /* We need to know whether child (gzip/bzip/etc) exits abnormally */
+       signal(SIGCHLD, handle_SIGCHLD);
+#endif
+
+       /* create an archive */
+       if (opt & OPT_CREATE) {
+#if ENABLE_FEATURE_SEAMLESS_GZ || ENABLE_FEATURE_SEAMLESS_BZ2
+               int zipMode = 0;
+               if (ENABLE_FEATURE_SEAMLESS_GZ && (opt & OPT_GZIP))
+                       zipMode = 1;
+               if (ENABLE_FEATURE_SEAMLESS_BZ2 && (opt & OPT_BZIP2))
+                       zipMode = 2;
+#endif
+               /* NB: writeTarFile() closes tar_handle->src_fd */
+               return writeTarFile(tar_handle->src_fd, verboseFlag, opt & OPT_DEREFERENCE,
+                               tar_handle->accept,
+                               tar_handle->reject, zipMode);
+       }
+
+       while (get_header_ptr(tar_handle) == EXIT_SUCCESS)
+               continue;
+
+       /* 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/unzip.c b/archival/unzip.c
new file mode 100644 (file)
index 0000000..7b47a8a
--- /dev/null
@@ -0,0 +1,565 @@
+/* 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
+ * Zip64 + other methods
+ */
+
+#include "libbb.h"
+#include "unarchive.h"
+
+enum {
+#if BB_BIG_ENDIAN
+       ZIP_FILEHEADER_MAGIC = 0x504b0304,
+       ZIP_CDS_MAGIC        = 0x504b0102,
+       ZIP_CDE_MAGIC        = 0x504b0506,
+       ZIP_DD_MAGIC         = 0x504b0708,
+#else
+       ZIP_FILEHEADER_MAGIC = 0x04034b50,
+       ZIP_CDS_MAGIC        = 0x02014b50,
+       ZIP_CDE_MAGIC        = 0x06054b50,
+       ZIP_DD_MAGIC         = 0x08074b50,
+#endif
+};
+
+#define ZIP_HEADER_LEN 26
+
+typedef union {
+       uint8_t raw[ZIP_HEADER_LEN];
+       struct {
+               uint16_t version;               /* 0-1 */
+               uint16_t flags;                 /* 2-3 */
+               uint16_t method;                /* 4-5 */
+               uint16_t modtime;               /* 6-7 */
+               uint16_t moddate;               /* 8-9 */
+               uint32_t crc32 PACKED;          /* 10-13 */
+               uint32_t cmpsize PACKED;        /* 14-17 */
+               uint32_t ucmpsize PACKED;       /* 18-21 */
+               uint16_t filename_len;          /* 22-23 */
+               uint16_t extra_len;             /* 24-25 */
+       } formatted PACKED;
+} zip_header_t; /* PACKED - gcc 4.2.1 doesn't like it (spews warning) */
+
+/* Check the offset of the last element, not the length.  This leniency
+ * allows for poor packing, whereby the overall struct may be too long,
+ * even though the elements are all in the right place.
+ */
+struct BUG_zip_header_must_be_26_bytes {
+       char BUG_zip_header_must_be_26_bytes[
+               offsetof(zip_header_t, formatted.extra_len) + 2
+                       == ZIP_HEADER_LEN ? 1 : -1];
+};
+
+#define FIX_ENDIANNESS_ZIP(zip_header) do { \
+       (zip_header).formatted.version      = SWAP_LE16((zip_header).formatted.version     ); \
+       (zip_header).formatted.flags        = SWAP_LE16((zip_header).formatted.flags       ); \
+       (zip_header).formatted.method       = SWAP_LE16((zip_header).formatted.method      ); \
+       (zip_header).formatted.modtime      = SWAP_LE16((zip_header).formatted.modtime     ); \
+       (zip_header).formatted.moddate      = SWAP_LE16((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_LE16((zip_header).formatted.filename_len); \
+       (zip_header).formatted.extra_len    = SWAP_LE16((zip_header).formatted.extra_len   ); \
+} while (0)
+
+#define CDS_HEADER_LEN 42
+
+typedef union {
+       uint8_t raw[CDS_HEADER_LEN];
+       struct {
+               /* uint32_t signature; 50 4b 01 02 */
+               uint16_t version_made_by;       /* 0-1 */
+               uint16_t version_needed;        /* 2-3 */
+               uint16_t cds_flags;             /* 4-5 */
+               uint16_t method;                /* 6-7 */
+               uint16_t mtime;                 /* 8-9 */
+               uint16_t mdate;                 /* 10-11 */
+               uint32_t crc32;                 /* 12-15 */
+               uint32_t cmpsize;               /* 16-19 */
+               uint32_t ucmpsize;              /* 20-23 */
+               uint16_t file_name_length;      /* 24-25 */
+               uint16_t extra_field_length;    /* 26-27 */
+               uint16_t file_comment_length;   /* 28-29 */
+               uint16_t disk_number_start;     /* 30-31 */
+               uint16_t internal_file_attributes; /* 32-33 */
+               uint32_t external_file_attributes PACKED; /* 34-37 */
+               uint32_t relative_offset_of_local_header PACKED; /* 38-41 */
+       } formatted PACKED;
+} cds_header_t;
+
+struct BUG_cds_header_must_be_42_bytes {
+       char BUG_cds_header_must_be_42_bytes[
+               offsetof(cds_header_t, formatted.relative_offset_of_local_header) + 4
+                       == CDS_HEADER_LEN ? 1 : -1];
+};
+
+#define FIX_ENDIANNESS_CDS(cds_header) do { \
+       (cds_header).formatted.crc32        = SWAP_LE32((cds_header).formatted.crc32       ); \
+       (cds_header).formatted.cmpsize      = SWAP_LE32((cds_header).formatted.cmpsize     ); \
+       (cds_header).formatted.ucmpsize     = SWAP_LE32((cds_header).formatted.ucmpsize    ); \
+       (cds_header).formatted.file_name_length = SWAP_LE16((cds_header).formatted.file_name_length); \
+       (cds_header).formatted.extra_field_length = SWAP_LE16((cds_header).formatted.extra_field_length); \
+       (cds_header).formatted.file_comment_length = SWAP_LE16((cds_header).formatted.file_comment_length); \
+} while (0)
+
+#define CDE_HEADER_LEN 16
+
+typedef union {
+       uint8_t raw[CDE_HEADER_LEN];
+       struct {
+               /* uint32_t signature; 50 4b 05 06 */
+               uint16_t this_disk_no;
+               uint16_t disk_with_cds_no;
+               uint16_t cds_entries_on_this_disk;
+               uint16_t cds_entries_total;
+               uint32_t cds_size;
+               uint32_t cds_offset;
+               /* uint16_t file_comment_length; */
+               /* .ZIP file comment (variable size) */
+       } formatted PACKED;
+} cde_header_t;
+
+struct BUG_cde_header_must_be_16_bytes {
+       char BUG_cde_header_must_be_16_bytes[
+               sizeof(cde_header_t) == CDE_HEADER_LEN ? 1 : -1];
+};
+
+#define FIX_ENDIANNESS_CDE(cde_header) do { \
+       (cde_header).formatted.cds_offset = SWAP_LE32((cde_header).formatted.cds_offset); \
+} while (0)
+
+enum { zip_fd = 3 };
+
+
+#if ENABLE_DESKTOP
+/* NB: does not preserve file position! */
+static uint32_t find_cds_offset(void)
+{
+       unsigned char buf[1024];
+       cde_header_t cde_header;
+       unsigned char *p;
+       off_t end;
+
+       end = xlseek(zip_fd, 0, SEEK_END);
+       if (end < 1024)
+               end = 1024;
+       end -= 1024;
+       xlseek(zip_fd, end, SEEK_SET);
+       full_read(zip_fd, buf, 1024);
+
+       p = buf;
+       while (p <= buf + 1024 - CDE_HEADER_LEN - 4) {
+               if (*p != 'P') {
+                       p++;
+                       continue;
+               }
+               if (*++p != 'K')
+                       continue;
+               if (*++p != 5)
+                       continue;
+               if (*++p != 6)
+                       continue;
+               /* we found CDE! */
+               memcpy(cde_header.raw, p + 1, CDE_HEADER_LEN);
+               FIX_ENDIANNESS_CDE(cde_header);
+               return cde_header.formatted.cds_offset;
+       }
+       bb_error_msg_and_die("can't find file table");
+};
+
+static uint32_t read_next_cds(int count_m1, uint32_t cds_offset, cds_header_t *cds_ptr)
+{
+       off_t org;
+
+       org = xlseek(zip_fd, 0, SEEK_CUR);
+
+       if (!cds_offset)
+               cds_offset = find_cds_offset();
+
+       while (count_m1-- >= 0) {
+               xlseek(zip_fd, cds_offset + 4, SEEK_SET);
+               xread(zip_fd, cds_ptr->raw, CDS_HEADER_LEN);
+               FIX_ENDIANNESS_CDS(*cds_ptr);
+               cds_offset += 4 + CDS_HEADER_LEN
+                       + cds_ptr->formatted.file_name_length
+                       + cds_ptr->formatted.extra_field_length
+                       + cds_ptr->formatted.file_comment_length;
+       }
+
+       xlseek(zip_fd, org, SEEK_SET);
+       return cds_offset;
+};
+#endif
+
+static void unzip_skip(off_t skip)
+{
+       bb_copyfd_exact_size(zip_fd, -1, skip);
+}
+
+static void unzip_create_leading_dirs(const 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 void unzip_extract(zip_header_t *zip_header, 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_exact_size(zip_fd, dst_fd, size);
+       } else {
+               /* Method 8 - inflate */
+               inflate_unzip_result res;
+               if (inflate_unzip(&res, zip_header->formatted.cmpsize, zip_fd, dst_fd) < 0)
+                       bb_error_msg_and_die("inflate error");
+               /* Validate decompression - crc */
+               if (zip_header->formatted.crc32 != (res.crc ^ 0xffffffffL)) {
+                       bb_error_msg_and_die("crc error");
+               }
+               /* Validate decompression - size */
+               if (zip_header->formatted.ucmpsize != res.bytes_out) {
+                       /* Don't die. Who knows, maybe len calculation
+                        * was botched somewhere. After all, crc matched! */
+                       bb_error_msg("bad length");
+               }
+       }
+}
+
+int unzip_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int unzip_main(int argc, char **argv)
+{
+       enum { O_PROMPT, O_NEVER, O_ALWAYS };
+
+       zip_header_t zip_header;
+       smallint verbose = 1;
+       smallint listing = 0;
+       smallint overwrite = O_PROMPT;
+#if ENABLE_DESKTOP
+       uint32_t cds_offset;
+       unsigned cds_entries;
+#endif
+       unsigned total_size;
+       unsigned total_entries;
+       int dst_fd = -1;
+       char *src_fn = NULL;
+       char *dst_fn = NULL;
+       llist_t *zaccept = NULL;
+       llist_t *zreject = NULL;
+       char *base_dir = NULL;
+       int i, opt;
+       int opt_range = 0;
+       char key_buf[80];
+       struct stat stat_buf;
+
+       /* '-' makes getopt return 1 for non-options */
+       while ((opt = getopt(argc, argv, "-d:lnopqx")) != -1) {
+               switch (opt_range) {
+               case 0: /* Options */
+                       switch (opt) {
+                       case 'l': /* List */
+                               listing = 1;
+                               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 */
+                               verbose = 0;
+                               break;
+
+                       case 1: /* The zip file */
+                               /* +5: space for ".zip" and NUL */
+                               src_fn = xmalloc(strlen(optarg) + 5);
+                               strcpy(src_fn, optarg);
+                               opt_range++;
+                               break;
+
+                       default:
+                               bb_show_usage();
+
+                       }
+                       break;
+
+               case 1: /* Include files */
+                       if (opt == 1) {
+                               llist_add_to(&zaccept, optarg);
+                               break;
+                       }
+                       if (opt == 'd') {
+                               base_dir = optarg;
+                               opt_range += 2;
+                               break;
+                       }
+                       if (opt == 'x') {
+                               opt_range++;
+                               break;
+                       }
+                       bb_show_usage();
+
+               case 2 : /* Exclude files */
+                       if (opt == 1) {
+                               llist_add_to(&zreject, optarg);
+                               break;
+                       }
+                       if (opt == 'd') { /* Extract to base directory */
+                               base_dir = optarg;
+                               opt_range++;
+                               break;
+                       }
+                       /* fall through */
+
+               default:
+                       bb_show_usage();
+               }
+       }
+
+       if (src_fn == NULL) {
+               bb_show_usage();
+       }
+
+       /* Open input file */
+       if (LONE_DASH(src_fn)) {
+               xdup2(STDIN_FILENO, zip_fd);
+               /* Cannot use prompt mode since zip data is arriving on STDIN */
+               if (overwrite == O_PROMPT)
+                       overwrite = O_NEVER;
+       } else {
+               static const char extn[][5] = {"", ".zip", ".ZIP"};
+               int orig_src_fn_len = strlen(src_fn);
+               int src_fd = -1;
+
+               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("can't open %s, %s.zip, %s.ZIP", src_fn, src_fn, src_fn);
+               }
+               xmove_fd(src_fd, zip_fd);
+       }
+
+       /* Change dir if necessary */
+       if (base_dir)
+               xchdir(base_dir);
+
+       if (verbose) {
+               printf("Archive:  %s\n", src_fn);
+               if (listing){
+                       puts("  Length     Date   Time    Name\n"
+                            " --------    ----   ----    ----");
+               }
+       }
+
+       total_size = 0;
+       total_entries = 0;
+#if ENABLE_DESKTOP
+       cds_entries = 0;
+       cds_offset = 0;
+#endif
+       while (1) {
+               uint32_t magic;
+
+               /* Check magic number */
+               xread(zip_fd, &magic, 4);
+               /* Central directory? It's at the end, so exit */
+               if (magic == ZIP_CDS_MAGIC)
+                       break;
+#if ENABLE_DESKTOP
+               /* Data descriptor? It was a streaming file, go on */
+               if (magic == ZIP_DD_MAGIC) {
+                       /* skip over duplicate crc32, cmpsize and ucmpsize */
+                       unzip_skip(3 * 4);
+                       continue;
+               }
+#endif
+               if (magic != ZIP_FILEHEADER_MAGIC)
+                       bb_error_msg_and_die("invalid zip magic %08X", (int)magic);
+
+               /* Read the file header */
+               xread(zip_fd, zip_header.raw, ZIP_HEADER_LEN);
+               FIX_ENDIANNESS_ZIP(zip_header);
+               if ((zip_header.formatted.method != 0) && (zip_header.formatted.method != 8)) {
+                       bb_error_msg_and_die("unsupported method %d", zip_header.formatted.method);
+               }
+#if !ENABLE_DESKTOP
+               if (zip_header.formatted.flags & 0x0009) {
+                       bb_error_msg_and_die("zip flags 1 and 8 are not supported");
+               }
+#else
+               if (zip_header.formatted.flags & 0x0001) {
+                       /* 0x0001 - encrypted */
+                       bb_error_msg_and_die("zip flag 1 (encryption) is not supported");
+               }
+               if (zip_header.formatted.flags & 0x0008) {
+                       cds_header_t cds_header;
+                       /* 0x0008 - streaming. [u]cmpsize can be reliably gotten
+                        * only from Central Directory. See unzip_doc.txt */
+                       cds_offset = read_next_cds(total_entries - cds_entries, cds_offset, &cds_header);
+                       cds_entries = total_entries + 1;
+                       zip_header.formatted.crc32    = cds_header.formatted.crc32;
+                       zip_header.formatted.cmpsize  = cds_header.formatted.cmpsize;
+                       zip_header.formatted.ucmpsize = cds_header.formatted.ucmpsize;
+               }
+#endif
+
+               /* Read filename */
+               free(dst_fn);
+               dst_fn = xzalloc(zip_header.formatted.filename_len + 1);
+               xread(zip_fd, dst_fn, zip_header.formatted.filename_len);
+
+               /* Skip extra header bytes */
+               unzip_skip(zip_header.formatted.extra_len);
+
+               /* Filter zip entries */
+               if (find_list_entry(zreject, dst_fn)
+                || (zaccept && !find_list_entry(zaccept, dst_fn))
+               ) { /* Skip entry */
+                       i = 'n';
+
+               } else { /* Extract entry */
+                       if (listing) { /* List entry */
+                               if (verbose) {
+                                       unsigned 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_size += zip_header.formatted.ucmpsize;
+                               } else {
+                                       /* short listing -- filenames only */
+                                       puts(dst_fn);
+                               }
+                               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("can't stat '%s'", dst_fn);
+                                       }
+                                       if (verbose) {
+                                               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("can't 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, sizeof(key_buf), stdin)) {
+                                                               bb_perror_msg_and_die("can't 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 (verbose) {
+                               printf("  inflating: %s\n", dst_fn);
+                       }
+                       unzip_extract(&zip_header, dst_fd);
+                       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(zip_header.formatted.cmpsize);
+                       break;
+
+               case 'r':
+                       /* Prompt for new name */
+                       printf("new name: ");
+                       if (!fgets(key_buf, sizeof(key_buf), stdin)) {
+                               bb_perror_msg_and_die("can't 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;
+               }
+
+               total_entries++;
+       }
+
+       if (listing && verbose) {
+               printf(" --------                   -------\n"
+                      "%9d                   %d files\n",
+                      total_size, total_entries);
+       }
+
+       return 0;
+}
diff --git a/archival/unzip_doc.txt.bz2 b/archival/unzip_doc.txt.bz2
new file mode 100644 (file)
index 0000000..ab77d10
Binary files /dev/null and b/archival/unzip_doc.txt.bz2 differ
diff --git a/busybox-1.14.3.tar.bz2 b/busybox-1.14.3.tar.bz2
new file mode 100644 (file)
index 0000000..2c38044
Binary files /dev/null and b/busybox-1.14.3.tar.bz2 differ
diff --git a/console-tools/Config.in b/console-tools/Config.in
new file mode 100644 (file)
index 0000000..994140b
--- /dev/null
@@ -0,0 +1,138 @@
+#
+# 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 KBD_MODE
+       bool "kbd_mode"
+       default n
+       help
+         This program reports and sets keyboard mode.
+
+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 SETCONSOLE && GETOPT_LONG
+       help
+         Support long options for the setconsole applet.
+
+config SETFONT
+       bool "setfont"
+       default n
+       help
+         Allows to load console screen map. Useful for i18n.
+
+config FEATURE_SETFONT_TEXTUAL_MAP
+       bool "Support reading textual screen maps"
+       default n
+       depends on SETFONT
+       help
+         Support reading textual screen maps.
+
+config DEFAULT_SETFONT_DIR
+       string "Default directory for console-tools files"
+       default ""
+       depends on SETFONT
+       help
+         Directory to use if setfont's params are simple filenames
+         (not /path/to/file or ./file). Default is "" (no default directory).
+
+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.
+
+config SHOWKEY
+       bool "showkey"
+       default n
+       help
+         Shows keys pressed.
+
+endmenu
diff --git a/console-tools/Kbuild b/console-tools/Kbuild
new file mode 100644 (file)
index 0000000..df5ffdb
--- /dev/null
@@ -0,0 +1,22 @@
+# 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_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_KBD_MODE)         += kbd_mode.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_SETFONT)          += loadfont.o
+lib-$(CONFIG_SETKEYCODES)      += setkeycodes.o
+lib-$(CONFIG_SETLOGCONS)       += setlogcons.o
+lib-$(CONFIG_SHOWKEY)          += showkey.o
diff --git a/console-tools/chvt.c b/console-tools/chvt.c
new file mode 100644 (file)
index 0000000..302ffb4
--- /dev/null
@@ -0,0 +1,24 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Mini chvt 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 "libbb.h"
+
+int chvt_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int chvt_main(int argc, char **argv)
+{
+       int num;
+
+       if (argc != 2) {
+               bb_show_usage();
+       }
+
+       num = xatou_range(argv[1], 1, 63);
+       console_make_active(get_console_fd_or_die(), num);
+       return EXIT_SUCCESS;
+}
diff --git a/console-tools/clear.c b/console-tools/clear.c
new file mode 100644 (file)
index 0000000..8b727b3
--- /dev/null
@@ -0,0 +1,19 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Mini clear 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.
+ *
+ */
+
+/* no options, no getopt */
+
+#include "libbb.h"
+
+int clear_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int clear_main(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
+{
+       return printf("\033[H\033[J") != 6;
+}
diff --git a/console-tools/deallocvt.c b/console-tools/deallocvt.c
new file mode 100644 (file)
index 0000000..0974883
--- /dev/null
@@ -0,0 +1,33 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Disallocate virtual terminal(s)
+ *
+ * Copyright (C) 2003 by Tito Ragusa <farmatito@tiscali.it>
+ * Copyright (C) 1999-2004 by Erik Andersen <andersen@codepoet.org>
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+/* no options, no getopt */
+
+#include "libbb.h"
+
+/* From <linux/vt.h> */
+enum { VT_DISALLOCATE = 0x5608 }; /* free memory associated to vt */
+
+int deallocvt_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int deallocvt_main(int argc UNUSED_PARAM, char **argv)
+{
+       /* num = 0 deallocate all unused consoles */
+       int num = 0;
+
+       if (argv[1]) {
+               if (argv[2])
+                       bb_show_usage();
+               num = xatou_range(argv[1], 1, 63);
+       }
+
+       /* double cast suppresses "cast to ptr from int of different size" */
+       xioctl(get_console_fd_or_die(), VT_DISALLOCATE, (void *)(ptrdiff_t)num);
+       return EXIT_SUCCESS;
+}
diff --git a/console-tools/dumpkmap.c b/console-tools/dumpkmap.c
new file mode 100644 (file)
index 0000000..c382b5a
--- /dev/null
@@ -0,0 +1,69 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Mini dumpkmap implementation for busybox
+ *
+ * Copyright (C) Arne Bernin <arne@matrix.loopback.org>
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ *
+ */
+/* no options, no getopt */
+
+#include "libbb.h"
+
+/* From <linux/kd.h> */
+struct kbentry {
+       unsigned char kb_table;
+       unsigned char kb_index;
+       unsigned short kb_value;
+};
+#define KDGKBENT 0x4B46  /* gets one entry in translation table */
+
+/* From <linux/keyboard.h> */
+#define NR_KEYS 128
+#define MAX_NR_KEYMAPS 256
+
+int dumpkmap_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int dumpkmap_main(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
+{
+       struct kbentry ke;
+       int i, j, fd;
+       RESERVE_CONFIG_BUFFER(flags,MAX_NR_KEYMAPS);
+
+/*     bb_warn_ignoring_args(argc>=2);*/
+
+       fd = get_console_fd_or_die();
+
+       write(STDOUT_FILENO, "bkeymap", 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(STDOUT_FILENO, 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_or_perror(fd, KDGKBENT, &ke,
+                                               "ioctl failed with %s, %s, %p",
+                                               (char *)&ke.kb_index,
+                                               (char *)&ke.kb_table,
+                                               &ke.kb_value)
+                               ) {
+                                       write(STDOUT_FILENO, (void*)&ke.kb_value, 2);
+                               }
+                       }
+               }
+       }
+       if (ENABLE_FEATURE_CLEAN_UP) {
+               close(fd);
+               RELEASE_CONFIG_BUFFER(flags);
+       }
+       return EXIT_SUCCESS;
+}
diff --git a/console-tools/kbd_mode.c b/console-tools/kbd_mode.c
new file mode 100644 (file)
index 0000000..544bbb7
--- /dev/null
@@ -0,0 +1,55 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Mini kbd_mode implementation for busybox
+ *
+ * Copyright (C) 2007 Loic Grenie <loic.grenie@gmail.com>
+ *   written using Andries Brouwer <aeb@cwi.nl>'s kbd_mode from
+ *   console-utils v0.2.3, licensed under GNU GPLv2
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+#include "libbb.h"
+#include <linux/kd.h>
+
+int kbd_mode_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int kbd_mode_main(int argc UNUSED_PARAM, char **argv)
+{
+       enum {
+               SCANCODE  = (1 << 0),
+               ASCII     = (1 << 1),
+               MEDIUMRAW = (1 << 2),
+               UNICODE   = (1 << 3),
+       };
+       int fd;
+       unsigned opt;
+       const char *tty_name = CURRENT_TTY;
+
+       opt = getopt32(argv, "sakuC:", &tty_name);
+       fd = xopen(tty_name, O_NONBLOCK);
+       opt &= 0xf; /* clear -C bit, see (*) */
+
+       if (!opt) { /* print current setting */
+               const char *mode = "unknown";
+               int m;
+
+               xioctl(fd, KDGKBMODE, &m);
+               if (m == K_RAW)
+                       mode = "raw (scancode)";
+               else if (m == K_XLATE)
+                       mode = "default (ASCII)";
+               else if (m == K_MEDIUMRAW)
+                       mode = "mediumraw (keycode)";
+               else if (m == K_UNICODE)
+                       mode = "Unicode (UTF-8)";
+               printf("The keyboard is in %s mode\n", mode);
+       } else {
+               /* here we depend on specific bits assigned to options (*) */
+               opt = opt & UNICODE ? 3 : opt >> 1;
+               /* double cast prevents warnings about widening conversion */
+               xioctl(fd, KDSKBMODE, (void*)(ptrdiff_t)opt);
+       }
+
+       if (ENABLE_FEATURE_CLEAN_UP)
+               close(fd);
+       return EXIT_SUCCESS;
+}
diff --git a/console-tools/loadfont.c b/console-tools/loadfont.c
new file mode 100644 (file)
index 0000000..3364180
--- /dev/null
@@ -0,0 +1,373 @@
+/* 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.)
+ *
+ * Licensed under GPLv2, see file LICENSE in this tarball for details.
+ */
+#include "libbb.h"
+#include <sys/kd.h>
+
+#ifndef KDFONTOP
+#define KDFONTOP 0x4B72
+struct console_font_op {
+       unsigned op;            /* KD_FONT_OP_* */
+       unsigned flags;         /* KD_FONT_FLAG_* */
+       unsigned width, height;
+       unsigned charcount;
+       unsigned char *data;    /* font data with height fixed to 32 */
+};
+
+#define KD_FONT_OP_SET          0  /* Set font */
+#define KD_FONT_OP_GET          1  /* Get font */
+#define KD_FONT_OP_SET_DEFAULT  2  /* Set font to default,
+                                         data points to name / NULL */
+#define KD_FONT_OP_COPY         3  /* Copy from another console */
+
+#define KD_FONT_FLAG_OLD        0x80000000 /* Invoked via old interface */
+#define KD_FONT_FLAG_DONT_RECALC 1 /* Don't call adjust_height() */
+                                   /* (Used internally for PIO_FONT support) */
+#endif /* KDFONTOP */
+
+
+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 do_loadfont(int fd, unsigned char *inbuf, int unit, int fontsize)
+{
+       char *buf;
+       int i;
+
+       if (unit < 1 || unit > 32)
+               bb_error_msg_and_die("bad character size %d", unit);
+
+       buf = xzalloc(16 * 1024);
+       for (i = 0; i < fontsize; i++)
+               memcpy(buf + (32 * i), inbuf + (unit * i), unit);
+
+       { /* KDFONTOP */
+               struct console_font_op cfo;
+
+               cfo.op = KD_FONT_OP_SET;
+               cfo.flags = 0;
+               cfo.width = 8;
+               cfo.height = unit;
+               cfo.charcount = fontsize;
+               cfo.data = (void*)buf;
+#if 0
+               if (!ioctl_or_perror(fd, KDFONTOP, &cfo, "KDFONTOP ioctl failed (will try PIO_FONTX)"))
+                       goto ret;  /* success */
+#else
+               xioctl(fd, KDFONTOP, &cfo);
+#endif
+       }
+
+#if 0
+/* These ones do not honour -C tty (they set font on current tty regardless)
+ * On x86, this distinction is visible on framebuffer consoles
+ * (regular character consoles may have only one shared font anyway)
+ */
+#if defined(PIO_FONTX) && !defined(__sparc__)
+       {
+               struct consolefontdesc cfd;
+
+               cfd.charcount = fontsize;
+               cfd.charheight = unit;
+               cfd.chardata = buf;
+
+               if (!ioctl_or_perror(fd, PIO_FONTX, &cfd, "PIO_FONTX ioctl failed (will try PIO_FONT)"))
+                       goto ret;  /* success */
+       }
+#endif
+       xioctl(fd, PIO_FONT, buf);
+ ret:
+#endif /* 0 */
+       free(buf);
+}
+
+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;
+       uint16_t unicode;
+
+       maxct = tailsz; /* more than enough */
+       up = xmalloc(maxct * sizeof(struct unipair));
+
+       for (glyph = 0; glyph < fontsize; glyph++) {
+               while (tailsz >= 2) {
+                       unicode = (((uint16_t) 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;
+       xioctl(fd, PIO_UNIMAPCLR, &advice);
+       ud.entry_ct = ct;
+       ud.entries = up;
+       xioctl(fd, PIO_UNIMAP, &ud);
+}
+
+static void do_load(int fd, struct psf_header *psfhdr, size_t len)
+{
+       int unit;
+       int fontsize;
+       int hastable;
+       unsigned head0, head = head;
+
+       /* test for psf first */
+       if (len >= sizeof(struct psf_header) && PSF_MAGIC_OK(psfhdr)) {
+               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 > len || (!hastable && head != len))
+                       bb_error_msg_and_die("input file: bad length");
+       } else {
+               /* file with three code pages? */
+               if (len == 9780) {
+                       head0 = 40;
+                       unit = 16;
+               } else {
+                       /* bare font */
+                       if (len & 0377)
+                               bb_error_msg_and_die("input file: bad length");
+                       head0 = 0;
+                       unit = len / 256;
+               }
+               fontsize = 256;
+               hastable = 0;
+       }
+
+       do_loadfont(fd, (unsigned char *)psfhdr + head0, unit, fontsize);
+       if (hastable)
+               do_loadtable(fd, (unsigned char *)psfhdr + head, len - head, fontsize);
+}
+
+#if ENABLE_LOADFONT
+int loadfont_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int loadfont_main(int argc UNUSED_PARAM, char **argv)
+{
+       size_t len;
+       struct psf_header *psfhdr;
+
+       // no arguments allowed!
+       opt_complementary = "=0";
+       getopt32(argv, "");
+
+       /*
+        * We used to look at the length of the input file
+        * with stat(); now that we accept compressed files,
+        * just read the entire file.
+        */
+       len = 32*1024; // can't be larger
+       psfhdr = xmalloc_read(STDIN_FILENO, &len);
+       // xmalloc_open_zipped_read_close(filename, &len);
+       if (!psfhdr)
+               bb_perror_msg_and_die("error reading input font");
+       do_load(get_console_fd_or_die(), psfhdr, len);
+
+       return EXIT_SUCCESS;
+}
+#endif
+
+#if ENABLE_SETFONT
+
+/*
+kbd-1.12:
+
+setfont [-O font+umap.orig] [-o font.orig] [-om cmap.orig]
+[-ou umap.orig] [-N] [font.new ...] [-m cmap] [-u umap] [-C console]
+[-hNN] [-v] [-V]
+
+-h NN  Override font height
+-o file
+       Save previous font in file
+-O file
+       Save previous font and Unicode map in file
+-om file
+       Store console map in file
+-ou file
+       Save previous Unicode map in file
+-m file
+       Load console map or Unicode console map from file
+-u file
+       Load Unicode table describing the font from file
+       Example:
+       # cp866
+       0x00-0x7f       idem
+       #
+       0x80    U+0410  # CYRILLIC CAPITAL LETTER A
+       0x81    U+0411  # CYRILLIC CAPITAL LETTER BE
+       0x82    U+0412  # CYRILLIC CAPITAL LETTER VE
+-C console
+       Set the font for the indicated console
+-v     Verbose
+-V     Version
+*/
+
+#if ENABLE_FEATURE_SETFONT_TEXTUAL_MAP
+static int ctoi(char *s)
+{
+       if (s[0] == '\'' && s[1] != '\0' && s[2] == '\'' && s[3] == '\0')
+               return s[1];
+       // U+ means 0x
+       if (s[0] == 'U' && s[1] == '+') {
+               s[0] = '0';
+               s[1] = 'x';
+       }
+       if (!isdigit(s[0]))
+               return -1;
+       return xstrtoul(s, 0);
+}
+#endif
+
+int setfont_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int setfont_main(int argc UNUSED_PARAM, char **argv)
+{
+       size_t len;
+       unsigned opts;
+       int fd;
+       struct psf_header *psfhdr;
+       char *mapfilename;
+       const char *tty_name = CURRENT_TTY;
+
+       opt_complementary = "=1";
+       opts = getopt32(argv, "m:C:", &mapfilename, &tty_name);
+       argv += optind;
+
+       fd = xopen(tty_name, O_NONBLOCK);
+
+       if (sizeof(CONFIG_DEFAULT_SETFONT_DIR) > 1) { // if not ""
+               if (*argv[0] != '/') {
+                       // goto default fonts location. don't die if doesn't exist
+                       chdir(CONFIG_DEFAULT_SETFONT_DIR "/consolefonts");
+               }
+       }
+       // load font
+       len = 32*1024; // can't be larger
+       psfhdr = xmalloc_open_zipped_read_close(*argv, &len);
+       if (!psfhdr)
+               bb_simple_perror_msg_and_die(*argv);
+       do_load(fd, psfhdr, len);
+
+       // load the screen map, if any
+       if (opts & 1) { // -m
+               unsigned mode = PIO_SCRNMAP;
+               void *map;
+
+               if (sizeof(CONFIG_DEFAULT_SETFONT_DIR) > 1) { // if not ""
+                       if (mapfilename[0] != '/') {
+                               // goto default keymaps location
+                               chdir(CONFIG_DEFAULT_SETFONT_DIR "/consoletrans");
+                       }
+               }
+               // fetch keymap
+               map = xmalloc_open_zipped_read_close(mapfilename, &len);
+               if (!map)
+                       bb_simple_perror_msg_and_die(mapfilename);
+               // file size is 256 or 512 bytes? -> assume binary map
+               if (len == E_TABSZ || len == 2*E_TABSZ) {
+                       if (len == 2*E_TABSZ)
+                               mode = PIO_UNISCRNMAP;
+               }
+#if ENABLE_FEATURE_SETFONT_TEXTUAL_MAP
+               // assume textual Unicode console maps:
+               // 0x00 U+0000  #  NULL (NUL)
+               // 0x01 U+0001  #  START OF HEADING (SOH)
+               // 0x02 U+0002  #  START OF TEXT (STX)
+               // 0x03 U+0003  #  END OF TEXT (ETX)
+               else {
+                       int i;
+                       char *token[2];
+                       parser_t *parser;
+
+                       if (ENABLE_FEATURE_CLEAN_UP)
+                               free(map);
+                       map = xmalloc(E_TABSZ * sizeof(unsigned short));
+
+#define unicodes ((unsigned short *)map)
+                       // fill vanilla map
+                       for (i = 0; i < E_TABSZ; i++)
+                               unicodes[i] = 0xf000 + i;
+
+                       parser = config_open(mapfilename);
+                       while (config_read(parser, token, 2, 2, "# \t", PARSE_NORMAL | PARSE_MIN_DIE)) {
+                               // parse code/value pair
+                               int a = ctoi(token[0]);
+                               int b = ctoi(token[1]);
+                               if (a < 0 || a >= E_TABSZ
+                                || b < 0 || b > 65535
+                               ) {
+                                       bb_error_msg_and_die("map format");
+                               }
+                               // patch map
+                               unicodes[a] = b;
+                               // unicode character is met?
+                               if (b > 255)
+                                       mode = PIO_UNISCRNMAP;
+                       }
+                       if (ENABLE_FEATURE_CLEAN_UP)
+                               config_close(parser);
+
+                       if (mode != PIO_UNISCRNMAP) {
+#define asciis ((unsigned char *)map)
+                               for (i = 0; i < E_TABSZ; i++)
+                                       asciis[i] = unicodes[i];
+#undef asciis
+                       }
+#undef unicodes
+               }
+#endif // ENABLE_FEATURE_SETFONT_TEXTUAL_MAP
+
+               // do set screen map
+               xioctl(fd, mode, map);
+
+               if (ENABLE_FEATURE_CLEAN_UP)
+                       free(map);
+       }
+
+       return EXIT_SUCCESS;
+}
+#endif
diff --git a/console-tools/loadkmap.c b/console-tools/loadkmap.c
new file mode 100644 (file)
index 0000000..ac2c0a6
--- /dev/null
@@ -0,0 +1,65 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Mini loadkmap implementation for busybox
+ *
+ * Copyright (C) 1998 Enrique Zanardi <ezanardi@ull.es>
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+#include "libbb.h"
+
+#define BINARY_KEYMAP_MAGIC "bkeymap"
+
+/* From <linux/kd.h> */
+struct kbentry {
+       unsigned char kb_table;
+       unsigned char kb_index;
+       unsigned short kb_value;
+};
+/* sets one entry in translation table */
+#define KDSKBENT        0x4B47
+
+/* From <linux/keyboard.h> */
+#define NR_KEYS         128
+#define MAX_NR_KEYMAPS  256
+
+int loadkmap_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int loadkmap_main(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
+{
+       struct kbentry ke;
+       int i, j, fd;
+       uint16_t ibuff[NR_KEYS];
+/*     const char *tty_name = CURRENT_TTY; */
+       RESERVE_CONFIG_BUFFER(flags,MAX_NR_KEYMAPS);
+
+/* bb_warn_ignoring_args(argc >= 2); */
+       fd = get_console_fd_or_die();
+/* or maybe:
+       opt = getopt32(argv, "C:", &tty_name);
+       fd = xopen(tty_name, O_NONBLOCK);
+*/
+
+       xread(STDIN_FILENO, flags, 7);
+       if (strncmp(flags, BINARY_KEYMAP_MAGIC, 7))
+               bb_error_msg_and_die("not a valid binary keymap");
+
+       xread(STDIN_FILENO, flags, MAX_NR_KEYMAPS);
+
+       for (i = 0; i < MAX_NR_KEYMAPS; i++) {
+               if (flags[i] == 1) {
+                       xread(STDIN_FILENO, ibuff, NR_KEYS * sizeof(uint16_t));
+                       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);
+               RELEASE_CONFIG_BUFFER(flags);
+       }
+       return EXIT_SUCCESS;
+}
diff --git a/console-tools/openvt.c b/console-tools/openvt.c
new file mode 100644 (file)
index 0000000..0906de4
--- /dev/null
@@ -0,0 +1,181 @@
+/* vi: set sw=4 ts=4: */
+/*
+ *  openvt.c - open a vt to run a command.
+ *
+ *  busyboxed by Quy Tonthat <quy@signal3.com>
+ *  hacked by Tito <farmatito@tiscali.it>
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+#include <linux/vt.h>
+#include "libbb.h"
+
+/* "Standard" openvt's man page (we do not support all of this):
+
+openvt [-c NUM] [-fsulv] [--] [command [args]]
+
+Find the first available VT, and run command on it. Stdio is directed
+to that VT. If no command is specified then $SHELL is used.
+
+-c NUM
+    Use the given VT number, not the first free one.
+-f
+    Force opening a VT: don't try to check if VT is already in use.
+-s
+    Switch to the new VT when starting the command.
+    The VT of the new command will be made the new current VT.
+-u
+    Figure out the owner of the current VT, and run login as that user.
+    Suitable to be called by init. Shouldn't be used with -c or -l.
+-l
+    Make the command a login shell: a "-" is prepended to the argv[0]
+    when command is executed.
+-v
+    Verbose.
+-w
+    Wait for command to complete. If -w and -s are used together,
+    switch back to the controlling terminal when the command completes.
+
+bbox:
+-u: not implemented
+-f: always in effect
+-l: not implemented, ignored
+-v: ignored
+-ws: does NOT switch back
+*/
+
+/* Helper: does this fd understand VT_xxx? */
+static int not_vt_fd(int fd)
+{
+       struct vt_stat vtstat;
+       return ioctl(fd, VT_GETSTATE, &vtstat); /* !0: error, it's not VT fd */
+}
+
+/* Helper: get a fd suitable for VT_xxx */
+static int get_vt_fd(void)
+{
+       int fd;
+
+       /* Do we, by chance, already have it? */
+       for (fd = 0; fd < 3; fd++)
+               if (!not_vt_fd(fd))
+                       return fd;
+       /* _only_ O_NONBLOCK: ask for neither read nor write perms */
+       /*FIXME: use? device_open(DEV_CONSOLE,0); */
+       fd = open(DEV_CONSOLE, O_NONBLOCK);
+       if (fd >= 0 && !not_vt_fd(fd))
+               return fd;
+       bb_error_msg_and_die("can't find open VT");
+}
+
+static int find_free_vtno(void)
+{
+       int vtno;
+       int fd = get_vt_fd();
+
+       errno = 0;
+       /*xfunc_error_retval = 3; - do we need compat? */
+       if (ioctl(fd, VT_OPENQRY, &vtno) != 0 || vtno <= 0)
+               bb_perror_msg_and_die("can't find open VT");
+// Not really needed, grep for DAEMON_ONLY_SANITIZE
+//     if (fd > 2)
+//             close(fd);
+       return vtno;
+}
+
+/* vfork scares gcc, it generates bigger code.
+ * Keep it away from main program.
+ * TODO: move to libbb; or adapt existing libbb's spawn().
+ */
+static NOINLINE void vfork_child(char **argv)
+{
+       if (vfork() == 0) {
+               /* CHILD */
+               /* Try to make this VT our controlling tty */
+               setsid(); /* lose old ctty */
+               ioctl(STDIN_FILENO, TIOCSCTTY, 0 /* 0: don't forcibly steal */);
+               //bb_error_msg("our sid %d", getsid(0));
+               //bb_error_msg("our pgrp %d", getpgrp());
+               //bb_error_msg("VT's sid %d", tcgetsid(0));
+               //bb_error_msg("VT's pgrp %d", tcgetpgrp(0));
+               BB_EXECVP(argv[0], argv);
+               bb_perror_msg_and_die("exec %s", argv[0]);
+       }
+}
+
+int openvt_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int openvt_main(int argc UNUSED_PARAM, char **argv)
+{
+       char vtname[sizeof(VC_FORMAT) + sizeof(int)*3];
+       struct vt_stat vtstat;
+       char *str_c;
+       int vtno;
+       int flags;
+       enum {
+               OPT_c = (1 << 0),
+               OPT_w = (1 << 1),
+               OPT_s = (1 << 2),
+               OPT_l = (1 << 3),
+               OPT_f = (1 << 4),
+               OPT_v = (1 << 5),
+       };
+
+       /* "+" - stop on first non-option */
+       flags = getopt32(argv, "+c:wslfv", &str_c);
+       argv += optind;
+
+       if (flags & OPT_c) {
+               /* Check for illegal vt number: < 1 or > 63 */
+               vtno = xatou_range(str_c, 1, 63);
+       } else {
+               vtno = find_free_vtno();
+       }
+
+       /* Grab new VT */
+       sprintf(vtname, VC_FORMAT, vtno);
+       /* (Try to) clean up stray open fds above fd 2 */
+       bb_daemonize_or_rexec(DAEMON_CLOSE_EXTRA_FDS | DAEMON_ONLY_SANITIZE, NULL);
+       close(STDIN_FILENO);
+       /*setsid(); - BAD IDEA: after we exit, child is SIGHUPed... */
+       xopen(vtname, O_RDWR);
+       xioctl(STDIN_FILENO, VT_GETSTATE, &vtstat);
+
+       if (flags & OPT_s) {
+               console_make_active(STDIN_FILENO, vtno);
+       }
+
+       if (!argv[0]) {
+               argv--;
+               argv[0] = getenv("SHELL");
+               if (!argv[0])
+                       argv[0] = (char *) DEFAULT_SHELL;
+               /*argv[1] = NULL; - already is */
+       }
+
+       xdup2(STDIN_FILENO, STDOUT_FILENO);
+       xdup2(STDIN_FILENO, STDERR_FILENO);
+
+#ifdef BLOAT
+       {
+       /* Handle -l (login shell) option */
+       const char *prog = argv[0];
+       if (flags & OPT_l)
+               argv[0] = xasprintf("-%s", argv[0]);
+       }
+#endif
+
+       vfork_child(argv);
+       if (flags & OPT_w) {
+               /* We have only one child, wait for it */
+               safe_waitpid(-1, NULL, 0); /* loops on EINTR */
+               if (flags & OPT_s) {
+                       console_make_active(STDIN_FILENO, vtstat.v_active);
+                       // Compat: even with -c N (try to) disallocate:
+                       // # /usr/app/kbd-1.12/bin/openvt -f -c 9 -ws sleep 5
+                       // openvt: could not deallocate console 9
+                       xioctl(STDIN_FILENO, VT_DISALLOCATE, (void*)(ptrdiff_t)vtno);
+               }
+       }
+       return EXIT_SUCCESS;
+}
diff --git a/console-tools/reset.c b/console-tools/reset.c
new file mode 100644 (file)
index 0000000..6917eda
--- /dev/null
@@ -0,0 +1,47 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Mini reset implementation for busybox
+ *
+ * Copyright (C) 1999-2004 by Erik Andersen <andersen@codepoet.org>
+ * Written by Erik Andersen and Kent Robotti <robotti@metconnect.com>
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.h"
+
+/* BTW, which "standard" package has this utility? It doesn't seem
+ * to be ncurses, coreutils, console-tools... then what? */
+
+#if ENABLE_STTY
+int stty_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+#endif
+
+int reset_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int reset_main(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
+{
+       static const char *const args[] = {
+               "stty", "sane", NULL
+       };
+
+       /* no options, no getopt */
+
+       if (isatty(STDIN_FILENO) && isatty(STDOUT_FILENO)) {
+               /* 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");
+               /* http://bugs.busybox.net/view.php?id=1414:
+                * people want it to reset echo etc: */
+#if ENABLE_STTY
+               return stty_main(2, (char**)args);
+#else
+               execvp("stty", (char**)args);
+#endif
+       }
+       return EXIT_SUCCESS;
+}
diff --git a/console-tools/resize.c b/console-tools/resize.c
new file mode 100644 (file)
index 0000000..4504cc8
--- /dev/null
@@ -0,0 +1,71 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * resize - set terminal width and height.
+ *
+ * Copyright 2006 Bernhard Reutner-Fischer
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+/* no options, no getopt */
+#include "libbb.h"
+
+#define ESC "\033"
+
+#define old_termios (*(struct termios*)&bb_common_bufsiz1)
+
+static void
+onintr(int sig UNUSED_PARAM)
+{
+       tcsetattr(STDERR_FILENO, TCSANOW, &old_termios);
+       exit(EXIT_FAILURE);
+}
+
+int resize_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int resize_main(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
+{
+       struct termios new;
+       struct winsize w = { 0, 0, 0, 0 };
+       int ret;
+
+       /* We use _stderr_ in order to make resize usable
+        * in shell backticks (those redirect stdout away from tty).
+        * NB: other versions of resize open "/dev/tty"
+        * and operate on it - should we do the same?
+        */
+
+       tcgetattr(STDERR_FILENO, &old_termios); /* fiddle echo */
+       new = old_termios;
+       new.c_cflag |= (CLOCAL | CREAD);
+       new.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
+       bb_signals(0
+               + (1 << SIGINT)
+               + (1 << SIGQUIT)
+               + (1 << SIGTERM)
+               + (1 << SIGALRM)
+               , onintr);
+       tcsetattr(STDERR_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
+        */
+       fprintf(stderr, ESC"7" ESC"[r" ESC"[999;999H" ESC"[6n");
+       alarm(3); /* Just in case terminal won't answer */
+       scanf(ESC"[%hu;%huR", &w.ws_row, &w.ws_col);
+       fprintf(stderr, ESC"8");
+
+       /* BTW, other versions of resize recalculate w.ws_xpixel, ws.ws_ypixel
+        * by calculating character cell HxW from old values
+        * (gotten via TIOCGWINSZ) and recomputing *pixel values */
+       ret = ioctl(STDERR_FILENO, TIOCSWINSZ, &w);
+
+       tcsetattr(STDERR_FILENO, TCSANOW, &old_termios);
+
+       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 (file)
index 0000000..8ad9948
--- /dev/null
@@ -0,0 +1,39 @@
+/* vi: set sw=4 ts=4: */
+/*
+ *  setconsole.c - redirect system console output
+ *
+ *  Copyright (C) 2004,2005  Enrik Berkhan <Enrik.Berkhan@inka.de>
+ *  Copyright (C) 2008 Bernhard Reutner-Fischer
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.h"
+
+int setconsole_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int setconsole_main(int argc UNUSED_PARAM, char **argv)
+{
+       const char *device = CURRENT_TTY;
+       bool reset;
+
+#if ENABLE_FEATURE_SETCONSOLE_LONG_OPTIONS
+       static const char setconsole_longopts[] ALIGN1 =
+               "reset\0" No_argument "r"
+               ;
+       applet_long_options = setconsole_longopts;
+#endif
+       /* at most one non-option argument */
+       opt_complementary = "?1";
+       reset = getopt32(argv, "r");
+
+       argv += 1 + reset;
+       if (*argv) {
+               device = *argv;
+       } else {
+               if (reset)
+                       device = DEV_CONSOLE;
+       }
+
+       xioctl(xopen(device, O_RDONLY), TIOCCONS, NULL);
+       return EXIT_SUCCESS;
+}
diff --git a/console-tools/setkeycodes.c b/console-tools/setkeycodes.c
new file mode 100644 (file)
index 0000000..597272a
--- /dev/null
@@ -0,0 +1,49 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * setkeycodes
+ *
+ * Copyright (C) 1994-1998 Andries E. Brouwer <aeb@cwi.nl>
+ *
+ * Adjusted for BusyBox by Erik Andersen <andersen@codepoet.org>
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+//#include <sys/ioctl.h>
+#include "libbb.h"
+
+/* From <linux/kd.h> */
+struct kbkeycode {
+       unsigned scancode, keycode;
+};
+enum {
+       KDSETKEYCODE = 0x4B4D  /* write kernel keycode table entry */
+};
+
+int setkeycodes_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int setkeycodes_main(int argc, char **argv)
+{
+       int fd, sc;
+       struct kbkeycode a;
+
+       if (!(argc & 1) /* if even */ || argc < 2) {
+               bb_show_usage();
+       }
+
+       fd = get_console_fd_or_die();
+
+       while (argc > 2) {
+               a.keycode = xatou_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;
+               }
+               ioctl_or_perror_and_die(fd, KDSETKEYCODE, &a,
+                       "can't 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 (file)
index 0000000..dd44591
--- /dev/null
@@ -0,0 +1,30 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * setlogcons: Send kernel messages to the current console or to console N
+ *
+ * Copyright (C) 2006 by Jan Kiszka <jan.kiszka@web.de>
+ *
+ * Based on setlogcons (kbd-1.12) by Andries E. Brouwer
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.h"
+
+int setlogcons_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int setlogcons_main(int argc UNUSED_PARAM, char **argv)
+{
+       struct {
+               char fn;
+               char subarg;
+       } arg = { 11, /* redirect kernel messages */
+                         0   /* to specified console (current as default) */
+                       };
+
+       if (argv[1])
+               arg.subarg = xatou_range(argv[1], 0, 63);
+
+       xioctl(xopen(VC_1, O_RDONLY), TIOCLINUX, &arg);
+
+       return EXIT_SUCCESS;
+}
diff --git a/console-tools/showkey.c b/console-tools/showkey.c
new file mode 100644 (file)
index 0000000..681114d
--- /dev/null
@@ -0,0 +1,138 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * shows keys pressed. inspired by kbd package
+ *
+ * Copyright (C) 2008 by Vladimir Dronnikov <dronnikov@gmail.com>
+ *
+ * Licensed under GPLv2, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.h"
+#include <linux/kd.h>
+
+// set raw tty mode
+// also used by microcom
+// libbb candidates?
+static void xget1(int fd, struct termios *t, struct termios *oldt)
+{
+       tcgetattr(fd, oldt);
+       *t = *oldt;
+       cfmakeraw(t);
+}
+
+static int xset1(int fd, struct termios *tio, const char *device)
+{
+       int ret = tcsetattr(fd, TCSAFLUSH, tio);
+
+       if (ret) {
+               bb_perror_msg("can't tcsetattr for %s", device);
+       }
+       return ret;
+}
+
+/*
+ * GLOBALS
+ */
+struct globals {
+       int kbmode;
+       struct termios tio, tio0;
+};
+#define G (*ptr_to_globals)
+#define kbmode (G.kbmode)
+#define tio    (G.tio)
+#define tio0   (G.tio0)
+#define INIT_G() do { \
+       SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
+} while (0)
+
+
+static void signal_handler(int signo)
+{
+       // restore keyboard and console settings
+       xset1(STDIN_FILENO, &tio0, "stdin");
+       xioctl(STDIN_FILENO, KDSKBMODE, (void *)(ptrdiff_t)kbmode);
+       // alarmed? -> exit 0
+       exit(SIGALRM == signo);
+}
+
+int showkey_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int showkey_main(int argc UNUSED_PARAM, char **argv)
+{
+       enum {
+               OPT_a = (1<<0), // display the decimal/octal/hex values of the keys
+               OPT_k = (1<<1), // display only the interpreted keycodes (default)
+               OPT_s = (1<<2), // display only the raw scan-codes
+       };
+
+       // FIXME: aks are all mutually exclusive
+       getopt32(argv, "aks");
+
+       INIT_G();
+
+       // get keyboard settings
+       xioctl(STDIN_FILENO, KDGKBMODE, &kbmode);
+       printf("kb mode was %s\n\nPress any keys. Program terminates %s\n\n",
+               kbmode == K_RAW ? "RAW" :
+                       (kbmode == K_XLATE ? "XLATE" :
+                               (kbmode == K_MEDIUMRAW ? "MEDIUMRAW" :
+                                       (kbmode == K_UNICODE ? "UNICODE" : "?UNKNOWN?")))
+               , (option_mask32 & OPT_a) ? "when CTRL+D pressed" : "10s after last keypress"
+       );
+       // prepare for raw mode
+       xget1(STDIN_FILENO, &tio, &tio0);
+       // put stdin in raw mode
+       xset1(STDIN_FILENO, &tio, "stdin");
+
+       if (option_mask32 & OPT_a) {
+               char c;
+               // just read stdin char by char
+               while (1 == safe_read(STDIN_FILENO, &c, 1)) {
+                       printf("%3d 0%03o 0x%02x\r\n", c, c, c);
+                       if (04 /*CTRL-D*/ == c)
+                               break;
+               }
+       } else {
+               // we should exit on any signal
+               bb_signals(BB_FATAL_SIGS, signal_handler);
+               // set raw keyboard mode
+               xioctl(STDIN_FILENO, KDSKBMODE, (void *)(ptrdiff_t)((option_mask32 & OPT_k) ? K_MEDIUMRAW : K_RAW));
+
+               // read and show scancodes
+               while (1) {
+                       char buf[18];
+                       int i, n;
+                       // setup 10s watchdog
+                       alarm(10);
+                       // read scancodes
+                       n = read(STDIN_FILENO, buf, sizeof(buf));
+                       i = 0;
+                       while (i < n) {
+                               char c = buf[i];
+                               // show raw scancodes ordered? ->
+                               if (option_mask32 & OPT_s) {
+                                       printf("0x%02x ", buf[i++]);
+                               // show interpreted scancodes (default) ? ->
+                               } else {
+                                       int kc;
+                                       if (i+2 < n && (c & 0x7f) == 0
+                                               && (buf[i+1] & 0x80) != 0
+                                               && (buf[i+2] & 0x80) != 0) {
+                                               kc = ((buf[i+1] & 0x7f) << 7) | (buf[i+2] & 0x7f);
+                                               i += 3;
+                                       } else {
+                                               kc = (c & 0x7f);
+                                               i++;
+                                       }
+                                       printf("keycode %3d %s", kc, (c & 0x80) ? "release" : "press");
+                               }
+                       }
+                       puts("\r");
+               }
+       }
+
+       // cleanup
+       signal_handler(SIGALRM);
+
+       // should never be here!
+       return EXIT_SUCCESS;
+}
diff --git a/coreutils/Config.in b/coreutils/Config.in
new file mode 100644 (file)
index 0000000..b047ce5
--- /dev/null
@@ -0,0 +1,839 @@
+#
+# 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 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 FEATURE_DF_FANCY
+       bool "Enable -a, -i, -B"
+       default n
+       depends on DF
+       help
+         This option enables -a, -i and -B.
+
+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 || ASH_BUILTIN_ECHO || HUSH
+       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 EXPAND
+       bool "expand"
+       default n
+       help
+         By default, convert all tabs to spaces.
+
+config FEATURE_EXPAND_LONG_OPTIONS
+       bool "Enable long options"
+       default n
+       depends on EXPAND && GETOPT_LONG
+       help
+         Support long options for the expand 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 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 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 FEATURE_RMDIR_LONG_OPTIONS
+       bool "Enable long options"
+       default n
+       depends on RMDIR && GETOPT_LONG
+       help
+         Support long options for the rmdir applet, including
+         --ignore-fail-on-non-empty for compatibility with GNU rmdir.
+
+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 SHA256SUM
+       bool "sha256sum"
+       default n
+       help
+         Compute and check SHA256 message digest
+
+config SHA512SUM
+       bool "sha512sum"
+       default n
+       help
+         Compute and check SHA512 message digest
+
+config SLEEP
+       bool "sleep"
+       default n
+       help
+         sleep is used to pause for a specified number of seconds.
+         It comes in 3 versions:
+         - small: takes one integer parameter
+         - fancy: takes multiple integer arguments with suffixes:
+           sleep 1d 2h 3m 15s
+         - fancy with fractional numbers:
+           sleep 2.3s 4.5h sleeps for 16202.3 seconds
+         Last one is "the most compatible" with coreutils sleep,
+         but it adds around 1k of code.
+
+config FEATURE_FANCY_SLEEP
+       bool "Enable multiple arguments and s/m/h/d suffixes"
+       default n
+       depends on SLEEP
+       help
+         Allow sleep to pause for specified minutes, hours, and days.
+
+config FEATURE_FLOAT_SLEEP
+       bool "Enable fractional arguments"
+       default n
+       depends on FEATURE_FANCY_SLEEP
+       help
+         Allow for fractional numeric parameters.
+
+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 SPLIT
+       bool "split"
+       default n
+       help
+         split a file into pieces.
+
+config FEATURE_SPLIT_FANCY
+       bool "Fancy extensions"
+       default n
+       depends on SPLIT
+       help
+         Add support for features not required by SUSv3.
+         Supports additional suffixes 'b' for 512 bytes,
+         'g' for 1GiB for the -b option.
+
+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 TAC
+       bool "tac"
+       default n
+       help
+         tac is used to concatenate and print files in reverse.
+
+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 || ASH_BUILTIN_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 UNEXPAND
+       bool "unexpand"
+       default n
+       help
+         By default, convert only leading sequences of blanks to tabs.
+
+config FEATURE_UNEXPAND_LONG_OPTIONS
+       bool "Enable long options"
+       default n
+       depends on UNEXPAND && GETOPT_LONG
+       help
+         Support long options for the unexpand applet.
+
+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 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 counter 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 (file)
index 0000000..57100a9
--- /dev/null
@@ -0,0 +1,95 @@
+# 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.
+
+libs-y                 += libcoreutils/
+
+lib-y:=
+lib-$(CONFIG_BASENAME)  += basename.o
+lib-$(CONFIG_CAL)       += cal.o
+lib-$(CONFIG_CAT)       += cat.o
+lib-$(CONFIG_MORE)      += cat.o # more uses it if stdout isn't a tty
+lib-$(CONFIG_LESS)      += cat.o # less too
+lib-$(CONFIG_CRONTAB)   += cat.o # crontab -l
+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_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_DIRNAME)   += dirname.o
+lib-$(CONFIG_DOS2UNIX)  += dos2unix.o
+lib-$(CONFIG_DU)        += du.o
+lib-$(CONFIG_ECHO)      += echo.o
+lib-$(CONFIG_ASH)       += echo.o # used by ash
+lib-$(CONFIG_HUSH)      += echo.o # used by hush
+lib-$(CONFIG_ENV)       += env.o
+lib-$(CONFIG_EXPR)      += expr.o
+lib-$(CONFIG_EXPAND)    += expand.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_FTPD)      += 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_ASH_BUILTIN_PRINTF) += printf.o
+lib-$(CONFIG_PWD)       += pwd.o
+lib-$(CONFIG_READLINK)  += readlink.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_SHA256SUM) += md5_sha1_sum.o
+lib-$(CONFIG_SHA512SUM) += md5_sha1_sum.o
+lib-$(CONFIG_SLEEP)     += sleep.o
+lib-$(CONFIG_SPLIT)     += split.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_TAC)       += tac.o
+lib-$(CONFIG_TAIL)      += tail.o
+lib-$(CONFIG_TEE)       += tee.o
+lib-$(CONFIG_TEST)      += test.o test_ptr_hack.o
+lib-$(CONFIG_ASH)       += test.o test_ptr_hack.o # used by ash
+lib-$(CONFIG_HUSH)      += test.o test_ptr_hack.o # used by hush
+lib-$(CONFIG_MSH)       += test.o test_ptr_hack.o # used by msh
+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_UNEXPAND)  += expand.o
+lib-$(CONFIG_UNIQ)      += uniq.o
+lib-$(CONFIG_USLEEP)    += usleep.o
+lib-$(CONFIG_UUDECODE)  += uudecode.o
+lib-$(CONFIG_UUENCODE)  += uuencode.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 (file)
index 0000000..8a5597e
--- /dev/null
@@ -0,0 +1,53 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Mini basename 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.
+ *
+ */
+
+/* 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 "libbb.h"
+
+/* This is a NOFORK applet. Be very careful! */
+
+int basename_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int basename_main(int argc, char **argv)
+{
+       size_t m, n;
+       char *s;
+
+       if (((unsigned int)(argc-2)) >= 2) {
+               bb_show_usage();
+       }
+
+       /* It should strip slash: /abc/def/ -> def */
+       s = bb_get_last_path_component_strip(*++argv);
+
+       m = strlen(s);
+       if (*++argv) {
+               n = strlen(*argv);
+               if ((m > n) && ((strcmp)(s+m-n, *argv) == 0)) {
+                       m -= n;
+                       /*s[m] = '\0'; - redundant */
+               }
+       }
+
+       /* puts(s) will do, but we can do without stdio this way: */
+       s[m++] = '\n';
+       /* NB: != is correct here: */
+       return full_write(STDOUT_FILENO, s, m) != (ssize_t)m;
+}
diff --git a/coreutils/cal.c b/coreutils/cal.c
new file mode 100644 (file)
index 0000000..9b59777
--- /dev/null
@@ -0,0 +1,349 @@
+/* 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 "libbb.h"
+
+/* We often use "unsigned" intead of "int", it's easier to div on most CPUs */
+
+#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 unsigned char days_in_month[] ALIGN1 = {
+       0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
+};
+
+static const unsigned char sep1752[] ALIGN1 = {
+                1,     2,      14,     15,     16,
+       17,     18,     19,     20,     21,     22,     23,
+       24,     25,     26,     27,     28,     29,     30
+};
+
+/* Set to 0 or 1 in main */
+#define julian ((unsigned)option_mask32)
+
+/* leap year -- account for Gregorian reformation in 1752 */
+static int leap_year(unsigned yr)
+{
+       if (yr <= 1752)
+               return !(yr % 4);
+       return (!(yr % 4) && (yr % 100)) || !(yr % 400);
+}
+
+/* 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 *, unsigned, unsigned);
+static void day_array(unsigned, unsigned, unsigned *);
+static void trim_trailing_spaces_and_print(char *);
+
+static void blank_string(char *buf, size_t buflen);
+static char *build_row(char *p, unsigned *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) MAIN_EXTERNALLY_VISIBLE;
+int cal_main(int argc, char **argv)
+{
+       struct tm *local_time;
+       struct tm zero_tm;
+       time_t now;
+       unsigned month, year, flags, i;
+       char *month_names[12];
+       char day_headings[28];  /* 28 for julian, 21 for nonjulian */
+       char buf[40];
+
+       flags = getopt32(argv, "jy");
+       /* This sets julian = flags & 1: */
+       option_mask32 &= 1;
+       month = 0;
+       argv += optind;
+       argc -= optind;
+
+       if (argc > 2) {
+               bb_show_usage();
+       }
+
+       if (!argc) {
+               time(&now);
+               local_time = localtime(&now);
+               year = local_time->tm_year + 1900;
+               if (!(flags & 2)) { /* no -y */
+                       month = local_time->tm_mon + 1;
+               }
+       } else {
+               if (argc == 2) {
+                       month = xatou_range(*argv++, 1, 12);
+               }
+               year = xatou_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) {
+               unsigned row, len, days[MAXDAYS];
+               unsigned *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 {
+               unsigned row, which_cal, week_len, days[12][MAXDAYS];
+               unsigned *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);
+                       }
+                       bb_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(EXIT_SUCCESS);
+}
+
+/*
+ * 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(unsigned month, unsigned year, unsigned *days)
+{
+       unsigned long temp;
+       unsigned i;
+       unsigned day, dw, dm;
+
+       memset(days, SPACE, MAXDAYS * sizeof(int));
+
+       if ((month == 9) && (year == 1752)) {
+               /* Assumes the Gregorian reformation eliminates
+                * 3 Sep. 1752 through 13 Sep. 1752.
+                */
+               unsigned j_offset = julian * 244;
+               size_t oday = 0;
+
+               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.
+        */
+       temp = (long)(year - 1) * 365 + leap_years_since_year_1(year - 1) + day;
+       if (temp < FIRST_MISSING_DAY) {
+               dw = ((temp - 1 + SATURDAY) % 7);
+       } else {
+               dw = (((temp - 1 + SATURDAY) - NUMBER_MISSING_DAYS) % 7);
+       }
+
+       if (!julian) {
+               day = 1;
+       }
+
+       dm = days_in_month[month];
+       if ((month == 2) && leap_year(year)) {
+               ++dm;
+       }
+
+       do {
+               days[dw++] = day++;
+       } while (--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, unsigned len, unsigned separate)
+{
+       unsigned 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, unsigned *dp)
+{
+       unsigned col, val, day;
+
+       memset(p, ' ', (julian + DAY_LEN) * 7);
+
+       col = 0;
+       do {
+               day = *dp++;
+               if (day != SPACE) {
+                       if (julian) {
+                               ++p;
+                               if (day >= 100) {
+                                       *p = '0';
+                                       p[-1] = (day / 100) + '0';
+                                       day %= 100;
+                               }
+                       }
+                       val = day / 10;
+                       if (val > 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 (file)
index 0000000..0024eb8
--- /dev/null
@@ -0,0 +1,48 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * cat implementation for busybox
+ *
+ * Copyright (C) 2003  Manuel Novoa III  <mjn3@codepoet.org>
+ *
+ * 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 "libbb.h"
+
+/* This is a NOFORK applet. Be very careful! */
+
+
+int bb_cat(char **argv)
+{
+       int fd;
+       int retval = EXIT_SUCCESS;
+
+       if (!*argv)
+               argv = (char**) &bb_argv_dash;
+
+       do {
+               fd = open_or_warn_stdin(*argv);
+               if (fd >= 0) {
+                       /* This is not a xfunc - never exits */
+                       off_t r = bb_copyfd_eof(fd, STDOUT_FILENO);
+                       if (fd != STDIN_FILENO)
+                               close(fd);
+                       if (r >= 0)
+                               continue;
+               }
+               retval = EXIT_FAILURE;
+       } while (*++argv);
+
+       return retval;
+}
+
+int cat_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int cat_main(int argc UNUSED_PARAM, char **argv)
+{
+       getopt32(argv, "u");
+       argv += optind;
+       return bb_cat(argv);
+}
diff --git a/coreutils/catv.c b/coreutils/catv.c
new file mode 100644 (file)
index 0000000..ff3139c
--- /dev/null
@@ -0,0 +1,75 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * cat -v implementation for busybox
+ *
+ * Copyright (C) 2006 Rob Landley <rob@landley.net>
+ *
+ * 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 "libbb.h"
+
+int catv_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int catv_main(int argc UNUSED_PARAM, char **argv)
+{
+       int retval = EXIT_SUCCESS;
+       int fd;
+       unsigned flags;
+
+       flags = getopt32(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;
+
+       /* Read from stdin if there's nothing else to do. */
+       if (!argv[0])
+               *--argv = (char*)"-";
+       do {
+               fd = open_or_warn_stdin(*argv);
+               if (fd < 0) {
+                       retval = EXIT_FAILURE;
+                       continue;
+               }
+               for (;;) {
+                       int i, res;
+
+#define read_buf bb_common_bufsiz1
+                       res = read(fd, read_buf, COMMON_BUFSIZE);
+                       if (res < 0)
+                               retval = EXIT_FAILURE;
+                       if (res < 1)
+                               break;
+                       for (i = 0; i < res; i++) {
+                               unsigned char c = read_buf[i];
+
+                               if (c > 126 && (flags & CATV_OPT_v)) {
+                                       if (c == 127) {
+                                               printf("^?");
+                                               continue;
+                                       }
+                                       printf("M-");
+                                       c -= 128;
+                               }
+                               if (c < 32) {
+                                       if (c == 10) {
+                                               if (flags & CATV_OPT_e)
+                                                       bb_putchar('$');
+                                       } else if (flags & (c==9 ? CATV_OPT_t : CATV_OPT_v)) {
+                                               printf("^%c", c+'@');
+                                               continue;
+                                       }
+                               }
+                               bb_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 (file)
index 0000000..7f39048
--- /dev/null
@@ -0,0 +1,31 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Mini chgrp 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.
+ */
+
+/* BB_AUDIT SUSv3 defects - none? */
+/* BB_AUDIT GNU defects - unsupported long options. */
+/* http://www.opengroup.org/onlinepubs/007904975/utilities/chgrp.html */
+
+#include "libbb.h"
+
+/* This is a NOEXEC applet. Be very careful! */
+
+
+int chgrp_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+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 (file)
index 0000000..40f681f
--- /dev/null
@@ -0,0 +1,160 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Mini chmod implementation for busybox
+ *
+ * Copyright (C) 1999-2004 by Erik Andersen <andersen@codepoet.org>
+ *
+ * Reworked by (C) 2002 Vladimir Oleynik <dzo@simtreas.ru>
+ *  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 "libbb.h"
+
+/* This is a NOEXEC applet. Be very careful! */
+
+
+#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 FAST_FUNC fileAction(const char *fileName, struct stat *statbuf, void* param, 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 *)param, &newmode))
+               bb_error_msg_and_die("invalid mode: %s", (char *)param);
+
+       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_simple_perror_msg(fileName);
+       return FALSE;
+}
+
+int chmod_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int chmod_main(int argc UNUSED_PARAM, 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(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
+                       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 directory.
+
+#!/bin/sh
+t1="/tmp/busybox chmod"
+t2="/usr/bin/chmod"
+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
+    )
+}
+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
+    rm out.diff
+}
+echo "If script produced 'out.diff' file, then at least one testcase failed"
+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 (file)
index 0000000..3452492
--- /dev/null
@@ -0,0 +1,180 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Mini chown 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.
+ */
+
+/* BB_AUDIT SUSv3 defects - none? */
+/* BB_AUDIT GNU defects - unsupported long options. */
+/* http://www.opengroup.org/onlinepubs/007904975/utilities/chown.html */
+
+#include "libbb.h"
+
+/* This is a NOEXEC applet. Be very careful! */
+
+
+#define OPT_STR     ("Rh" USE_DESKTOP("vcfLHP"))
+#define BIT_RECURSE 1
+#define OPT_RECURSE (opt & 1)
+#define OPT_NODEREF (opt & 2)
+#define OPT_VERBOSE (USE_DESKTOP(opt & 0x04) SKIP_DESKTOP(0))
+#define OPT_CHANGED (USE_DESKTOP(opt & 0x08) SKIP_DESKTOP(0))
+#define OPT_QUIET   (USE_DESKTOP(opt & 0x10) SKIP_DESKTOP(0))
+/* POSIX options
+ * -L traverse every symbolic link to a directory encountered
+ * -H if a command line argument is a symbolic link to a directory, traverse it
+ * -P do not traverse any symbolic links (default)
+ * We do not conform to the following:
+ * "Specifying more than one of -H, -L, and -P is not an error.
+ * The last option specified shall determine the behavior of the utility." */
+/* -L */
+#define BIT_TRAVERSE 0x20
+#define OPT_TRAVERSE (USE_DESKTOP(opt & BIT_TRAVERSE) SKIP_DESKTOP(0))
+/* -H or -L */
+#define BIT_TRAVERSE_TOP (0x20|0x40)
+#define OPT_TRAVERSE_TOP (USE_DESKTOP(opt & BIT_TRAVERSE_TOP) SKIP_DESKTOP(0))
+
+typedef int (*chown_fptr)(const char *, uid_t, gid_t);
+
+struct param_t {
+       struct bb_uidgid_t ugid;
+       chown_fptr chown_func;
+};
+
+static int FAST_FUNC fileAction(const char *fileName, struct stat *statbuf,
+               void *vparam, int depth UNUSED_PARAM)
+{
+#define param  (*(struct param_t*)vparam)
+#define opt option_mask32
+       uid_t u = (param.ugid.uid == (uid_t)-1) ? statbuf->st_uid : param.ugid.uid;
+       gid_t g = (param.ugid.gid == (gid_t)-1) ? statbuf->st_gid : param.ugid.gid;
+
+       if (param.chown_func(fileName, u, g) == 0) {
+               if (OPT_VERBOSE
+                || (OPT_CHANGED && (statbuf->st_uid != u || statbuf->st_gid != g))
+               ) {
+                       printf("changed ownership of '%s' to %u:%u\n",
+                                       fileName, (unsigned)u, (unsigned)g);
+               }
+               return TRUE;
+       }
+       if (!OPT_QUIET)
+               bb_simple_perror_msg(fileName); /* A filename can have % in it... */
+       return FALSE;
+#undef opt
+#undef param
+}
+
+int chown_main(int argc UNUSED_PARAM, char **argv)
+{
+       int retval = EXIT_SUCCESS;
+       int opt, flags;
+       struct param_t param;
+
+       param.ugid.uid = -1;
+       param.ugid.gid = -1;
+       param.chown_func = chown;
+
+       opt_complementary = "-2";
+       opt = getopt32(argv, OPT_STR);
+       argv += optind;
+
+       /* This matches coreutils behavior (almost - see below) */
+       if (OPT_NODEREF
+           /* || (OPT_RECURSE && !OPT_TRAVERSE_TOP): */
+           USE_DESKTOP( || (opt & (BIT_RECURSE|BIT_TRAVERSE_TOP)) == BIT_RECURSE)
+       ) {
+               param.chown_func = lchown;
+       }
+
+       flags = ACTION_DEPTHFIRST; /* match coreutils order */
+       if (OPT_RECURSE)
+               flags |= ACTION_RECURSE;
+       if (OPT_TRAVERSE_TOP)
+               flags |= ACTION_FOLLOWLINKS_L0; /* -H/-L: follow links on depth 0 */
+       if (OPT_TRAVERSE)
+               flags |= ACTION_FOLLOWLINKS; /* follow links if -L */
+
+       parse_chown_usergroup_or_die(&param.ugid, argv[0]);
+
+       /* Ok, ready to do the deed now */
+       argv++;
+       do {
+               if (!recursive_action(*argv,
+                               flags,          /* flags */
+                               fileAction,     /* file action */
+                               fileAction,     /* dir action */
+                               &param,         /* user data */
+                               0)              /* depth */
+               ) {
+                       retval = EXIT_FAILURE;
+               }
+       } while (*++argv);
+
+       return retval;
+}
+
+/*
+Testcase. Run in empty directory.
+
+#!/bin/sh
+t1="/tmp/busybox chown"
+t2="/usr/bin/chown"
+create() {
+    rm -rf $1; mkdir $1
+    (
+    cd $1 || exit 1
+    mkdir dir dir2
+    >up
+    >file
+    >dir/file
+    >dir2/file
+    ln -s dir linkdir
+    ln -s file linkfile
+    ln -s ../up dir/linkup
+    ln -s ../dir2 dir/linkupdir2
+    )
+    chown -R 0:0 $1
+}
+tst() {
+    create test1
+    create test2
+    echo "[$1]" >>test1.out
+    echo "[$1]" >>test2.out
+    (cd test1; $t1 $1) >>test1.out 2>&1
+    (cd test2; $t2 $1) >>test2.out 2>&1
+    (cd test1; ls -lnR) >out1
+    (cd test2; ls -lnR) >out2
+    echo "chown $1" >out.diff
+    if ! diff -u out1 out2 >>out.diff; then exit 1; fi
+    rm out.diff
+}
+tst_for_each() {
+    tst "$1 1:1 file"
+    tst "$1 1:1 dir"
+    tst "$1 1:1 linkdir"
+    tst "$1 1:1 linkfile"
+}
+echo "If script produced 'out.diff' file, then at least one testcase failed"
+>test1.out
+>test2.out
+# These match coreutils 6.8:
+tst_for_each "-v"
+tst_for_each "-vR"
+tst_for_each "-vRP"
+tst_for_each "-vRL"
+tst_for_each "-vRH"
+tst_for_each "-vh"
+tst_for_each "-vhR"
+tst_for_each "-vhRP"
+tst_for_each "-vhRL"
+tst_for_each "-vhRH"
+# Fix `name' in coreutils output
+sed 's/`/'"'"'/g' -i test2.out
+# Compare us with coreutils output
+diff -u test1.out test2.out
+
+*/
diff --git a/coreutils/chroot.c b/coreutils/chroot.c
new file mode 100644 (file)
index 0000000..1198a41
--- /dev/null
@@ -0,0 +1,37 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Mini chroot 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.
+ */
+
+/* BB_AUDIT SUSv3 N/A -- Matches GNU behavior. */
+
+#include "libbb.h"
+
+int chroot_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int chroot_main(int argc, char **argv)
+{
+       if (argc < 2) {
+               bb_show_usage();
+       }
+
+       ++argv;
+       xchroot(*argv);
+       xchdir("/");
+
+       ++argv;
+       if (argc == 2) {
+               argv -= 2;
+               argv[0] = getenv("SHELL");
+               if (!argv[0]) {
+                       argv[0] = (char *) DEFAULT_SHELL;
+               }
+               argv[1] = (char *) "-i";
+       }
+
+       BB_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 (file)
index 0000000..3a77c75
--- /dev/null
@@ -0,0 +1,67 @@
+/* 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 "libbb.h"
+
+int cksum_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int cksum_main(int argc UNUSED_PARAM, char **argv)
+{
+       uint32_t *crc32_table = crc32_filltable(NULL, 1);
+       uint32_t crc;
+       off_t length, filesize;
+       int bytes_read;
+       int exit_code = EXIT_SUCCESS;
+       uint8_t *cp;
+
+#if ENABLE_DESKTOP
+       getopt32(argv, ""); /* coreutils 6.9 compat */
+       argv += optind;
+#else
+       argv++;
+#endif
+
+       do {
+               int fd = open_or_warn_stdin(*argv ? *argv : bb_msg_standard_input);
+
+               if (fd < 0) {
+                       exit_code = EXIT_FAILURE;
+                       continue;
+               }
+               crc = 0;
+               length = 0;
+
+#define read_buf bb_common_bufsiz1
+               while ((bytes_read = safe_read(fd, read_buf, sizeof(read_buf))) > 0) {
+                       cp = (uint8_t *) read_buf;
+                       length += bytes_read;
+                       do {
+                               crc = (crc << 8) ^ crc32_table[(crc >> 24) ^ *cp++];
+                       } while (--bytes_read);
+               }
+               close(fd);
+
+               filesize = length;
+
+               while (length) {
+                       crc = (crc << 8) ^ crc32_table[(uint8_t)(crc >> 24) ^ (uint8_t)length];
+                       /* must ensure that shift is unsigned! */
+                       if (sizeof(length) <= sizeof(unsigned))
+                               length = (unsigned)length >> 8;
+                       else if (sizeof(length) <= sizeof(unsigned long))
+                               length = (unsigned long)length >> 8;
+                       else
+                               length = (unsigned long long)length >> 8;
+               }
+               crc = ~crc;
+
+               printf((*argv ? "%"PRIu32" %"OFF_FMT"i %s\n" : "%"PRIu32" %"OFF_FMT"i\n"),
+                               crc, filesize, *argv);
+       } while (*argv && *++argv);
+
+       fflush_stdout_and_exit(exit_code);
+}
diff --git a/coreutils/comm.c b/coreutils/comm.c
new file mode 100644 (file)
index 0000000..221cbfb
--- /dev/null
@@ -0,0 +1,100 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Mini comm implementation for busybox
+ *
+ * Copyright (C) 2005 by Robert Sullivan <cogito.ergo.cogito@gmail.com>
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.h"
+
+#define COMM_OPT_1 (1 << 0)
+#define COMM_OPT_2 (1 << 1)
+#define COMM_OPT_3 (1 << 2)
+
+/* writeline outputs the input given, appropriately aligned according to class */
+static void writeline(char *line, int class)
+{
+       int flags = option_mask32;
+       if (class == 0) {
+               if (flags & COMM_OPT_1)
+                       return;
+       } else if (class == 1) {
+               if (flags & COMM_OPT_2)
+                       return;
+               if (!(flags & COMM_OPT_1))
+                       putchar('\t');
+       } else /*if (class == 2)*/ {
+               if (flags & COMM_OPT_3)
+                       return;
+               if (!(flags & COMM_OPT_1))
+                       putchar('\t');
+               if (!(flags & COMM_OPT_2))
+                       putchar('\t');
+       }
+       puts(line);
+}
+
+int comm_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int comm_main(int argc UNUSED_PARAM, char **argv)
+{
+       char *thisline[2];
+       FILE *stream[2];
+       int i;
+       int order;
+
+       opt_complementary = "=2";
+       getopt32(argv, "123");
+       argv += optind;
+
+       for (i = 0; i < 2; ++i) {
+               stream[i] = xfopen_stdin(argv[i]);
+       }
+
+       order = 0;
+       thisline[1] = thisline[0] = NULL;
+       while (1) {
+               if (order <= 0) {
+                       free(thisline[0]);
+                       thisline[0] = xmalloc_fgetline(stream[0]);
+               }
+               if (order >= 0) {
+                       free(thisline[1]);
+                       thisline[1] = xmalloc_fgetline(stream[1]);
+               }
+
+               i = !thisline[0] + (!thisline[1] << 1);
+               if (i)
+                       break;
+               order = strcmp(thisline[0], thisline[1]);
+
+               if (order >= 0)
+                       writeline(thisline[1], order ? 1 : 2);
+               else
+                       writeline(thisline[0], 0);
+       }
+
+       /* EOF at least on one of the streams */
+       i &= 1;
+       if (thisline[i]) {
+               /* stream[i] is not at EOF yet */
+               /* we did not print thisline[i] yet */
+               char *p = thisline[i];
+               writeline(p, i);
+               while (1) {
+                       free(p);
+                       p = xmalloc_fgetline(stream[i]);
+                       if (!p)
+                               break;
+                       writeline(p, i);
+               }
+       }
+
+       if (ENABLE_FEATURE_CLEAN_UP) {
+               fclose(stream[0]);
+               fclose(stream[1]);
+       }
+
+       return EXIT_SUCCESS;
+}
diff --git a/coreutils/cp.c b/coreutils/cp.c
new file mode 100644 (file)
index 0000000..71a2939
--- /dev/null
@@ -0,0 +1,155 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Mini cp implementation for busybox
+ *
+ * Copyright (C) 2000 by Matt Kraai <kraai@alumni.carnegiemellon.edu>
+ * SELinux support by Yuichi Nakamura <ynakam@hitachisoft.jp>
+ *
+ * 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 "libbb.h"
+#include "libcoreutils/coreutils.h"
+
+/* This is a NOEXEC applet. Be very careful! */
+
+
+int cp_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+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),
+       };
+
+       // Need at least two arguments
+       // Soft- and hardlinking doesn't mix
+       // -P and -d are the same (-P is POSIX, -d is GNU)
+       // -r and -R are the same
+       // -R (and therefore -r) turns on -d (coreutils does this)
+       // -a = -pdR
+       opt_complementary = "-2:l--s:s--l:Pd:rRd:Rd:apdR:HL";
+       // -v (--verbose) is ignored
+       flags = getopt32(argv, FILEUTILS_CP_OPTSTR "arPHv");
+       /* Options of cp from GNU coreutils 6.10:
+        * -a, --archive
+        * -f, --force
+        * -i, --interactive
+        * -l, --link
+        * -L, --dereference
+        * -P, --no-dereference
+        * -R, -r, --recursive
+        * -s, --symbolic-link
+        * -v, --verbose
+        * -H   follow command-line symbolic links in SOURCE
+        * -d   same as --no-dereference --preserve=links
+        * -p   same as --preserve=mode,ownership,timestamps
+        * -c   same as --preserve=context
+        * NOT SUPPORTED IN BBOX:
+        * long options are not supported (even those above).
+        * --backup[=CONTROL]
+        *      make a backup of each existing destination file
+        * -b   like --backup but does not accept an argument
+        * --copy-contents
+        *      copy contents of special files when recursive
+        * --preserve[=ATTR_LIST]
+        *      preserve attributes (default: mode,ownership,timestamps),
+        *      if possible additional attributes: security context,links,all
+        * --no-preserve=ATTR_LIST
+        * --parents
+        *      use full source file name under DIRECTORY
+        * --remove-destination
+        *      remove  each existing destination file before attempting to open
+        * --sparse=WHEN
+        *      control creation of sparse files
+        * --strip-trailing-slashes
+        *      remove any trailing slashes from each SOURCE argument
+        * -S, --suffix=SUFFIX
+        *      override the usual backup suffix
+        * -t, --target-directory=DIRECTORY
+        *      copy all SOURCE arguments into DIRECTORY
+        * -T, --no-target-directory
+        *      treat DEST as a normal file
+        * -u, --update
+        *      copy only when the SOURCE file is newer than the destination
+        *      file or when the destination file is missing
+        * -x, --one-file-system
+        *      stay on this file system
+        * -Z, --context=CONTEXT
+        *      (SELinux) set SELinux security context of copy to CONTEXT
+        */
+       argc -= optind;
+       argv += optind;
+       flags ^= FILEUTILS_DEREFERENCE; /* the sense of this flag was reversed */
+       /* coreutils 6.9 compat:
+        * by default, "cp" derefs symlinks (creates regular dest files),
+        * but "cp -R" does not. We switch off deref if -r or -R (see above).
+        * However, "cp -RL" must still deref symlinks: */
+       if (flags & FILEUTILS_DEREF_SOFTLINK) /* -L */
+               flags |= FILEUTILS_DEREFERENCE;
+       /* The behavior of -H is *almost* like -L, but not quite, so let's
+        * just ignore it too for fun. TODO.
+       if (flags & OPT_H) ... // deref command-line params only
+       */
+
+#if ENABLE_SELINUX
+       if (flags & FILEUTILS_PRESERVE_SECURITY_CONTEXT) {
+               selinux_or_die();
+       }
+#endif
+
+       last = argv[argc - 1];
+       /* If there are only two arguments and...  */
+       if (argc == 2) {
+               s_flags = cp_mv_stat2(*argv, &source_stat,
+                                     (flags & FILEUTILS_DEREFERENCE) ? stat : lstat);
+               if (s_flags < 0)
+                       return EXIT_FAILURE;
+               d_flags = cp_mv_stat(last, &dest_stat);
+               if (d_flags < 0)
+                       return EXIT_FAILURE;
+
+               /* ...if 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 = last;
+                       goto DO_COPY; /* NB: argc==2 -> *++argv==last */
+               }
+       }
+
+       while (1) {
+               dest = concat_path_file(last, bb_get_last_path_component_strip(*argv));
+ DO_COPY:
+               if (copy_file(*argv, dest, flags) < 0) {
+                       status = 1;
+               }
+               if (*++argv == last) {
+                       /* possibly leaking dest... */
+                       break;
+               }
+               free((void*)dest);
+       }
+
+       /* Exit. We are NOEXEC, not NOFORK. We do exit at the end of main() */
+       return status;
+}
diff --git a/coreutils/cut.c b/coreutils/cut.c
new file mode 100644 (file)
index 0000000..9cc22be
--- /dev/null
@@ -0,0 +1,287 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * cut.c - minimalist version of cut
+ *
+ * Copyright (C) 1999,2000,2001 by Lineo, inc.
+ * Written by Mark Whitley <markw@codepoet.org>
+ * debloated by Bernhard Reutner-Fischer
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.h"
+
+/* This is a NOEXEC applet. Be very careful! */
+
+
+/* option vars */
+static const char optstring[] ALIGN1 = "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)
+
+struct cut_list {
+       int startpos;
+       int endpos;
+};
+
+enum {
+       BOL = 0,
+       EOL = INT_MAX,
+       NON_RANGE = -1
+};
+
+static int cmpfunc(const void *a, const void *b)
+{
+       return (((struct cut_list *) a)->startpos -
+                       ((struct cut_list *) b)->startpos);
+
+}
+
+static void cut_file(FILE *file, char delim, const struct cut_list *cut_lists, unsigned nlists)
+{
+       char *line;
+       unsigned linenum = 0;   /* keep these zero-based to be consistent */
+
+       /* go through every line in the file */
+       while ((line = xmalloc_fgetline(file)) != NULL) {
+
+               /* set up a list so we can keep track of what's been printed */
+               int linelen = strlen(line);
+               char *printed = xzalloc(linelen + 1);
+               char *orig_line = line;
+               unsigned cl_pos = 0;
+               int spos;
+
+               /* cut based on chars/bytes XXX: only works when sizeof(char) == byte */
+               if (option_mask32 & (CUT_OPT_CHAR_FLGS | CUT_OPT_BYTE_FLGS)) {
+                       /* print the chars specified in each cut list */
+                       for (; cl_pos < nlists; cl_pos++) {
+                               spos = cut_lists[cl_pos].startpos;
+                               while (spos < linelen) {
+                                       if (!printed[spos]) {
+                                               printed[spos] = 'X';
+                                               putchar(line[spos]);
+                                       }
+                                       spos++;
+                                       if (spos > cut_lists[cl_pos].endpos
+                                       /* NON_RANGE is -1, so if below is true,
+                                        * the above was true too (spos is >= 0) */
+                                       /* || cut_lists[cl_pos].endpos == NON_RANGE */
+                                       ) {
+                                               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 (((int)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 < (int)linenum) {
+                               spos++;
+                               /* go to the next list if we're at the end of this one */
+                               if (spos > cut_lists[cl_pos].endpos
+                                || cut_lists[cl_pos].endpos == NON_RANGE
+                               ) {
+                                       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 ((int)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);
+       }
+}
+
+int cut_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int cut_main(int argc UNUSED_PARAM, char **argv)
+{
+       /* growable array holding a series of lists */
+       struct cut_list *cut_lists = NULL;
+       unsigned nlists = 0;    /* number of elements in above list */
+       char delim = '\t';      /* delimiter, default is tab */
+       char *sopt, *ltok;
+       unsigned opt;
+
+       opt_complementary = "b--bcf:c--bcf:f--bcf";
+       opt = getopt32(argv, optstring, &sopt, &sopt, &sopt, &ltok);
+//     argc -= optind;
+       argv += optind;
+       if (!(opt & (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 (opt & CUT_OPT_DELIM_FLGS) {
+               if (ltok[0] && ltok[1]) { /* more than 1 char? */
+                       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 (!(opt & CUT_OPT_FIELDS_FLGS)) {
+               static const char _op_on_field[] ALIGN1 = " only when operating on fields";
+
+               if (opt & CUT_OPT_SUPPRESS_FLGS) {
+                       bb_error_msg_and_die
+                               ("suppressing non-delimited lines makes sense%s",
+                                _op_on_field);
+               }
+               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 (!ltok[0])
+                               continue;
+
+                       /* get the start pos */
+                       ntok = strsep(&ltok, "-");
+                       if (!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 */
+                       if (ltok == NULL) {
+                               e = NON_RANGE;
+                       } else if (!ltok[0]) {
+                               e = EOL;
+                       } else {
+                               e = xatoi_u(ltok);
+                               /* 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;
+                       }
+
+                       /* add the new list */
+                       cut_lists = xrealloc_vector(cut_lists, 4, nlists);
+                       /* NB: startpos is always >= 0,
+                        * while endpos may be = NON_RANGE (-1) */
+                       cut_lists[nlists].startpos = s;
+                       cut_lists[nlists].endpos = e;
+                       nlists++;
+               }
+
+               /* 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);
+       }
+
+       {
+               int retval = EXIT_SUCCESS;
+
+               if (!*argv)
+                       *--argv = (char *)"-";
+
+               do {
+                       FILE *file = fopen_or_warn_stdin(*argv);
+                       if (!file) {
+                               retval = EXIT_FAILURE;
+                               continue;
+                       }
+                       cut_file(file, delim, cut_lists, nlists);
+                       fclose_if_not_stdin(file);
+               } while (*++argv);
+
+               if (ENABLE_FEATURE_CLEAN_UP)
+                       free(cut_lists);
+               fflush_stdout_and_exit(retval);
+       }
+}
diff --git a/coreutils/date.c b/coreutils/date.c
new file mode 100644 (file)
index 0000000..177b7d0
--- /dev/null
@@ -0,0 +1,239 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Mini date implementation for busybox
+ *
+ * by Matthew Grant <grantma@anathoth.gen.nz>
+ *
+ * iso-format handling added by Robert Griebl <griebl@gmx.de>
+ * bugfixes and cleanup by Bernhard Reutner-Fischer
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+*/
+
+#include "libbb.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 maybe_set_utc(int opt)
+{
+       if (opt & DATE_OPT_UTC)
+               putenv((char*)"TZ=UTC0");
+}
+
+int date_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int date_main(int argc UNUSED_PARAM, char **argv)
+{
+       struct tm tm_time;
+       time_t tm;
+       unsigned opt;
+       int ifmt = -1;
+       char *date_str;
+       char *fmt_dt2str;
+       char *fmt_str2dt;
+       char *filename;
+       char *isofmt_arg = NULL;
+
+       opt_complementary = "d--s:s--d"
+               USE_FEATURE_DATE_ISOFMT(":R--I:I--R");
+       opt = getopt32(argv, "Rs:ud:r:"
+                       USE_FEATURE_DATE_ISOFMT("I::D:"),
+                       &date_str, &date_str, &filename
+                       USE_FEATURE_DATE_ISOFMT(, &isofmt_arg, &fmt_str2dt));
+       argv += optind;
+       maybe_set_utc(opt);
+
+       if (ENABLE_FEATURE_DATE_ISOFMT && (opt & DATE_OPT_TIMESPEC)) {
+               ifmt = 0; /* default is date */
+               if (isofmt_arg) {
+                       static const char isoformats[] ALIGN1 =
+                               "date\0""hours\0""minutes\0""seconds\0";
+                       ifmt = index_in_strings(isoformats, isofmt_arg);
+                       if (ifmt < 0)
+                               bb_show_usage();
+               }
+       }
+
+       fmt_dt2str = NULL;
+       if (argv[0] && argv[0][0] == '+') {
+               fmt_dt2str = &argv[0][1];       /* Skip over the '+' */
+               argv++;
+       }
+       if (!(opt & (DATE_OPT_SET | DATE_OPT_DATE))) {
+               opt |= DATE_OPT_SET;
+               date_str = argv[0]; /* can be NULL */
+               if (date_str)
+                       argv++;
+       }
+       if (*argv)
+               bb_show_usage();
+
+       /* Now we have parsed all the information except the date format
+          which depends on whether the clock is being set or read */
+
+       if (opt & DATE_OPT_REFERENCE) {
+               struct stat statbuf;
+               xstat(filename, &statbuf);
+               tm = statbuf.st_mtime;
+       } else
+               time(&tm);
+       memcpy(&tm_time, localtime(&tm), sizeof(tm_time));
+
+       /* If date string is given, update tm_time, and maybe set date */
+       if (date_str != NULL) {
+               /* Zero out fields - take her back to midnight! */
+               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)) {
+                       if (strptime(date_str, fmt_str2dt, &tm_time) == NULL)
+                               bb_error_msg_and_die(bb_msg_invalid_date, date_str);
+               } else {
+                       char end = '\0';
+                       const char *last_colon = strrchr(date_str, ':');
+
+                       if (last_colon != NULL) {
+                               /* Parse input and assign appropriately to tm_time */
+
+                               if (sscanf(date_str, "%u:%u%c",
+                                                               &tm_time.tm_hour,
+                                                               &tm_time.tm_min,
+                                                               &end) >= 2) {
+                                       /* no adjustments needed */
+                               } else if (sscanf(date_str, "%u.%u-%u:%u%c",
+                                                               &tm_time.tm_mon, &tm_time.tm_mday,
+                                                               &tm_time.tm_hour, &tm_time.tm_min,
+                                                               &end) >= 4) {
+                                       /* Adjust dates from 1-12 to 0-11 */
+                                       tm_time.tm_mon -= 1;
+                               } else if (sscanf(date_str, "%u.%u.%u-%u:%u%c", &tm_time.tm_year,
+                                                               &tm_time.tm_mon, &tm_time.tm_mday,
+                                                               &tm_time.tm_hour, &tm_time.tm_min,
+                                                               &end) >= 5) {
+                                       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, "%u-%u-%u %u:%u%c", &tm_time.tm_year,
+                                                               &tm_time.tm_mon, &tm_time.tm_mday,
+                                                               &tm_time.tm_hour, &tm_time.tm_min,
+                                                               &end) >= 5) {
+                                       tm_time.tm_year -= 1900; /* Adjust years */
+                                       tm_time.tm_mon -= 1; /* Adjust dates from 1-12 to 0-11 */
+//TODO: coreutils 6.9 also accepts "YYYY-MM-DD HH" (no minutes)
+                               } else {
+                                       bb_error_msg_and_die(bb_msg_invalid_date, date_str);
+                               }
+                               if (end == ':') {
+                                       if (sscanf(last_colon + 1, "%u%c", &tm_time.tm_sec, &end) == 1)
+                                               end = '\0';
+                                       /* else end != NUL and we error out */
+                               }
+                       } else {
+                               if (sscanf(date_str, "%2u%2u%2u%2u%u%c", &tm_time.tm_mon,
+                                                       &tm_time.tm_mday, &tm_time.tm_hour, &tm_time.tm_min,
+                                                       &tm_time.tm_year, &end) < 4)
+                                       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;
+                               if (end == '.') {
+                                       if (sscanf(strchr(date_str, '.') + 1, "%u%c",
+                                                       &tm_time.tm_sec, &end) == 1)
+                                               end = '\0';
+                                       /* else end != NUL and we error out */
+                               }
+                       }
+                       if (end != '\0') {
+                               bb_error_msg_and_die(bb_msg_invalid_date, date_str);
+                       }
+               }
+               /* 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 (fmt_dt2str == NULL) {
+               int i;
+               fmt_dt2str = xzalloc(32);
+               if (ENABLE_FEATURE_DATE_ISOFMT && ifmt >= 0) {
+                       strcpy(fmt_dt2str, "%Y-%m-%d");
+                       if (ifmt > 0) {
+                               i = 8;
+                               fmt_dt2str[i++] = 'T';
+                               fmt_dt2str[i++] = '%';
+                               fmt_dt2str[i++] = 'H';
+                               if (ifmt > 1) {
+                                       fmt_dt2str[i++] = ':';
+                                       fmt_dt2str[i++] = '%';
+                                       fmt_dt2str[i++] = 'M';
+                                       if (ifmt > 2) {
+                                               fmt_dt2str[i++] = ':';
+                                               fmt_dt2str[i++] = '%';
+                                               fmt_dt2str[i++] = 'S';
+                                       }
+                               }
+ format_utc:
+                               fmt_dt2str[i++] = '%';
+                               fmt_dt2str[i] = (opt & DATE_OPT_UTC) ? 'Z' : 'z';
+                       }
+               } else if (opt & DATE_OPT_RFC2822) {
+                       /* Undo busybox.c for date -R */
+                       if (ENABLE_LOCALE_SUPPORT)
+                               setlocale(LC_TIME, "C");
+                       strcpy(fmt_dt2str, "%a, %d %b %Y %H:%M:%S ");
+                       i = 22;
+                       goto format_utc;
+               } else /* default case */
+                       fmt_dt2str = (char*)"%a %b %e %H:%M:%S %Z %Y";
+       }
+
+#define date_buf bb_common_bufsiz1
+       if (*fmt_dt2str == '\0') {
+               /* With no format string, just print a blank line */
+               date_buf[0] = '\0';
+       } else {
+               /* Handle special conversions */
+               if (strncmp(fmt_dt2str, "%f", 2) == 0) {
+                       fmt_dt2str = (char*)"%Y.%m.%d-%H:%M:%S";
+               }
+
+               /* Generate output string */
+               strftime(date_buf, sizeof(date_buf), fmt_dt2str, &tm_time);
+       }
+       puts(date_buf);
+
+       return EXIT_SUCCESS;
+}
diff --git a/coreutils/dd.c b/coreutils/dd.c
new file mode 100644 (file)
index 0000000..38dacc7
--- /dev/null
@@ -0,0 +1,355 @@
+/* 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 "libbb.h"
+
+/* This is a NOEXEC applet. Be very careful! */
+
+
+enum {
+       ifd = STDIN_FILENO,
+       ofd = STDOUT_FILENO,
+};
+
+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 },
+       { }
+};
+
+struct globals {
+       off_t out_full, out_part, in_full, in_part;
+};
+#define G (*(struct globals*)&bb_common_bufsiz1)
+/* We have to zero it out because of NOEXEC */
+#define INIT_G() memset(&G, 0, sizeof(G))
+
+
+static void dd_output_status(int UNUSED_PARAM cur_signal)
+{
+       /* Deliberately using %u, not %d */
+       fprintf(stderr, "%"OFF_FMT"u+%"OFF_FMT"u records in\n"
+                       "%"OFF_FMT"u+%"OFF_FMT"u records out\n",
+                       G.in_full, G.in_part,
+                       G.out_full, G.out_part);
+}
+
+static ssize_t full_write_or_warn(const void *buf, size_t len,
+       const char *const filename)
+{
+       ssize_t n = full_write(ofd, buf, len);
+       if (n < 0)
+               bb_perror_msg("writing '%s'", filename);
+       return n;
+}
+
+static bool write_and_stats(const void *buf, size_t len, size_t obs,
+       const char *filename)
+{
+       ssize_t n = full_write_or_warn(buf, len, filename);
+       if (n < 0)
+               return 1;
+       if ((size_t)n == obs)
+               G.out_full++;
+       else if (n) /* > 0 */
+               G.out_part++;
+       return 0;
+}
+
+#if ENABLE_LFS
+#define XATOU_SFX xatoull_sfx
+#else
+#define XATOU_SFX xatoul_sfx
+#endif
+
+int dd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int dd_main(int argc UNUSED_PARAM, char **argv)
+{
+       enum {
+               /* Must be in the same order as OP_conv_XXX! */
+               /* (see "flags |= (1 << what)" below) */
+               FLAG_NOTRUNC = 1 << 0,
+               FLAG_SYNC    = 1 << 1,
+               FLAG_NOERROR = 1 << 2,
+               FLAG_FSYNC   = 1 << 3,
+               /* end of conv flags */
+               FLAG_TWOBUFS = 1 << 4,
+               FLAG_COUNT   = 1 << 5,
+       };
+       static const char keywords[] ALIGN1 =
+               "bs\0""count\0""seek\0""skip\0""if\0""of\0"
+#if ENABLE_FEATURE_DD_IBS_OBS
+               "ibs\0""obs\0""conv\0"
+#endif
+               ;
+#if ENABLE_FEATURE_DD_IBS_OBS
+       static const char conv_words[] ALIGN1 =
+               "notrunc\0""sync\0""noerror\0""fsync\0";
+#endif
+       enum {
+               OP_bs = 0,
+               OP_count,
+               OP_seek,
+               OP_skip,
+               OP_if,
+               OP_of,
+#if ENABLE_FEATURE_DD_IBS_OBS
+               OP_ibs,
+               OP_obs,
+               OP_conv,
+               /* Must be in the same order as FLAG_XXX! */
+               OP_conv_notrunc = 0,
+               OP_conv_sync,
+               OP_conv_noerror,
+               OP_conv_fsync,
+       /* Unimplemented conv=XXX: */
+       //nocreat       do not create the output file
+       //excl          fail if the output file already exists
+       //fdatasync     physically write output file data before finishing
+       //swab          swap every pair of input bytes
+       //lcase         change upper case to lower case
+       //ucase         change lower case to upper case
+       //block         pad newline-terminated records with spaces to cbs-size
+       //unblock       replace trailing spaces in cbs-size records with newline
+       //ascii         from EBCDIC to ASCII
+       //ebcdic        from ASCII to EBCDIC
+       //ibm           from ASCII to alternate EBCDIC
+#endif
+       };
+       int exitcode = EXIT_FAILURE;
+       size_t ibs = 512, obs = 512;
+       ssize_t n, w;
+       char *ibuf, *obuf;
+       /* And these are all zeroed at once! */
+       struct {
+               int flags;
+               size_t oc;
+               off_t count;
+               off_t seek, skip;
+               const char *infile, *outfile;
+       } Z;
+#define flags   (Z.flags  )
+#define oc      (Z.oc     )
+#define count   (Z.count  )
+#define seek    (Z.seek   )
+#define skip    (Z.skip   )
+#define infile  (Z.infile )
+#define outfile (Z.outfile)
+
+       memset(&Z, 0, sizeof(Z));
+       INIT_G();
+       //fflush(NULL); - is this needed because of NOEXEC?
+
+#if ENABLE_FEATURE_DD_SIGNAL_HANDLING
+       signal_SA_RESTART_empty_mask(SIGUSR1, dd_output_status);
+#endif
+
+       for (n = 1; argv[n]; n++) {
+               int what;
+               char *val;
+               char *arg = argv[n];
+
+#if ENABLE_DESKTOP
+               /* "dd --". NB: coreutils 6.9 will complain if they see
+                * more than one of them. We wouldn't. */
+               if (arg[0] == '-' && arg[1] == '-' && arg[2] == '\0')
+                       continue;
+#endif
+               val = strchr(arg, '=');
+               if (val == NULL)
+                       bb_show_usage();
+               *val = '\0';
+               what = index_in_strings(keywords, arg);
+               if (what < 0)
+                       bb_show_usage();
+               /* *val = '='; - to preserve ps listing? */
+               val++;
+#if ENABLE_FEATURE_DD_IBS_OBS
+               if (what == OP_ibs) {
+                       /* Must fit into positive ssize_t */
+                       ibs = xatoul_range_sfx(val, 1, ((size_t)-1L)/2, dd_suffixes);
+                       /*continue;*/
+               }
+               if (what == OP_obs) {
+                       obs = xatoul_range_sfx(val, 1, ((size_t)-1L)/2, dd_suffixes);
+                       /*continue;*/
+               }
+               if (what == OP_conv) {
+                       while (1) {
+                               /* find ',', replace them with NUL so we can use val for
+                                * index_in_strings() without copying.
+                                * We rely on val being non-null, else strchr would fault.
+                                */
+                               arg = strchr(val, ',');
+                               if (arg)
+                                       *arg = '\0';
+                               what = index_in_strings(conv_words, val);
+                               if (what < 0)
+                                       bb_error_msg_and_die(bb_msg_invalid_arg, val, "conv");
+                               flags |= (1 << what);
+                               if (!arg) /* no ',' left, so this was the last specifier */
+                                       break;
+                               /* *arg = ','; - to preserve ps listing? */
+                               val = arg + 1; /* skip this keyword and ',' */
+                       }
+                       continue; /* we trashed 'what', can't fall through */
+               }
+#endif
+               if (what == OP_bs) {
+                       ibs = obs = xatoul_range_sfx(val, 1, ((size_t)-1L)/2, dd_suffixes);
+                       /*continue;*/
+               }
+               /* These can be large: */
+               if (what == OP_count) {
+                       flags |= FLAG_COUNT;
+                       count = XATOU_SFX(val, dd_suffixes);
+                       /*continue;*/
+               }
+               if (what == OP_seek) {
+                       seek = XATOU_SFX(val, dd_suffixes);
+                       /*continue;*/
+               }
+               if (what == OP_skip) {
+                       skip = XATOU_SFX(val, dd_suffixes);
+                       /*continue;*/
+               }
+               if (what == OP_if) {
+                       infile = val;
+                       /*continue;*/
+               }
+               if (what == OP_of) {
+                       outfile = val;
+                       /*continue;*/
+               }
+       } /* end of "for (argv[n])" */
+
+//XXX:FIXME for huge ibs or obs, malloc'ing them isn't the brightest idea ever
+       ibuf = obuf = xmalloc(ibs);
+       if (ibs != obs) {
+               flags |= FLAG_TWOBUFS;
+               obuf = xmalloc(obs);
+       }
+       if (infile != NULL)
+               xmove_fd(xopen(infile, O_RDONLY), ifd);
+       else {
+               infile = bb_msg_standard_input;
+       }
+       if (outfile != NULL) {
+               int oflag = O_WRONLY | O_CREAT;
+
+               if (!seek && !(flags & FLAG_NOTRUNC))
+                       oflag |= O_TRUNC;
+
+               xmove_fd(xopen(outfile, oflag), ofd);
+
+               if (seek && !(flags & FLAG_NOTRUNC)) {
+                       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 {
+               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)
+                                       goto die_infile;
+                               if (n == 0)
+                                       break;
+                       }
+               }
+       }
+       if (seek) {
+               if (lseek(ofd, seek * obs, SEEK_CUR) < 0)
+                       goto die_outfile;
+       }
+
+       while (!(flags & FLAG_COUNT) || (G.in_full + G.in_part != count)) {
+               if (flags & FLAG_NOERROR) /* Pre-zero the buffer if conv=noerror */
+                       memset(ibuf, 0, ibs);
+               n = safe_read(ifd, ibuf, ibs);
+               if (n == 0)
+                       break;
+               if (n < 0) {
+                       if (!(flags & FLAG_NOERROR))
+                               goto die_infile;
+                       n = ibs;
+                       bb_simple_perror_msg(infile);
+               }
+               if ((size_t)n == ibs)
+                       G.in_full++;
+               else {
+                       G.in_part++;
+                       if (flags & FLAG_SYNC) {
+                               memset(ibuf + n, '\0', ibs - n);
+                               n = ibs;
+                       }
+               }
+               if (flags & FLAG_TWOBUFS) {
+                       char *tmp = ibuf;
+                       while (n) {
+                               size_t d = obs - oc;
+
+                               if (d > (size_t)n)
+                                       d = n;
+                               memcpy(obuf + oc, tmp, d);
+                               n -= d;
+                               tmp += d;
+                               oc += d;
+                               if (oc == obs) {
+                                       if (write_and_stats(obuf, obs, obs, outfile))
+                                               goto out_status;
+                                       oc = 0;
+                               }
+                       }
+               } else if (write_and_stats(ibuf, n, obs, outfile))
+                       goto out_status;
+
+               if (flags & FLAG_FSYNC) {
+                       if (fsync(ofd) < 0)
+                               goto die_outfile;
+               }
+       }
+
+       if (ENABLE_FEATURE_DD_IBS_OBS && oc) {
+               w = full_write_or_warn(obuf, oc, outfile);
+               if (w < 0) goto out_status;
+               if (w > 0) G.out_part++;
+       }
+       if (close(ifd) < 0) {
+ die_infile:
+               bb_simple_perror_msg_and_die(infile);
+       }
+
+       if (close(ofd) < 0) {
+ die_outfile:
+               bb_simple_perror_msg_and_die(outfile);
+       }
+
+       exitcode = EXIT_SUCCESS;
+ out_status:
+       dd_output_status(0);
+
+       return exitcode;
+}
diff --git a/coreutils/df.c b/coreutils/df.c
new file mode 100644 (file)
index 0000000..e9a865c
--- /dev/null
@@ -0,0 +1,196 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Mini df implementation for busybox
+ *
+ * Copyright (C) 1999-2004 by Erik Andersen <andersen@codepoet.org>
+ * based on original code by (I think) Bruce Perens <bruce@pixar.com>.
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+/* BB_AUDIT SUSv3 _NOT_ compliant -- option -t missing. */
+/* 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.
+ *
+ * Aug 28, 2008      Bernhard Reutner-Fischer
+ *
+ * Implement -P and -B; better coreutils compat; cleanup
+ */
+
+#include <mntent.h>
+#include <sys/vfs.h>
+#include "libbb.h"
+
+#if !ENABLE_FEATURE_HUMAN_READABLE
+static unsigned long kscale(unsigned long b, unsigned long bs)
+{
+       return (b * (unsigned long long) bs + 1024/2) / 1024;
+}
+#endif
+
+int df_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int df_main(int argc, char **argv)
+{
+       unsigned long blocks_used;
+       unsigned blocks_percent_used;
+       unsigned long df_disp_hr = 1024;
+       int status = EXIT_SUCCESS;
+       unsigned opt;
+       FILE *mount_table;
+       struct mntent *mount_entry;
+       struct statfs s;
+
+       enum {
+               OPT_KILO  = (1 << 0),
+               OPT_POSIX = (1 << 1),
+               OPT_ALL   = (1 << 2) * ENABLE_FEATURE_DF_FANCY,
+               OPT_INODE = (1 << 3) * ENABLE_FEATURE_DF_FANCY,
+               OPT_BSIZE = (1 << 4) * ENABLE_FEATURE_DF_FANCY,
+               OPT_HUMAN = (1 << (2 + 3*ENABLE_FEATURE_DF_FANCY)) * ENABLE_FEATURE_HUMAN_READABLE,
+               OPT_MEGA  = (1 << (3 + 3*ENABLE_FEATURE_DF_FANCY)) * ENABLE_FEATURE_HUMAN_READABLE,
+       };
+       const char *disp_units_hdr = NULL;
+       char *chp;
+
+#if ENABLE_FEATURE_HUMAN_READABLE && ENABLE_FEATURE_DF_FANCY
+       opt_complementary = "k-mB:m-Bk:B-km";
+#elif ENABLE_FEATURE_HUMAN_READABLE
+       opt_complementary = "k-m:m-k";
+#endif
+       opt = getopt32(argv, "kP"
+                       USE_FEATURE_DF_FANCY("aiB:")
+                       USE_FEATURE_HUMAN_READABLE("hm")
+                       USE_FEATURE_DF_FANCY(, &chp));
+       if (opt & OPT_MEGA)
+               df_disp_hr = 1024*1024;
+
+       if (opt & OPT_BSIZE)
+               df_disp_hr = xatoul_range(chp, 1, ULONG_MAX); /* disallow 0 */
+
+       /* From the manpage of df from coreutils-6.10:
+          Disk space is shown in 1K blocks by default, unless the environment
+          variable POSIXLY_CORRECT is set, in which case 512-byte blocks are used.
+       */
+       if (getenv("POSIXLY_CORRECT")) /* TODO - a new libbb function? */
+               df_disp_hr = 512;
+
+       if (opt & OPT_HUMAN) {
+               df_disp_hr = 0;
+               disp_units_hdr = "     Size";
+       }
+       if (opt & OPT_INODE)
+               disp_units_hdr = "   Inodes";
+
+       if (disp_units_hdr == NULL) {
+#if ENABLE_FEATURE_HUMAN_READABLE
+               disp_units_hdr = xasprintf("%s-blocks",
+                       make_human_readable_str(df_disp_hr, 0, !!(opt & OPT_POSIX)));
+#else
+               disp_units_hdr = xasprintf("%lu-blocks", df_disp_hr);
+#endif
+       }
+       printf("Filesystem           %-15sUsed Available %s Mounted on\n",
+                       disp_units_hdr, (opt & OPT_POSIX) ? "Capacity" : "Use%");
+
+       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);
+       }
+
+       while (1) {
+               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);
+                       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_simple_perror_msg(mount_point);
+                       goto set_error;
+               }
+
+               if ((s.f_blocks > 0) || !mount_table || (opt & OPT_ALL)) {
+                       if (opt & OPT_INODE) {
+                               s.f_blocks = s.f_files;
+                               s.f_bavail = s.f_bfree = s.f_ffree;
+                               s.f_bsize = 1;
+
+                               if (df_disp_hr)
+                                       df_disp_hr = 1;
+                       }
+                       blocks_used = s.f_blocks - s.f_bfree;
+                       blocks_percent_used = 0;
+                       if (blocks_used + s.f_bavail) {
+                               blocks_percent_used = (blocks_used * 100ULL
+                                               + (blocks_used + s.f_bavail)/2
+                                               ) / (blocks_used + s.f_bavail);
+                       }
+
+                       /* GNU coreutils 6.10 skips certain mounts, try to be compatible.  */
+                       if (strcmp(device, "rootfs") == 0)
+                               continue;
+
+#ifdef WHY_WE_DO_IT_FOR_DEV_ROOT_ONLY
+/* ... and also this is the only user of find_block_device */
+                       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;
+                               }
+                       }
+#endif
+
+                       if (printf("\n%-20s" + 1, device) > 20)
+                                   printf("\n%-20s", "");
+#if ENABLE_FEATURE_HUMAN_READABLE
+                       printf(" %9s ",
+                               make_human_readable_str(s.f_blocks, s.f_bsize, df_disp_hr));
+
+                       printf(" %9s " + 1,
+                               make_human_readable_str((s.f_blocks - s.f_bfree),
+                                               s.f_bsize, df_disp_hr));
+
+                       printf("%9s %3u%% %s\n",
+                                       make_human_readable_str(s.f_bavail, s.f_bsize, df_disp_hr),
+                                       blocks_percent_used, mount_point);
+#else
+                       printf(" %9lu %9lu %9lu %3u%% %s\n",
+                                       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
+               }
+       }
+
+       return status;
+}
diff --git a/coreutils/dirname.c b/coreutils/dirname.c
new file mode 100644 (file)
index 0000000..c0c0925
--- /dev/null
@@ -0,0 +1,27 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Mini dirname 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.
+ */
+
+/* BB_AUDIT SUSv3 compliant */
+/* http://www.opengroup.org/onlinepubs/007904975/utilities/dirname.html */
+
+#include "libbb.h"
+
+/* This is a NOFORK applet. Be very careful! */
+
+int dirname_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int dirname_main(int argc, char **argv)
+{
+       if (argc != 2) {
+               bb_show_usage();
+       }
+
+       puts(dirname(argv[1]));
+
+       return fflush(stdout);
+}
diff --git a/coreutils/dos2unix.c b/coreutils/dos2unix.c
new file mode 100644 (file)
index 0000000..309cbc3
--- /dev/null
@@ -0,0 +1,98 @@
+/* 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 <hanecak@megaloman.sk>.
+ * 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 "libbb.h"
+
+enum {
+       CT_UNIX2DOS = 1,
+       CT_DOS2UNIX
+};
+
+/* if fn is NULL then input is stdin and output is stdout */
+static void convert(char *fn, int conv_type)
+{
+       FILE *in, *out;
+       int i;
+       char *temp_fn = temp_fn; /* for compiler */
+       char *resolved_fn = resolved_fn;
+
+       in = stdin;
+       out = stdout;
+       if (fn != NULL) {
+               struct stat st;
+
+               resolved_fn = xmalloc_follow_symlinks(fn);
+               if (resolved_fn == NULL)
+                       bb_simple_perror_msg_and_die(fn);
+               in = xfopen_for_read(resolved_fn);
+               fstat(fileno(in), &st);
+
+               temp_fn = xasprintf("%sXXXXXX", resolved_fn);
+               i = mkstemp(temp_fn);
+               if (i == -1
+                || fchmod(i, st.st_mode) == -1
+                || !(out = fdopen(i, "w+"))
+               ) {
+                       bb_simple_perror_msg_and_die(temp_fn);
+               }
+       }
+
+       while ((i = fgetc(in)) != EOF) {
+               if (i == '\r')
+                       continue;
+               if (i == '\n')
+                       if (conv_type == CT_UNIX2DOS)
+                               fputc('\r', out);
+               fputc(i, out);
+       }
+
+       if (fn != NULL) {
+               if (fclose(in) < 0 || fclose(out) < 0) {
+                       unlink(temp_fn);
+                       bb_perror_nomsg_and_die();
+               }
+               xrename(temp_fn, resolved_fn);
+               free(temp_fn);
+               free(resolved_fn);
+       }
+}
+
+int dos2unix_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int dos2unix_main(int argc, char **argv)
+{
+       int o, conv_type;
+
+       /* See if we are supposed to be doing dos2unix or unix2dos */
+       conv_type = CT_UNIX2DOS;
+       if (applet_name[0] == 'd') {
+               conv_type = CT_DOS2UNIX;
+       }
+
+       /* -u convert to unix, -d convert to dos */
+       opt_complementary = "u--d:d--u"; /* mutually exclusive */
+       o = getopt32(argv, "du");
+
+       /* Do the conversion requested by an argument else do the default
+        * conversion depending on our name.  */
+       if (o)
+               conv_type = o;
+
+       do {
+               /* might be convert(NULL) if there is no filename given */
+               convert(argv[optind], conv_type);
+               optind++;
+       } while (optind < argc);
+
+       return 0;
+}
diff --git a/coreutils/du.c b/coreutils/du.c
new file mode 100644 (file)
index 0000000..16c7732
--- /dev/null
@@ -0,0 +1,228 @@
+/* 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 <beppu@codepoet.org>
+ * Copyright (C) 2002  Edward Betts <edward@debian.org>
+ *
+ * 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 "libbb.h"
+
+enum {
+       OPT_a_files_too    = (1 << 0),
+       OPT_H_follow_links = (1 << 1),
+       OPT_k_kbytes       = (1 << 2),
+       OPT_L_follow_links = (1 << 3),
+       OPT_s_total_norecurse = (1 << 4),
+       OPT_x_one_FS       = (1 << 5),
+       OPT_d_maxdepth     = (1 << 6),
+       OPT_l_hardlinks    = (1 << 7),
+       OPT_c_total        = (1 << 8),
+       OPT_h_for_humans   = (1 << 9),
+       OPT_m_mbytes       = (1 << 10),
+};
+
+struct globals {
+#if ENABLE_FEATURE_HUMAN_READABLE
+       unsigned long disp_hr;
+#else
+       unsigned disp_k;
+#endif
+       int max_print_depth;
+       bool status;
+       int slink_depth;
+       int du_depth;
+       dev_t dir_dev;
+};
+#define G (*(struct globals*)&bb_common_bufsiz1)
+
+
+static void print(unsigned long size, const char *filename)
+{
+       /* TODO - May not want to defer error checking here. */
+#if ENABLE_FEATURE_HUMAN_READABLE
+       printf("%s\t%s\n", make_human_readable_str(size, 512, G.disp_hr),
+                       filename);
+#else
+       if (G.disp_k) {
+               size++;
+               size >>= 1;
+       }
+       printf("%ld\t%s\n", size, filename);
+#endif
+}
+
+/* tiny recursive du */
+static unsigned long du(const char *filename)
+{
+       struct stat statbuf;
+       unsigned long sum;
+
+       if (lstat(filename, &statbuf) != 0) {
+               bb_simple_perror_msg(filename);
+               G.status = EXIT_FAILURE;
+               return 0;
+       }
+
+       if (option_mask32 & OPT_x_one_FS) {
+               if (G.du_depth == 0) {
+                       G.dir_dev = statbuf.st_dev;
+               } else if (G.dir_dev != statbuf.st_dev) {
+                       return 0;
+               }
+       }
+
+       sum = statbuf.st_blocks;
+
+       if (S_ISLNK(statbuf.st_mode)) {
+               if (G.slink_depth > G.du_depth) { /* -H or -L */
+                       if (stat(filename, &statbuf) != 0) {
+                               bb_simple_perror_msg(filename);
+                               G.status = EXIT_FAILURE;
+                               return 0;
+                       }
+                       sum = statbuf.st_blocks;
+                       if (G.slink_depth == 1) {
+                               /* Convert -H to -L */
+                               G.slink_depth = INT_MAX;
+                       }
+               }
+       }
+
+       if (!(option_mask32 & OPT_l_hardlinks)
+        && statbuf.st_nlink > 1
+       ) {
+               /* Add files/directories with links only once */
+               if (is_in_ino_dev_hashtable(&statbuf)) {
+                       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) {
+                       G.status = EXIT_FAILURE;
+                       return sum;
+               }
+
+               while ((entry = readdir(dir))) {
+                       newfile = concat_subpath_file(filename, entry->d_name);
+                       if (newfile == NULL)
+                               continue;
+                       ++G.du_depth;
+                       sum += du(newfile);
+                       --G.du_depth;
+                       free(newfile);
+               }
+               closedir(dir);
+       } else {
+               if (!(option_mask32 & OPT_a_files_too) && G.du_depth != 0)
+                       return sum;
+       }
+       if (G.du_depth <= G.max_print_depth) {
+               print(sum, filename);
+       }
+       return sum;
+}
+
+int du_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int du_main(int argc UNUSED_PARAM, char **argv)
+{
+       unsigned long total;
+       int slink_depth_save;
+       unsigned opt;
+
+#if ENABLE_FEATURE_HUMAN_READABLE
+       USE_FEATURE_DU_DEFAULT_BLOCKSIZE_1K(G.disp_hr = 1024;)
+       SKIP_FEATURE_DU_DEFAULT_BLOCKSIZE_1K(G.disp_hr = 512;)
+       if (getenv("POSIXLY_CORRECT"))  /* TODO - a new libbb function? */
+               G.disp_hr = 512;
+#else
+       USE_FEATURE_DU_DEFAULT_BLOCKSIZE_1K(G.disp_k = 1;)
+       /* SKIP_FEATURE_DU_DEFAULT_BLOCKSIZE_1K(G.disp_k = 0;) - G is pre-zeroed */
+#endif
+       G.max_print_depth = INT_MAX;
+
+       /* 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.
+        */
+#if ENABLE_FEATURE_HUMAN_READABLE
+       opt_complementary = "h-km:k-hm:m-hk:H-L:L-H:s-d:d-s:d+";
+       opt = getopt32(argv, "aHkLsx" "d:" "lc" "hm", &G.max_print_depth);
+       argv += optind;
+       if (opt & OPT_h_for_humans) {
+               G.disp_hr = 0;
+       }
+       if (opt & OPT_m_mbytes) {
+               G.disp_hr = 1024*1024;
+       }
+       if (opt & OPT_k_kbytes) {
+               G.disp_hr = 1024;
+       }
+#else
+       opt_complementary = "H-L:L-H:s-d:d-s:d+";
+       opt = getopt32(argv, "aHkLsx" "d:" "lc", &G.max_print_depth);
+       argv += optind;
+#if !ENABLE_FEATURE_DU_DEFAULT_BLOCKSIZE_1K
+       if (opt & OPT_k_kbytes) {
+               G.disp_k = 1;
+       }
+#endif
+#endif
+       if (opt & OPT_H_follow_links) {
+               G.slink_depth = 1;
+       }
+       if (opt & OPT_L_follow_links) {
+               G.slink_depth = INT_MAX;
+       }
+       if (opt & OPT_s_total_norecurse) {
+               G.max_print_depth = 0;
+       }
+
+       /* go through remaining args (if any) */
+       if (!*argv) {
+               *--argv = (char*)".";
+               if (G.slink_depth == 1) {
+                       G.slink_depth = 0;
+               }
+       }
+
+       slink_depth_save = G.slink_depth;
+       total = 0;
+       do {
+               total += du(*argv);
+               /* otherwise du /dir /dir won't show /dir twice: */
+               reset_ino_dev_hashtable();
+               G.slink_depth = slink_depth_save;
+       } while (*++argv);
+
+       if (opt & OPT_c_total)
+               print(total, "total");
+
+       fflush_stdout_and_exit(G.status);
+}
diff --git a/coreutils/echo.c b/coreutils/echo.c
new file mode 100644 (file)
index 0000000..decca09
--- /dev/null
@@ -0,0 +1,303 @@
+/* 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 did not allow 4-digit octals.
+ */
+
+#include "libbb.h"
+
+/* This is a NOFORK applet. Be very careful! */
+
+/* NB: can be used by shell even if not enabled as applet */
+
+int echo_main(int argc UNUSED_PARAM, char **argv)
+{
+       const char *arg;
+#if !ENABLE_FEATURE_FANCY_ECHO
+       enum {
+               eflag = '\\',
+               nflag = 1,  /* 1 -- print '\n' */
+       };
+
+       /* We must check that stdout is not closed.
+        * The reason for this is highly non-obvious.
+        * echo_main is used from shell. Shell must correctly handle "echo foo"
+        * if stdout is closed. With stdio, output gets shoveled into
+        * stdout buffer, and even fflush cannot clear it out. It seems that
+        * even if libc receives EBADF on write attempts, it feels determined
+        * to output data no matter what. So it will try later,
+        * and possibly will clobber future output. Not good. */
+// TODO: check fcntl() & O_ACCMODE == O_WRONLY or O_RDWR?
+       if (fcntl(1, F_GETFL) == -1)
+               return 1; /* match coreutils 6.10 (sans error msg to stderr) */
+       //if (dup2(1, 1) != 1) - old way
+       //      return 1;
+
+       arg = *++argv;
+       if (!arg)
+               goto newline_ret;
+#else
+       const char *p;
+       char nflag = 1;
+       char eflag = 0;
+
+       /* We must check that stdout is not closed. */
+       if (fcntl(1, F_GETFL) == -1)
+               return 1;
+
+       while (1) {
+               arg = *++argv;
+               if (!arg)
+                       goto newline_ret;
+               if (*arg != '-')
+                       break;
+
+               /* 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.
+                */
+               p = arg + 1;
+               if (!*p)        /* A single '-', so echo it. */
+                       goto just_echo;
+
+               do {
+                       if (!strrchr("neE", *p))
+                               goto just_echo;
+               } while (*++p);
+
+               /* All of the options in this arg are valid, so handle them. */
+               p = arg + 1;
+               do {
+                       if (*p == 'n')
+                               nflag = 0;
+                       if (*p == 'e')
+                               eflag = '\\';
+               } while (*++p);
+       }
+ just_echo:
+#endif
+       while (1) {
+               /* arg is already == *argv and isn't NULL */
+               int c;
+
+               if (!eflag) {
+                       /* optimization for very common case */
+                       fputs(arg, stdout);
+               } else while ((c = *arg++)) {
+                       if (c == eflag) {       /* Check for escape seq. */
+                               if (*arg == 'c') {
+                                       /* '\c' means cancel newline and
+                                        * ignore all subsequent chars. */
+                                       goto ret;
+                               }
+#if !ENABLE_FEATURE_FANCY_ECHO
+                               /* SUSv3 specifies that octal escapes must begin with '0'. */
+                               if ( ((int)(unsigned char)(*arg) - '0') >= 8) /* '8' or bigger */
+#endif
+                               {
+                                       /* Since SUSv3 mandates a first digit of 0, 4-digit octals
+                                       * of the form \0### are accepted. */
+                                       if (*arg == '0') {
+                                               /* NB: don't turn "...\0" into "...\" */
+                                               if (arg[1] && ((unsigned char)(arg[1]) - '0') < 8) {
+                                                       arg++;
+                                               }
+                                       }
+                                       /* bb_process_escape_sequence handles NUL correctly
+                                        * ("...\" case). */
+                                       c = bb_process_escape_sequence(&arg);
+                               }
+                       }
+                       bb_putchar(c);
+               }
+
+               arg = *++argv;
+               if (!arg)
+                       break;
+               bb_putchar(' ');
+       }
+
+ newline_ret:
+       if (nflag) {
+               bb_putchar('\n');
+       }
+ ret:
+       return fflush(stdout);
+}
+
+/*-
+ * 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. <BSD Advertising Clause omitted per the July 22, 1999 licensing change
+ *             ftp://ftp.cs.berkeley.edu/pub/4bsd/README.Impt.License.Change>
+ *
+ *     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
+ */
+
+#ifdef VERSION_WITH_WRITEV
+/* We can't use stdio.
+ * The reason for this is highly non-obvious.
+ * echo_main is used from shell. Shell must correctly handle "echo foo"
+ * if stdout is closed. With stdio, output gets shoveled into
+ * stdout buffer, and even fflush cannot clear it out. It seems that
+ * even if libc receives EBADF on write attempts, it feels determined
+ * to output data no matter what. So it will try later,
+ * and possibly will clobber future output. Not good.
+ *
+ * Using writev instead, with 'direct' conversion of argv vector.
+ */
+
+int echo_main(int argc, char **argv)
+{
+       struct iovec io[argc];
+       struct iovec *cur_io = io;
+       char *arg;
+       char *p;
+#if !ENABLE_FEATURE_FANCY_ECHO
+       enum {
+               eflag = '\\',
+               nflag = 1,  /* 1 -- print '\n' */
+       };
+       arg = *++argv;
+       if (!arg)
+               goto newline_ret;
+#else
+       char nflag = 1;
+       char eflag = 0;
+
+       while (1) {
+               arg = *++argv;
+               if (!arg)
+                       goto newline_ret;
+               if (*arg != '-')
+                       break;
+
+               /* 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.
+                */
+               p = arg + 1;
+               if (!*p)        /* A single '-', so echo it. */
+                       goto just_echo;
+
+               do {
+                       if (!strrchr("neE", *p))
+                               goto just_echo;
+               } while (*++p);
+
+               /* All of the options in this arg are valid, so handle them. */
+               p = arg + 1;
+               do {
+                       if (*p == 'n')
+                               nflag = 0;
+                       if (*p == 'e')
+                               eflag = '\\';
+               } while (*++p);
+       }
+ just_echo:
+#endif
+
+       while (1) {
+               /* arg is already == *argv and isn't NULL */
+               int c;
+
+               cur_io->iov_base = p = arg;
+
+               if (!eflag) {
+                       /* optimization for very common case */
+                       p += strlen(arg);
+               } else while ((c = *arg++)) {
+                       if (c == eflag) {       /* Check for escape seq. */
+                               if (*arg == 'c') {
+                                       /* '\c' means cancel newline and
+                                        * ignore all subsequent chars. */
+                                       cur_io->iov_len = p - (char*)cur_io->iov_base;
+                                       cur_io++;
+                                       goto ret;
+                               }
+#if !ENABLE_FEATURE_FANCY_ECHO
+                               /* SUSv3 specifies that octal escapes must begin with '0'. */
+                               if ( (((unsigned char)*arg) - '1') >= 7)
+#endif
+                               {
+                                       /* Since SUSv3 mandates a first digit of 0, 4-digit octals
+                                       * of the form \0### are accepted. */
+                                       if (*arg == '0' && ((unsigned char)(arg[1]) - '0') < 8) {
+                                               arg++;
+                                       }
+                                       /* bb_process_escape_sequence can handle nul correctly */
+                                       c = bb_process_escape_sequence( (void*) &arg);
+                               }
+                       }
+                       *p++ = c;
+               }
+
+               arg = *++argv;
+               if (arg)
+                       *p++ = ' ';
+               cur_io->iov_len = p - (char*)cur_io->iov_base;
+               cur_io++;
+               if (!arg)
+                       break;
+       }
+
+ newline_ret:
+       if (nflag) {
+               cur_io->iov_base = (char*)"\n";
+               cur_io->iov_len = 1;
+               cur_io++;
+       }
+ ret:
+       /* TODO: implement and use full_writev? */
+       return writev(1, io, (cur_io - io)) >= 0;
+}
+#endif
diff --git a/coreutils/env.c b/coreutils/env.c
new file mode 100644 (file)
index 0000000..f50a03e
--- /dev/null
@@ -0,0 +1,125 @@
+/* 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 <andersen@codepoet.org>
+ */
+
+/* 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 <dzo@simtreas.ru> (C) 2003
+ * - correct "-" option usage
+ * - multiple "-u unsetenv" support
+ * - GNU long option support
+ * - use xfunc_error_retval
+ */
+
+/* This is a NOEXEC applet. Be very careful! */
+
+#include "libbb.h"
+
+#if ENABLE_FEATURE_ENV_LONG_OPTIONS
+static const char env_longopts[] ALIGN1 =
+       "ignore-environment\0" No_argument       "i"
+       "unset\0"              Required_argument "u"
+       ;
+#endif
+
+int env_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int env_main(int argc UNUSED_PARAM, char **argv)
+{
+       char **ep;
+       unsigned opt;
+       llist_t *unset_env = NULL;
+
+       opt_complementary = "u::";
+#if ENABLE_FEATURE_ENV_LONG_OPTIONS
+       applet_long_options = env_longopts;
+#endif
+       opt = getopt32(argv, "+iu:", &unset_env);
+       argv += optind;
+       if (*argv && LONE_DASH(argv[0])) {
+               opt |= 1;
+               ++argv;
+       }
+       if (opt & 1) {
+               clearenv();
+       }
+       while (unset_env) {
+               char *var = llist_pop(&unset_env);
+               /* This does not handle -uVAR=VAL
+                * (coreutils _sets_ the variable in that case): */
+               /*unsetenv(var);*/
+               /* This does, but uses somewhan undocumented feature that
+                * putenv("name_without_equal_sign") unsets the variable: */
+               putenv(var);
+       }
+
+       while (*argv && (strchr(*argv, '=') != NULL)) {
+               if (putenv(*argv) < 0) {
+                       bb_perror_msg_and_die("putenv");
+               }
+               ++argv;
+       }
+
+       if (*argv) {
+               BB_EXECVP(*argv, argv);
+               /* SUSv3-mandated exit codes. */
+               xfunc_error_retval = (errno == ENOENT) ? 127 : 126;
+               bb_simple_perror_msg_and_die(*argv);
+       }
+
+       for (ep = environ; *ep; ep++) {
+               puts(*ep);
+       }
+
+       fflush_stdout_and_exit(EXIT_SUCCESS);
+}
+
+/*
+ * 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. <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/coreutils/expand.c b/coreutils/expand.c
new file mode 100644 (file)
index 0000000..0967e25
--- /dev/null
@@ -0,0 +1,180 @@
+/* expand - convert tabs to spaces
+ * unexpand - convert spaces to tabs
+ *
+ * Copyright (C) 89, 91, 1995-2006 Free Software Foundation, Inc.
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ *
+ * David MacKenzie <djm@gnu.ai.mit.edu>
+ *
+ * Options for expand:
+ * -t num  --tabs=NUM      Convert tabs to num spaces (default 8 spaces).
+ * -i      --initial       Only convert initial tabs on each line to spaces.
+ *
+ * Options for unexpand:
+ * -a      --all           Convert all blanks, instead of just initial blanks.
+ * -f      --first-only    Convert only leading sequences of blanks (default).
+ * -t num  --tabs=NUM      Have tabs num characters apart instead of 8.
+ *
+ *  Busybox version (C) 2007 by Tito Ragusa <farmatito@tiscali.it>
+ *
+ *  Caveat: this versions of expand and unexpand don't accept tab lists.
+ */
+
+#include "libbb.h"
+
+enum {
+       OPT_INITIAL     = 1 << 0,
+       OPT_TABS        = 1 << 1,
+       OPT_ALL         = 1 << 2,
+};
+
+#if ENABLE_EXPAND
+static void expand(FILE *file, int tab_size, unsigned opt)
+{
+       char *line;
+
+       tab_size = -tab_size;
+
+       while ((line = xmalloc_fgets(file)) != NULL) {
+               int pos;
+               unsigned char c;
+               char *ptr = line;
+
+               goto start;
+               while ((c = *ptr) != '\0') {
+                       if ((opt & OPT_INITIAL) && !isblank(c)) {
+                               fputs(ptr, stdout);
+                               break;
+                       }
+                       ptr++;
+                       if (c == '\t') {
+                               c = ' ';
+                               while (++pos < 0)
+                                       bb_putchar(c);
+                       }
+                       bb_putchar(c);
+                       if (++pos >= 0) {
+ start:
+                               pos = tab_size;
+                       }
+               }
+               free(line);
+       }
+}
+#endif
+
+#if ENABLE_UNEXPAND
+static void unexpand(FILE *file, unsigned tab_size, unsigned opt)
+{
+       char *line;
+
+       while ((line = xmalloc_fgets(file)) != NULL) {
+               char *ptr = line;
+               unsigned column = 0;
+
+               while (*ptr) {
+                       unsigned n;
+
+                       while (*ptr == ' ') {
+                               column++;
+                               ptr++;
+                       }
+                       if (*ptr == '\t') {
+                               column += tab_size - (column % tab_size);
+                               ptr++;
+                               continue;
+                       }
+
+                       n = column / tab_size;
+                       column = column % tab_size;
+                       while (n--)
+                               putchar('\t');
+
+                       if ((opt & OPT_INITIAL) && ptr != line) {
+                               printf("%*s%s", column, "", ptr);
+                               break;
+                       }
+                       n = strcspn(ptr, "\t ");
+                       printf("%*s%.*s", column, "", n, ptr);
+                       ptr += n;
+                       column = (column + n) % tab_size;
+               }
+               free(line);
+       }
+}
+#endif
+
+int expand_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int expand_main(int argc UNUSED_PARAM, char **argv)
+{
+       /* Default 8 spaces for 1 tab */
+       const char *opt_t = "8";
+       FILE *file;
+       unsigned tab_size;
+       unsigned opt;
+       int exit_status = EXIT_SUCCESS;
+
+#if ENABLE_FEATURE_EXPAND_LONG_OPTIONS
+       static const char expand_longopts[] ALIGN1 =
+               /* name, has_arg, val */
+               "initial\0"          No_argument       "i"
+               "tabs\0"             Required_argument "t"
+       ;
+#endif
+#if ENABLE_FEATURE_UNEXPAND_LONG_OPTIONS
+       static const char unexpand_longopts[] ALIGN1 =
+               /* name, has_arg, val */
+               "first-only\0"       No_argument       "i"
+               "tabs\0"             Required_argument "t"
+               "all\0"              No_argument       "a"
+       ;
+#endif
+
+       if (ENABLE_EXPAND && (!ENABLE_UNEXPAND || applet_name[0] == 'e')) {
+               USE_FEATURE_EXPAND_LONG_OPTIONS(applet_long_options = expand_longopts);
+               opt = getopt32(argv, "it:", &opt_t);
+       } else {
+               USE_FEATURE_UNEXPAND_LONG_OPTIONS(applet_long_options = unexpand_longopts);
+               /* -t NUM sets also -a */
+               opt_complementary = "ta";
+               opt = getopt32(argv, "ft:a", &opt_t);
+               /* -f --first-only is the default */
+               if (!(opt & OPT_ALL)) opt |= OPT_INITIAL;
+       }
+       tab_size = xatou_range(opt_t, 1, UINT_MAX);
+
+       argv += optind;
+
+       if (!*argv) {
+               *--argv = (char*)bb_msg_standard_input;
+       }
+       do {
+               file = fopen_or_warn_stdin(*argv);
+               if (!file) {
+                       exit_status = EXIT_FAILURE;
+                       continue;
+               }
+
+               if (ENABLE_EXPAND && (!ENABLE_UNEXPAND || applet_name[0] == 'e'))
+                       USE_EXPAND(expand(file, tab_size, opt));
+               else
+                       USE_UNEXPAND(unexpand(file, tab_size, opt));
+
+               /* Check and close the file */
+               if (fclose_if_not_stdin(file)) {
+                       bb_simple_perror_msg(*argv);
+                       exit_status = EXIT_FAILURE;
+               }
+               /* If stdin also clear EOF */
+               if (file == stdin)
+                       clearerr(file);
+       } while (*++argv);
+
+       /* Now close stdin also */
+       /* (if we didn't read from it, it's a no-op) */
+       if (fclose(stdin))
+               bb_perror_msg_and_die(bb_msg_standard_input);
+
+       fflush_stdout_and_exit(exit_status);
+}
diff --git a/coreutils/expr.c b/coreutils/expr.c
new file mode 100644 (file)
index 0000000..54c2ee1
--- /dev/null
@@ -0,0 +1,501 @@
+/* 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 <edward@debian.org>.
+ * Copyright (C) 2003-2005  Vladimir Oleynik <dzo@simtreas.ru>
+ *  - 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 "libbb.h"
+#include "xregex.h"
+
+#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... */
+
+/* The kinds of value we can have.  */
+enum {
+       INTEGER,
+       STRING
+};
+
+/* A value is.... */
+struct valinfo {
+       smallint 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.  */
+struct globals {
+       char **args;
+};
+#define G (*(struct globals*)&bb_common_bufsiz1)
+
+/* forward declarations */
+static VALUE *eval(void);
+
+
+/* Return a VALUE for I.  */
+
+static VALUE *int_value(arith_t i)
+{
+       VALUE *v;
+
+       v = xzalloc(sizeof(VALUE));
+       if (INTEGER) /* otherwise xzaaloc did it already */
+               v->type = INTEGER;
+       v->u.i = i;
+       return v;
+}
+
+/* Return a VALUE for S.  */
+
+static VALUE *str_value(const char *s)
+{
+       VALUE *v;
+
+       v = xzalloc(sizeof(VALUE));
+       if (STRING) /* otherwise xzaaloc did it already */
+               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;
+       /* STRING: */
+       return v->u.s[0] == '\0' || LONE_CHAR(v->u.s, '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 bool 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 str[0]+str[1] if the next token matches STR exactly.
+   STR must not be NULL.  */
+
+static int nextarg(const char *str)
+{
+       if (*G.args == NULL || strcmp(*G.args, str) != 0)
+               return 0;
+       return (unsigned char)str[0] + (unsigned char)str[1];
+}
+
+/* The comparison operator handling functions.  */
+
+static int cmp_common(VALUE *l, VALUE *r, int op)
+{
+       arith_t ll, rr;
+
+       ll = l->u.i;
+       rr = r->u.i;
+       if (l->type == STRING || r->type == STRING) {
+               tostring(l);
+               tostring(r);
+               ll = strcmp(l->u.s, r->u.s);
+               rr = 0;
+       }
+       /* calculating ll - rr and checking the result is prone to overflows.
+        * We'll do it differently: */
+       if (op == '<')
+               return ll < rr;
+       if (op == ('<' + '='))
+               return ll <= rr;
+       if (op == '=' || (op == '=' + '='))
+               return ll == rr;
+       if (op == '!' + '=')
+               return ll != rr;
+       if (op == '>')
+               return ll > rr;
+       /* >= */
+       return ll >= rr;
+}
+
+/* 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 == '+')
+               return li + ri;
+       if (op == '-')
+               return li - ri;
+       if (op == '*')
+               return li * ri;
+       if (ri == 0)
+               bb_error_msg_and_die("division by zero");
+       if (op == '/')
+               return li / ri;
+       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] == '^') {
+               bb_error_msg(
+"warning: '%s': using '^' as the first character\n"
+"of a basic regular expression is not portable; it is ignored", pv->u.s);
+       }
+
+       memset(&re_buffer, 0, sizeof(re_buffer));
+       memset(re_regs, 0, sizeof(re_regs));
+       xregcomp(&re_buffer, pv->u.s, 0);
+
+       /* 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 && re_regs[1].rm_so >= 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);
+       }
+       regfree(&re_buffer);
+       return v;
+}
+
+/* Handle bare operands and ( expr ) syntax.  */
+
+static VALUE *eval7(void)
+{
+       VALUE *v;
+
+       if (!*G.args)
+               bb_error_msg_and_die("syntax error");
+
+       if (nextarg("(")) {
+               G.args++;
+               v = eval();
+               if (!nextarg(")"))
+                       bb_error_msg_and_die("syntax error");
+               G.args++;
+               return v;
+       }
+
+       if (nextarg(")"))
+               bb_error_msg_and_die("syntax error");
+
+       return str_value(*G.args++);
+}
+
+/* Handle match, substr, index, length, and quote keywords.  */
+
+static VALUE *eval6(void)
+{
+       static const char keywords[] ALIGN1 =
+               "quote\0""length\0""match\0""index\0""substr\0";
+
+       VALUE *r, *i1, *i2;
+       VALUE *l = l; /* silence gcc */
+       VALUE *v = v; /* silence gcc */
+       int key = *G.args ? index_in_strings(keywords, *G.args) + 1 : 0;
+
+       if (key == 0) /* not a keyword */
+               return eval7();
+       G.args++; /* We have a valid token, so get the next argument.  */
+       if (key == 1) { /* quote */
+               if (!*G.args)
+                       bb_error_msg_and_die("syntax error");
+               return str_value(*G.args++);
+       }
+       if (key == 2) { /* length */
+               r = eval6();
+               tostring(r);
+               v = int_value(strlen(r->u.s));
+               freev(r);
+       } else
+               l = eval6();
+
+       if (key == 3) { /* match */
+               r = eval6();
+               v = docolon(l, r);
+               freev(l);
+               freev(r);
+       }
+       if (key == 4) { /* index */
+               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);
+       }
+       if (key == 5) { /* substr */
+               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;
+
+}
+
+/* Handle : operator (pattern matching).
+   Calls docolon to do the real work.  */
+
+static VALUE *eval5(void)
+{
+       VALUE *l, *r, *v;
+
+       l = eval6();
+       while (nextarg(":")) {
+               G.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) {
+               op = nextarg("*");
+               if (!op) { op = nextarg("/");
+                if (!op) { op = nextarg("%");
+                 if (!op) return l;
+               }}
+               G.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) {
+               op = nextarg("+");
+               if (!op) {
+                       op = nextarg("-");
+                       if (!op) return l;
+               }
+               G.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) {
+               op = nextarg("<");
+               if (!op) { op = nextarg("<=");
+                if (!op) { op = nextarg("=");
+                 if (!op) { op = nextarg("==");
+                  if (!op) { op = nextarg("!=");
+                   if (!op) { op = nextarg(">=");
+                    if (!op) { op = nextarg(">");
+                     if (!op) return l;
+               }}}}}}
+               G.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("&")) {
+               G.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("|")) {
+               G.args++;
+               r = eval1();
+               if (null(l)) {
+                       freev(l);
+                       l = r;
+               } else
+                       freev(r);
+       }
+       return l;
+}
+
+int expr_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int expr_main(int argc UNUSED_PARAM, char **argv)
+{
+       VALUE *v;
+
+       xfunc_error_retval = 2; /* coreutils compat */
+       G.args = argv + 1;
+       if (*G.args == NULL) {
+               bb_error_msg_and_die("too few arguments");
+       }
+       v = eval();
+       if (*G.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));
+}
diff --git a/coreutils/false.c b/coreutils/false.c
new file mode 100644 (file)
index 0000000..f448ebf
--- /dev/null
@@ -0,0 +1,21 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Mini false 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.
+ */
+
+/* BB_AUDIT SUSv3 compliant */
+/* http://www.opengroup.org/onlinepubs/000095399/utilities/false.html */
+
+#include "libbb.h"
+
+/* This is a NOFORK applet. Be very careful! */
+
+int false_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int false_main(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
+{
+       return EXIT_FAILURE;
+}
diff --git a/coreutils/fold.c b/coreutils/fold.c
new file mode 100644 (file)
index 0000000..e2a30d5
--- /dev/null
@@ -0,0 +1,151 @@
+/* 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 "libbb.h"
+
+/* Must match getopt32 call */
+#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 (!(option_mask32 & 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) MAIN_EXTERNALLY_VISIBLE;
+int fold_main(int argc, char **argv)
+{
+       char *line_out = NULL;
+       int allocated_out = 0;
+       char *w_opt;
+       int width = 80;
+       int i;
+       int errs = 0;
+
+       if (ENABLE_INCLUDE_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);
+                       }
+               }
+       }
+
+       getopt32(argv, "bsw:", &w_opt);
+       if (option_mask32 & FLAG_WIDTH)
+               width = xatoul_range(w_opt, 1, 10000);
+
+       argv += optind;
+       if (!*argv)
+               *--argv = (char*)"-";
+
+       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. */
+
+               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 (option_mask32 & 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);
+                                               bb_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 (fclose_if_not_stdin(istream)) {
+                       bb_simple_perror_msg(*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 (file)
index 0000000..ac476d0
--- /dev/null
@@ -0,0 +1,140 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * head implementation for busybox
+ *
+ * Copyright (C) 2003  Manuel Novoa III  <mjn3@codepoet.org>
+ *
+ * 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 "libbb.h"
+
+static const char head_opts[] ALIGN1 =
+       "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 },
+       { }
+};
+#endif
+
+static const char header_fmt_str[] ALIGN1 = "\n==> %s <==\n";
+
+int head_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+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_INCLUDE_SUSv2 || ENABLE_FEATURE_FANCY_HEAD
+       /* Allow legacy syntax of an initial numeric option without -n. */
+       if (argv[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_INCLUDE_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();
+               }
+       }
+
+       argc -= optind;
+       argv += optind;
+       if (!*argv)
+               *--argv = (char*)"-";
+
+       fmt = header_fmt_str + 1;
+#if ENABLE_FEATURE_FANCY_HEAD
+       if (argc <= header_threshhold) {
+               header_threshhold = 0;
+       }
+#else
+       if (argc <= 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_simple_perror_msg(*argv);
+                               retval = EXIT_FAILURE;
+                       }
+                       die_if_ferror_stdout();
+               } else {
+                       retval = EXIT_FAILURE;
+               }
+               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 (file)
index 0000000..2794510
--- /dev/null
@@ -0,0 +1,26 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Mini hostid implementation for busybox
+ *
+ * Copyright (C) 2000  Edward Betts <edward@debian.org>.
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+/* BB_AUDIT SUSv3 N/A -- Matches GNU behavior. */
+
+#include "libbb.h"
+
+/* This is a NOFORK applet. Be very careful! */
+
+int hostid_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int hostid_main(int argc, char **argv UNUSED_PARAM)
+{
+       if (argc > 1) {
+               bb_show_usage();
+       }
+
+       printf("%lx\n", gethostid());
+
+       return fflush(stdout);
+}
diff --git a/coreutils/id.c b/coreutils/id.c
new file mode 100644 (file)
index 0000000..43f403f
--- /dev/null
@@ -0,0 +1,214 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Mini id implementation for busybox
+ *
+ * Copyright (C) 2000 by Randolph Chung <tausq@debian.org>
+ * Copyright (C) 2008 by Tito Ragusa <farmatito@tiscali.it>
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+/* BB_AUDIT SUSv3 compliant. */
+/* Hacked by Tito Ragusa (C) 2004 to handle usernames of whatever
+ * length and to be more similar to GNU id.
+ * -Z option support: by Yuichi Nakamura <ynakam@hitachisoft.jp>
+ * Added -G option Tito Ragusa (C) 2008 for SUSv3.
+ */
+
+#include "libbb.h"
+
+#if !ENABLE_USE_BB_PWD_GRP
+#if defined(__UCLIBC_MAJOR__) && (__UCLIBC_MAJOR__ == 0)
+#if (__UCLIBC_MINOR__ < 9) || (__UCLIBC_MINOR__ == 9 &&  __UCLIBC_SUBLEVEL__ < 30)
+#error "Sorry, you need at least uClibc version 0.9.30 for id applet to build"
+#endif
+#endif
+#endif
+
+enum {
+       PRINT_REAL      = (1 << 0),
+       NAME_NOT_NUMBER = (1 << 1),
+       JUST_USER       = (1 << 2),
+       JUST_GROUP      = (1 << 3),
+       JUST_ALL_GROUPS = (1 << 4),
+#if ENABLE_SELINUX
+       JUST_CONTEXT    = (1 << 5),
+#endif
+};
+
+static int print_common(unsigned id, const char *name, const char *prefix)
+{
+       if (prefix) {
+               printf("%s", prefix);
+       }
+       if (!(option_mask32 & NAME_NOT_NUMBER) || !name) {
+               printf("%u", id);
+       }
+       if (!option_mask32 || (option_mask32 & NAME_NOT_NUMBER)) {
+               if (name) {
+                       printf(option_mask32 ? "%s" : "(%s)", name);
+               } else {
+                       /* Don't set error status flag in default mode */
+                       if (option_mask32) {
+                               if (ENABLE_DESKTOP)
+                                       bb_error_msg("unknown ID %u", id);
+                               return EXIT_FAILURE;
+                       }
+               }
+       }
+       return EXIT_SUCCESS;
+}
+
+static int print_group(gid_t id, const char *prefix)
+{
+       return print_common(id, gid2group(id), prefix);
+}
+
+static int print_user(uid_t id, const char *prefix)
+{
+       return print_common(id, uid2uname(id), prefix);
+}
+
+/* On error set *n < 0 and return >= 0
+ * If *n is too small, update it and return < 0
+ *  (ok to trash groups[] in both cases)
+ * Otherwise fill in groups[] and return >= 0
+ */
+static int get_groups(const char *username, gid_t rgid, gid_t *groups, int *n)
+{
+       int m;
+
+       if (username) {
+               /* If the user is a member of more than
+                * *n groups, then -1 is returned. Otherwise >= 0.
+                * (and no defined way of detecting errors?!) */
+               m = getgrouplist(username, rgid, groups, n);
+               /* I guess *n < 0 might indicate error. Anyway,
+                * malloc'ing -1 bytes won't be good, so: */
+               //if (*n < 0)
+               //      return 0;
+               //return m;
+               //commented out here, happens below anyway
+       } else {
+               /* On error -1 is returned, which ends up in *n */
+               int nn = getgroups(*n, groups);
+               /* 0: nn <= *n, groups[] was big enough; -1 otherwise */
+               m = - (nn > *n);
+               *n = nn;
+       }
+       if (*n < 0)
+               return 0; /* error, don't return < 0! */
+       return m;
+}
+
+int id_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int id_main(int argc UNUSED_PARAM, char **argv)
+{
+       uid_t ruid;
+       gid_t rgid;
+       uid_t euid;
+       gid_t egid;
+       unsigned opt;
+       int i;
+       int status = EXIT_SUCCESS;
+       const char *prefix;
+       const char *username;
+#if ENABLE_SELINUX
+       security_context_t scontext = NULL;
+#endif
+       /* Don't allow -n -r -nr -ug -rug -nug -rnug -uZ -gZ -GZ*/
+       /* Don't allow more than one username */
+       opt_complementary = "?1:u--g:g--u:G--u:u--G:g--G:G--g:r?ugG:n?ugG"
+                        USE_SELINUX(":u--Z:Z--u:g--Z:Z--g:G--Z:Z--G");
+       opt = getopt32(argv, "rnugG" USE_SELINUX("Z"));
+
+       username = argv[optind];
+       if (username) {
+               struct passwd *p = xgetpwnam(username);
+               euid = ruid = p->pw_uid;
+               egid = rgid = p->pw_gid;
+       } else {
+               egid = getegid();
+               rgid = getgid();
+               euid = geteuid();
+               ruid = getuid();
+       }
+       /* JUST_ALL_GROUPS ignores -r PRINT_REAL flag even if man page for */
+       /* id says: print the real ID instead of the effective ID, with -ugG */
+       /* in fact in this case egid is always printed if egid != rgid */
+       if (!opt || (opt & JUST_ALL_GROUPS)) {
+               gid_t *groups;
+               int n;
+
+               if (!opt) {
+                       /* Default Mode */
+                       status |= print_user(ruid, "uid=");
+                       status |= print_group(rgid, " gid=");
+                       if (euid != ruid)
+                               status |= print_user(euid, " euid=");
+                       if (egid != rgid)
+                               status |= print_group(egid, " egid=");
+               } else {
+                       /* JUST_ALL_GROUPS */
+                       status |= print_group(rgid, NULL);
+                       if (egid != rgid)
+                               status |= print_group(egid, " ");
+               }
+               /* We are supplying largish buffer, trying
+                * to not run get_groups() twice. That might be slow
+                * ("user database in remote SQL server" case) */
+               groups = xmalloc(64 * sizeof(gid_t));
+               n = 64;
+               if (get_groups(username, rgid, groups, &n) < 0) {
+                       /* Need bigger buffer after all */
+                       groups = xrealloc(groups, n * sizeof(gid_t));
+                       get_groups(username, rgid, groups, &n);
+               }
+               if (n > 0) {
+                       /* Print the list */
+                       prefix = " groups=";
+                       for (i = 0; i < n; i++) {
+                               if (opt && (groups[i] == rgid || groups[i] == egid))
+                                       continue;
+                               status |= print_group(groups[i], opt ? " " : prefix);
+                               prefix = ",";
+                       }
+               } else if (n < 0) { /* error in get_groups() */
+                       if (!ENABLE_DESKTOP)
+                               bb_error_msg_and_die("cannot get groups");
+                       else
+                               return EXIT_FAILURE;
+               }
+               if (ENABLE_FEATURE_CLEAN_UP)
+                       free(groups);
+#if ENABLE_SELINUX
+               if (is_selinux_enabled()) {
+                       if (getcon(&scontext) == 0)
+                               printf(" context=%s", scontext);
+               }
+#endif
+       } else if (opt & PRINT_REAL) {
+               euid = ruid;
+               egid = rgid;
+       }
+
+       if (opt & JUST_USER)
+               status |= print_user(euid, NULL);
+       else if (opt & JUST_GROUP)
+               status |= print_group(egid, NULL);
+#if ENABLE_SELINUX
+       else if (opt & JUST_CONTEXT) {
+               selinux_or_die();
+               if (username || getcon(&scontext)) {
+                       bb_error_msg_and_die("can't get process context%s",
+                               username ? " for a different user" : "");
+               }
+               fputs(scontext, stdout);
+       }
+       /* freecon(NULL) seems to be harmless */
+       if (ENABLE_FEATURE_CLEAN_UP)
+               freecon(scontext);
+#endif
+       bb_putchar('\n');
+       fflush_stdout_and_exit(status);
+}
diff --git a/coreutils/id_test.sh b/coreutils/id_test.sh
new file mode 100755 (executable)
index 0000000..0d65f2a
--- /dev/null
@@ -0,0 +1,244 @@
+#!/bin/bash
+# Test script for busybox id vs. coreutils id.
+# Needs root privileges for some tests.
+
+cp /usr/bin/id .
+BUSYBOX=./busybox
+ID=./id
+LIST=`awk -F: '{ printf "%s\n", $1 }' /etc/passwd`
+FLAG_USER_EXISTS="no"
+TEST_USER="f583ca884c1d93458fb61ed137ff44f6"
+
+echo "test 1: id [options] nousername"
+rm -f foo bar
+for OPTIONS in "" "-u" "-un" "-unr" "-g" "-gn" "-gnr" "-G" "-Gn" "-Gnr"
+do
+       #echo "$OPTIONS"
+       $BUSYBOX id $OPTIONS >foo 2>/dev/null
+       RET1=$?
+       $ID $OPTIONS >bar 2>/dev/null
+       RET2=$?
+       if test "$RET1" != "$RET2"; then
+               echo "Return Values differ ($RET1 != $RET2): options $OPTIONS"
+       fi
+       diff foo bar
+done
+
+echo "test 2: id [options] username"
+rm -f foo bar
+for OPTIONS in "" "-u" "-un" "-unr" "-g" "-gn" "-gnr" "-G" "-Gn" "-Gnr"
+do
+       #echo "$OPTIONS"
+       for i in $LIST ; do
+               if test "$i" = "$TEST_USER"; then
+                       FLAG_USER_EXISTS="yes"
+               fi
+               $BUSYBOX id $OPTIONS $i >foo 2>/dev/null
+               RET1=$?
+               $ID $OPTIONS $i >bar 2>/dev/null
+               RET2=$?
+               if test "$RET1" != "$RET2"; then
+                       echo "Return Values differ ($RET1 != $RET2): options $OPTIONS"
+               fi
+               diff foo bar
+       done
+done
+
+if test $FLAG_USER_EXISTS = "yes"; then
+       echo "test 3,4,5,6,7,8,9,10,11,12 skipped because test user $TEST_USER already exists"
+       rm -f foo bar
+       exit 1
+fi
+
+adduser -s /bin/true -g "" -H -D "$TEST_USER" || exit 1
+
+chown $TEST_USER.$TEST_USER $BUSYBOX
+chmod u+s $BUSYBOX 2>&1 /dev/null
+chown $TEST_USER.$TEST_USER $ID
+chmod u+s $ID 2>&1 /dev/null
+
+echo "test 3 setuid, existing user: id [options] no username"
+rm -f foo bar
+for OPTIONS in "" "-u" "-un" "-unr" "-g" "-gn" "-gnr" "-G" "-Gn" "-Gnr"
+do
+       #echo "$OPTIONS"
+       $BUSYBOX id $OPTIONS >foo 2>/dev/null
+       RET1=$?
+       $ID $OPTIONS >bar 2>/dev/null
+       RET2=$?
+       if test "$RET1" != "$RET2"; then
+               echo "Return Values differ ($RET1 != $RET2): options $OPTIONS"
+       fi
+       diff foo bar
+       #done
+done
+
+echo "test 4 setuid, existing user: id [options] username"
+rm -f foo bar
+for OPTIONS in "" "-u" "-un" "-unr" "-g" "-gn" "-gnr" "-G" "-Gn" "-Gnr"
+do
+       #echo "$OPTIONS"
+       for i in $LIST ; do
+               $BUSYBOX id $OPTIONS $i >foo 2>/dev/null
+               RET1=$?
+               $ID $OPTIONS $i >bar 2>/dev/null
+               RET2=$?
+               if test "$RET1" != "$RET2"; then
+                       echo "Return Values differ ($RET1 != $RET2): options $OPTIONS"
+               fi
+               diff foo bar
+       done
+done
+
+chown $TEST_USER.$TEST_USER $BUSYBOX
+chmod g+s $BUSYBOX 2>&1 /dev/null
+chown $TEST_USER.$TEST_USER $ID
+chmod g+s $ID 2>&1 /dev/null
+
+echo "test 5 setgid, existing user: id [options] no username"
+rm -f foo bar
+for OPTIONS in "" "-u" "-un" "-unr" "-g" "-gn" "-gnr" "-G" "-Gn" "-Gnr"
+do
+       #echo "$OPTIONS"
+       $BUSYBOX id $OPTIONS >foo 2>/dev/null
+       RET1=$?
+       $ID $OPTIONS >bar 2>/dev/null
+       RET2=$?
+       if test "$RET1" != "$RET2"; then
+               echo "Return Values differ ($RET1 != $RET2): options $OPTIONS"
+       fi
+       diff foo bar
+       #done
+done
+
+echo "test 6 setgid, existing user: id [options] username"
+rm -f foo bar
+for OPTIONS in "" "-u" "-un" "-unr" "-g" "-gn" "-gnr" "-G" "-Gn" "-Gnr"
+do
+       #echo "$OPTIONS"
+       for i in $LIST ; do
+               $BUSYBOX id $OPTIONS $i >foo 2>/dev/null
+               RET1=$?
+               $ID $OPTIONS $i >bar 2>/dev/null
+               RET2=$?
+               if test "$RET1" != "$RET2"; then
+                       echo "Return Values differ ($RET1 != $RET2): options $OPTIONS"
+               fi
+               diff foo bar
+       done
+done
+
+chown $TEST_USER.$TEST_USER $BUSYBOX
+chmod u+s,g+s $BUSYBOX 2>&1 /dev/null
+chown $TEST_USER.$TEST_USER $ID
+chmod u+s,g+s $ID 2>&1 /dev/null
+
+echo "test 7 setuid, setgid, existing user: id [options] no username"
+rm -f foo bar
+for OPTIONS in "" "-u" "-un" "-unr" "-g" "-gn" "-gnr" "-G" "-Gn" "-Gnr"
+do
+       #echo "$OPTIONS"
+       $BUSYBOX id $OPTIONS >foo 2>/dev/null
+       RET1=$?
+       $ID $OPTIONS >bar 2>/dev/null
+       RET2=$?
+       if test "$RET1" != "$RET2"; then
+               echo "Return Values differ ($RET1 != $RET2): options $OPTIONS"
+       fi
+       diff foo bar
+       #done
+done
+
+echo "test 8 setuid, setgid, existing user: id [options] username"
+rm -f foo bar
+for OPTIONS in "" "-u" "-un" "-unr" "-g" "-gn" "-gnr" "-G" "-Gn" "-Gnr"
+do
+       #echo "$OPTIONS"
+       for i in $LIST ; do
+               $BUSYBOX id $OPTIONS $i >foo 2>/dev/null
+               RET1=$?
+               $ID $OPTIONS $i >bar 2>/dev/null
+               RET2=$?
+               if test "$RET1" != "$RET2"; then
+                       echo "Return Values differ ($RET1 != $RET2): options $OPTIONS"
+               fi
+               diff foo bar
+       done
+done
+
+deluser $TEST_USER || exit 1
+
+echo "test 9 setuid, setgid, not existing user: id [options] no username"
+rm -f foo bar
+for OPTIONS in "" "-u" "-un" "-unr" "-g" "-gn" "-gnr" "-G" "-Gn" "-Gnr"
+do
+       #echo "$OPTIONS"
+       $BUSYBOX id $OPTIONS >foo 2>/dev/null
+       RET1=$?
+       $ID $OPTIONS >bar 2>/dev/null
+       RET2=$?
+       if test "$RET1" != "$RET2"; then
+               echo "Return Values differ ($RET1 != $RET2): options $OPTIONS"
+       fi
+       diff foo bar
+done
+
+echo "test 10 setuid, setgid, not existing user: id [options] username"
+rm -f foo bar
+for OPTIONS in "" "-u" "-un" "-unr" "-g" "-gn" "-gnr" "-G" "-Gn" "-Gnr"
+do
+       #echo "$OPTIONS"
+       for i in $LIST ; do
+               $BUSYBOX id $OPTIONS $i >foo 2>/dev/null
+               RET1=$?
+               $ID $OPTIONS $i >bar 2>/dev/null
+               RET2=$?
+               if test "$RET1" != "$RET2"; then
+                       echo "Return Values differ ($RET1 != $RET2): options $OPTIONS"
+               fi
+               diff foo bar
+       done
+done
+
+chown .root $BUSYBOX 2>&1 /dev/null
+chown .root $ID 2>&1 /dev/null
+chmod g+s $BUSYBOX 2>&1 /dev/null
+chmod g+s $ID 2>&1 /dev/null
+
+echo "test 11 setgid, not existing group: id [options] no username"
+rm -f foo bar
+for OPTIONS in "" "-u" "-un" "-unr" "-g" "-gn" "-gnr" "-G" "-Gn" "-Gnr"
+do
+       #echo "$OPTIONS"
+       $BUSYBOX id $OPTIONS >foo 2>/dev/null
+       RET1=$?
+       $ID $OPTIONS >bar 2>/dev/null
+       RET2=$?
+       if test "$RET1" != "$RET2"; then
+               echo "Return Values differ ($RET1 != $RET2): options $OPTIONS"
+       fi
+       diff foo bar
+       #done
+done
+
+echo "test 12 setgid, not existing group: id [options] username"
+rm -f foo bar
+for OPTIONS in "" "-u" "-un" "-unr" "-g" "-gn" "-gnr" "-G" "-Gn" "-Gnr"
+do
+       #echo "$OPTIONS"
+       for i in $LIST ; do
+               $BUSYBOX id $OPTIONS $i >foo 2>/dev/null
+               RET1=$?
+               $ID $OPTIONS $i >bar 2>/dev/null
+               RET2=$?
+               if test "$RET1" != "$RET2"; then
+                       echo "Return Values differ ($RET1 != $RET2): options $OPTIONS"
+               fi
+               diff foo bar
+       done
+done
+
+chown root.root $BUSYBOX 2>&1 /dev/null
+chown root.root $ID 2>&1 /dev/null
+rm -f $ID
+rm -f foo bar
diff --git a/coreutils/install.c b/coreutils/install.c
new file mode 100644 (file)
index 0000000..2b796e2
--- /dev/null
@@ -0,0 +1,210 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Copyright (C) 2003 by Glenn McGrath
+ * SELinux support: by Yuichi Nakamura <ynakam@hitachisoft.jp>
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.h"
+#include "libcoreutils/coreutils.h"
+
+#if ENABLE_FEATURE_INSTALL_LONG_OPTIONS
+static const char install_longopts[] ALIGN1 =
+       "directory\0"           No_argument       "d"
+       "preserve-timestamps\0" No_argument       "p"
+       "strip\0"               No_argument       "s"
+       "group\0"               Required_argument "g"
+       "mode\0"                Required_argument "m"
+       "owner\0"               Required_argument "o"
+/* autofs build insists of using -b --suffix=.orig */
+/* TODO? (short option for --suffix is -S) */
+#if ENABLE_SELINUX
+       "context\0"             Required_argument "Z"
+       "preserve_context\0"    No_argument       "\xff"
+       "preserve-context\0"    No_argument       "\xff"
+#endif
+       ;
+#endif
+
+
+#if ENABLE_SELINUX
+static void setdefaultfilecon(const char *path)
+{
+       struct stat s;
+       security_context_t scontext = NULL;
+
+       if (!is_selinux_enabled()) {
+               return;
+       }
+       if (lstat(path, &s) != 0) {
+               return;
+       }
+
+       if (matchpathcon(path, s.st_mode, &scontext) < 0) {
+               goto out;
+       }
+       if (strcmp(scontext, "<<none>>") == 0) {
+               goto out;
+       }
+
+       if (lsetfilecon(path, scontext) < 0) {
+               if (errno != ENOTSUP) {
+                       bb_perror_msg("warning: failed to change context"
+                                       " of %s to %s", path, scontext);
+               }
+       }
+
+ out:
+       freecon(scontext);
+}
+
+#endif
+
+int install_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int install_main(int argc, char **argv)
+{
+       struct stat statbuf;
+       mode_t mode;
+       uid_t uid;
+       gid_t gid;
+       char *arg, *last;
+       const char *gid_str;
+       const char *uid_str;
+       const char *mode_str;
+       int copy_flags = FILEUTILS_DEREFERENCE | FILEUTILS_FORCE;
+       int opts;
+       int min_args = 1;
+       int ret = EXIT_SUCCESS;
+       int isdir = 0;
+#if ENABLE_SELINUX
+       security_context_t scontext;
+       bool use_default_selinux_context = 1;
+#endif
+       enum {
+               OPT_c             = 1 << 0,
+               OPT_v             = 1 << 1,
+               OPT_b             = 1 << 2,
+               OPT_MKDIR_LEADING = 1 << 3,
+               OPT_DIRECTORY     = 1 << 4,
+               OPT_PRESERVE_TIME = 1 << 5,
+               OPT_STRIP         = 1 << 6,
+               OPT_GROUP         = 1 << 7,
+               OPT_MODE          = 1 << 8,
+               OPT_OWNER         = 1 << 9,
+#if ENABLE_SELINUX
+               OPT_SET_SECURITY_CONTEXT = 1 << 10,
+               OPT_PRESERVE_SECURITY_CONTEXT = 1 << 11,
+#endif
+       };
+
+#if ENABLE_FEATURE_INSTALL_LONG_OPTIONS
+       applet_long_options = install_longopts;
+#endif
+       opt_complementary = "s--d:d--s" USE_SELINUX(":Z--\xff:\xff--Z");
+       /* -c exists for backwards compatibility, it's needed */
+       /* -v is ignored ("print name of each created directory") */
+       /* -b is ignored ("make a backup of each existing destination file") */
+       opts = getopt32(argv, "cvb" "Ddpsg:m:o:" USE_SELINUX("Z:"),
+                       &gid_str, &mode_str, &uid_str USE_SELINUX(, &scontext));
+       argc -= optind;
+       argv += optind;
+
+#if ENABLE_SELINUX
+       if (opts & (OPT_PRESERVE_SECURITY_CONTEXT|OPT_SET_SECURITY_CONTEXT)) {
+               selinux_or_die();
+               use_default_selinux_context = 0;
+               if (opts & OPT_PRESERVE_SECURITY_CONTEXT) {
+                       copy_flags |= FILEUTILS_PRESERVE_SECURITY_CONTEXT;
+               }
+               if (opts & OPT_SET_SECURITY_CONTEXT) {
+                       setfscreatecon_or_die(scontext);
+                       copy_flags |= FILEUTILS_SET_SECURITY_CONTEXT;
+               }
+       }
+#endif
+
+       /* preserve access and modification time, this is GNU behaviour,
+        * BSD only preserves modification time */
+       if (opts & OPT_PRESERVE_TIME) {
+               copy_flags |= FILEUTILS_PRESERVE_STATUS;
+       }
+       mode = 0666;
+       if (opts & OPT_MODE)
+               bb_parse_mode(mode_str, &mode);
+       uid = (opts & OPT_OWNER) ? get_ug_id(uid_str, xuname2uid) : getuid();
+       gid = (opts & OPT_GROUP) ? get_ug_id(gid_str, xgroup2gid) : getgid();
+
+       last = argv[argc - 1];
+       if (!(opts & OPT_DIRECTORY)) {
+               argv[argc - 1] = NULL;
+               min_args++;
+
+               /* coreutils install resolves link in this case, don't use lstat */
+               isdir = stat(last, &statbuf) < 0 ? 0 : S_ISDIR(statbuf.st_mode);
+       }
+
+       if (argc < min_args)
+               bb_show_usage();
+
+       while ((arg = *argv++) != NULL) {
+               char *dest = last;
+               if (opts & OPT_DIRECTORY) {
+                       dest = arg;
+                       /* GNU coreutils 6.9 does not set uid:gid
+                        * on intermediate created directories
+                        * (only on last one) */
+                       if (bb_make_directory(dest, 0755, FILEUTILS_RECUR)) {
+                               ret = EXIT_FAILURE;
+                               goto next;
+                       }
+               } else {
+                       if (opts & OPT_MKDIR_LEADING) {
+                               char *ddir = xstrdup(dest);
+                               bb_make_directory(dirname(ddir), 0755, FILEUTILS_RECUR);
+                               /* errors are not checked. copy_file
+                                * will fail if dir is not created. */
+                               free(ddir);
+                       }
+                       if (isdir)
+                               dest = concat_path_file(last, basename(arg));
+                       if (copy_file(arg, dest, copy_flags)) {
+                               /* copy is not made */
+                               ret = EXIT_FAILURE;
+                               goto next;
+                       }
+               }
+
+               /* Set the file mode */
+               if ((opts & OPT_MODE) && chmod(dest, mode) == -1) {
+                       bb_perror_msg("can't change %s of %s", "permissions", dest);
+                       ret = EXIT_FAILURE;
+               }
+#if ENABLE_SELINUX
+               if (use_default_selinux_context)
+                       setdefaultfilecon(dest);
+#endif
+               /* Set the user and group id */
+               if ((opts & (OPT_OWNER|OPT_GROUP))
+                && lchown(dest, uid, gid) == -1
+               ) {
+                       bb_perror_msg("can't change %s of %s", "ownership", dest);
+                       ret = EXIT_FAILURE;
+               }
+               if (opts & OPT_STRIP) {
+                       char *args[3];
+                       args[0] = (char*)"strip";
+                       args[1] = dest;
+                       args[2] = NULL;
+                       if (spawn_and_wait(args)) {
+                               bb_perror_msg("strip");
+                               ret = EXIT_FAILURE;
+                       }
+               }
+ next:
+               if (ENABLE_FEATURE_CLEAN_UP && isdir)
+                       free(dest);
+       }
+
+       return ret;
+}
diff --git a/coreutils/length.c b/coreutils/length.c
new file mode 100644 (file)
index 0000000..43a0f59
--- /dev/null
@@ -0,0 +1,22 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Licensed under GPLv2, see file LICENSE in this tarball for details.
+ */
+
+/* BB_AUDIT SUSv3 N/A -- Apparently a busybox (obsolete?) extension. */
+
+#include "libbb.h"
+
+/* This is a NOFORK applet. Be very careful! */
+
+int length_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int length_main(int argc, char **argv)
+{
+       if ((argc != 2) || (**(++argv) == '-')) {
+               bb_show_usage();
+       }
+
+       printf("%u\n", (unsigned)strlen(*argv));
+
+       return fflush(stdout);
+}
diff --git a/coreutils/libcoreutils/Kbuild b/coreutils/libcoreutils/Kbuild
new file mode 100644 (file)
index 0000000..755d01f
--- /dev/null
@@ -0,0 +1,12 @@
+# Makefile for busybox
+#
+# Copyright (C) 1999-2004 by Erik Andersen <andersen@codepoet.org>
+#
+# 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 (file)
index 0000000..99b67b1
--- /dev/null
@@ -0,0 +1,20 @@
+/* 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
+
+PUSH_AND_SET_FUNCTION_VISIBILITY_TO_HIDDEN
+
+typedef int (*stat_func)(const char *fn, struct stat *ps);
+
+int cp_mv_stat2(const char *fn, struct stat *fn_stat, stat_func sf) FAST_FUNC;
+int cp_mv_stat(const char *fn, struct stat *fn_stat) FAST_FUNC;
+
+mode_t getopt_mk_fifo_nod(char **argv) FAST_FUNC;
+
+POP_SAVED_FUNCTION_VISIBILITY
+
+#endif
diff --git a/coreutils/libcoreutils/cp_mv_stat.c b/coreutils/libcoreutils/cp_mv_stat.c
new file mode 100644 (file)
index 0000000..0fd467c
--- /dev/null
@@ -0,0 +1,50 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * coreutils utility routine
+ *
+ * Copyright (C) 2003  Manuel Novoa III  <mjn3@codepoet.org>
+ *
+ * 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 FAST_FUNC cp_mv_stat2(const char *fn, struct stat *fn_stat, stat_func sf)
+{
+       if (sf(fn, fn_stat) < 0) {
+               if (errno != ENOENT) {
+#if ENABLE_FEATURE_VERBOSE_CP_MESSAGE
+                       if (errno == ENOTDIR) {
+                               bb_error_msg("cannot stat '%s': Path has non-directory component", fn);
+                               return -1;
+                       }
+#endif
+                       bb_perror_msg("cannot stat '%s'", fn);
+                       return -1;
+               }
+               return 0;
+       }
+       if (S_ISDIR(fn_stat->st_mode)) {
+               return 3;
+       }
+       return 1;
+}
+
+int FAST_FUNC 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 (file)
index 0000000..ba3222e
--- /dev/null
@@ -0,0 +1,48 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * coreutils utility routine
+ *
+ * Copyright (C) 2003  Manuel Novoa III  <mjn3@codepoet.org>
+ *
+ * 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"
+
+mode_t FAST_FUNC getopt_mk_fifo_nod(char **argv)
+{
+       mode_t mode = 0666;
+       char *smode = NULL;
+#if ENABLE_SELINUX
+       security_context_t scontext;
+#endif
+       int opt;
+       opt = getopt32(argv, "m:" USE_SELINUX("Z:"), &smode USE_SELINUX(,&scontext));
+       if (opt & 1) {
+               if (bb_parse_mode(smode, &mode))
+                       umask(0);
+       }
+
+#if ENABLE_SELINUX
+       if (opt & 2) {
+               selinux_or_die();
+               setfscreatecon_or_die(scontext);
+       }
+#endif
+
+       return mode;
+}
diff --git a/coreutils/ln.c b/coreutils/ln.c
new file mode 100644 (file)
index 0000000..eb71719
--- /dev/null
@@ -0,0 +1,109 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Mini ln 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.
+ */
+
+/* BB_AUDIT SUSv3 compliant */
+/* BB_AUDIT GNU options missing: -d, -F, -i, and -v. */
+/* http://www.opengroup.org/onlinepubs/007904975/utilities/ln.html */
+
+#include "libbb.h"
+
+/* This is a NOEXEC applet. Be very careful! */
+
+
+#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) MAIN_EXTERNALLY_VISIBLE;
+int ln_main(int argc, char **argv)
+{
+       int status = EXIT_SUCCESS;
+       int flag;
+       char *last;
+       char *src_name;
+       char *src;
+       char *suffix = (char*)"~";
+       struct stat statbuf;
+       int (*link_func)(const char *, const char *);
+
+       flag = getopt32(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_strip(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_strip(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_simple_perror_msg(*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_simple_perror_msg(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_simple_perror_msg(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 (file)
index 0000000..3400c30
--- /dev/null
@@ -0,0 +1,43 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Mini logname implementation for busybox
+ *
+ * Copyright (C) 2000  Edward Betts <edward@debian.org>.
+ *
+ * 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 "libbb.h"
+
+/* This is a NOFORK applet. Be very careful! */
+
+int logname_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int logname_main(int argc, char **argv UNUSED_PARAM)
+{
+       char buf[128];
+
+       if (argc > 1) {
+               bb_show_usage();
+       }
+
+       /* Using _r function - avoid pulling in static buffer from libc */
+       if (getlogin_r(buf, sizeof(buf)) == 0) {
+               puts(buf);
+               return fflush(stdout);
+       }
+
+       bb_perror_msg_and_die("getlogin");
+}
diff --git a/coreutils/ls.c b/coreutils/ls.c
new file mode 100644 (file)
index 0000000..38957e9
--- /dev/null
@@ -0,0 +1,1077 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * tiny-ls.c version 0.1.0: A minimalist 'ls'
+ * Copyright (C) 1996 Brian Candler <B.Candler@pobox.com>
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+/* [date unknown. Perhaps before year 2000]
+ * 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 <blocks>" header
+ * 2. 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?
+ *
+ * [2009-03]
+ * ls sorts listing now, and supports almost all options.
+ */
+
+#include "libbb.h"
+
+#if ENABLE_FEATURE_ASSUME_UNICODE
+#include <wchar.h>
+#endif
+
+/* This is a NOEXEC applet. Be very careful! */
+
+
+#if ENABLE_FTPD
+/* ftpd uses ls, and without timestamps Mozilla won't understand
+ * ftpd's LIST output.
+ */
+# undef CONFIG_FEATURE_LS_TIMESTAMPS
+# undef ENABLE_FEATURE_LS_TIMESTAMPS
+# undef USE_FEATURE_LS_TIMESTAMPS
+# undef SKIP_FEATURE_LS_TIMESTAMPS
+# define CONFIG_FEATURE_LS_TIMESTAMPS 1
+# define ENABLE_FEATURE_LS_TIMESTAMPS 1
+# define USE_FEATURE_LS_TIMESTAMPS(...) __VA_ARGS__
+# define SKIP_FEATURE_LS_TIMESTAMPS(...)
+#endif
+
+
+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, - unused, synonym to LIST_SIZE
+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,
+
+};
+
+/* "[-]Cadil1", POSIX mandated options, busybox always supports */
+/* "[-]gnsx", POSIX non-mandated options, busybox always supports */
+/* "[-]Q" GNU option? 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[] ALIGN1 =
+       "Cadil1gnsxQAk" /* 13 opts, total 13 */
+       USE_FEATURE_LS_TIMESTAMPS("cetu") /* 4, 17 */
+       USE_FEATURE_LS_SORTFILES("SXrv")  /* 4, 21 */
+       USE_FEATURE_LS_FILETYPES("Fp")    /* 2, 23 */
+       USE_FEATURE_LS_FOLLOWLINKS("L")   /* 1, 24 */
+       USE_FEATURE_LS_RECURSIVE("R")     /* 1, 25 */
+       USE_FEATURE_HUMAN_READABLE("h")   /* 1, 26 */
+       USE_SELINUX("KZ") /* 2, 28 */
+       USE_FEATURE_AUTOWIDTH("T:w:") /* 2, 30 */
+       ;
+enum {
+       //OPT_C = (1 << 0),
+       //OPT_a = (1 << 1),
+       //OPT_d = (1 << 2),
+       //OPT_i = (1 << 3),
+       //OPT_l = (1 << 4),
+       //OPT_1 = (1 << 5),
+       OPT_g = (1 << 6),
+       //OPT_n = (1 << 7),
+       //OPT_s = (1 << 8),
+       //OPT_x = (1 << 9),
+       OPT_Q = (1 << 10),
+       //OPT_A = (1 << 11),
+       //OPT_k = (1 << 12),
+       OPTBIT_color = 13
+               + 4 * ENABLE_FEATURE_LS_TIMESTAMPS
+               + 4 * ENABLE_FEATURE_LS_SORTFILES
+               + 2 * ENABLE_FEATURE_LS_FILETYPES
+               + 1 * ENABLE_FEATURE_LS_FOLLOWLINKS
+               + 1 * ENABLE_FEATURE_LS_RECURSIVE
+               + 1 * ENABLE_FEATURE_HUMAN_READABLE
+               + 2 * ENABLE_SELINUX
+               + 2 * ENABLE_FEATURE_AUTOWIDTH,
+       OPT_color = 1 << OPTBIT_color,
+};
+
+enum {
+       LIST_MASK_TRIGGER       = 0,
+       STYLE_MASK_TRIGGER      = STYLE_MASK,
+       DISP_MASK_TRIGGER       = DISP_ROWS,
+       SORT_MASK_TRIGGER       = SORT_MASK,
+};
+
+/* TODO: simple toggles may be stored as OPT_xxx bits instead */
+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 (don't show group) - handled via OPT_g */
+       LIST_ID_NUMERIC,            /* n */
+       LIST_BLOCKS,                /* s */
+       DISP_ROWS,                  /* x */
+       0,                          /* Q (quote filename) - handled via OPT_Q */
+       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_SELINUX
+       LIST_MODEBITS|LIST_ID_NAME|LIST_CONTEXT, /* Z */
+#endif
+       (1U<<31)
+       /* options after Z are not processed through opt_flags:
+        * T, w - ignored
+        */
+};
+
+
+/*
+ * a directory entry and its stat info are stored here
+ */
+struct dnode {                  /* the basic node */
+       const char *name;             /* the dir entry name */
+       const 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 */
+};
+
+static struct dnode **list_dir(const char *);
+static struct dnode **dnalloc(int);
+static int list_single(const struct dnode *);
+
+
+struct globals {
+#if ENABLE_FEATURE_LS_COLOR
+       smallint show_color;
+#endif
+       smallint exit_code;
+       unsigned all_fmt;
+#if ENABLE_FEATURE_AUTOWIDTH
+       unsigned tabstops; // = COLUMN_GAP;
+       unsigned terminal_width; // = TERMINAL_WIDTH;
+#endif
+#if ENABLE_FEATURE_LS_TIMESTAMPS
+       /* Do time() just once. Saves one syscall per file for "ls -l" */
+       time_t current_time_t;
+#endif
+};
+#define G (*(struct globals*)&bb_common_bufsiz1)
+#if ENABLE_FEATURE_LS_COLOR
+#define show_color     (G.show_color    )
+#else
+enum { show_color = 0 };
+#endif
+#define exit_code      (G.exit_code     )
+#define all_fmt        (G.all_fmt       )
+#if ENABLE_FEATURE_AUTOWIDTH
+#define tabstops       (G.tabstops      )
+#define terminal_width (G.terminal_width)
+#else
+enum {
+       tabstops = COLUMN_GAP,
+       terminal_width = TERMINAL_WIDTH,
+};
+#endif
+#define current_time_t (G.current_time_t)
+/* memset: we have to zero it out because of NOEXEC */
+#define INIT_G() do { \
+       memset(&G, 0, sizeof(G)); \
+       USE_FEATURE_AUTOWIDTH(tabstops = COLUMN_GAP;) \
+       USE_FEATURE_AUTOWIDTH(terminal_width = TERMINAL_WIDTH;) \
+       USE_FEATURE_LS_TIMESTAMPS(time(&current_time_t);) \
+} while (0)
+
+
+#if ENABLE_FEATURE_ASSUME_UNICODE
+/* libbb candidate */
+static size_t mbstrlen(const char *string)
+{
+       size_t width = mbsrtowcs(NULL /*dest*/, &string,
+                               MAXINT(size_t) /*len*/, NULL /*state*/);
+       if (width == (size_t)-1)
+               return strlen(string);
+       return width;
+}
+#else
+#define mbstrlen(string) strlen(string)
+#endif
+
+
+static struct dnode *my_stat(const char *fullname, const char *name, int force_follow)
+{
+       struct stat dstat;
+       struct dnode *cur;
+       USE_SELINUX(security_context_t sid = NULL;)
+
+       if ((all_fmt & FOLLOW_LINKS) || force_follow) {
+#if ENABLE_SELINUX
+               if (is_selinux_enabled())  {
+                        getfilecon(fullname, &sid);
+               }
+#endif
+               if (stat(fullname, &dstat)) {
+                       bb_simple_perror_msg(fullname);
+                       exit_code = EXIT_FAILURE;
+                       return 0;
+               }
+       } else {
+#if ENABLE_SELINUX
+               if (is_selinux_enabled()) {
+                       lgetfilecon(fullname, &sid);
+               }
+#endif
+               if (lstat(fullname, &dstat)) {
+                       bb_simple_perror_msg(fullname);
+                       exit_code = 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;
+}
+
+
+/* FYI type values: 1:fifo 2:char 4:dir 6:blk 8:file 10:link 12:socket
+ * (various wacky OSes: 13:Sun door 14:BSD whiteout 5:XENIX named file
+ *  3/7:multiplexed char/block device)
+ * and we use 0 for unknown and 15 for executables (see below) */
+#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)])
+/* 036 black foreground              050 black background
+   037 red foreground                051 red background
+   040 green foreground              052 green background
+   041 brown foreground              053 brown background
+   042 blue foreground               054 blue background
+   043 magenta (purple) foreground   055 magenta background
+   044 cyan (light blue) foreground  056 cyan background
+   045 gray foreground               057 white background
+*/
+#define COLOR(mode) ( \
+       /*un  fi  chr     dir     blk     file    link    sock        exe */ \
+       "\037\043\043\045\042\045\043\043\000\045\044\045\043\045\045\040" \
+       [TYPEINDEX(mode)])
+/* Select normal (0) [actually "reset all"] or bold (1)
+ * (other attributes are 2:dim 4:underline 5:blink 7:reverse,
+ *  let's use 7 for "impossible" types, just for fun)
+ * Note: coreutils 6.9 uses inverted red for setuid binaries.
+ */
+#define ATTR(mode) ( \
+       /*un fi chr   dir   blk   file  link  sock     exe */ \
+       "\01\00\01\07\01\07\01\07\00\07\01\07\01\07\07\01" \
+       [TYPEINDEX(mode)])
+
+#if ENABLE_FEATURE_LS_COLOR
+/* mode of zero is interpreted as "unknown" (stat failed) */
+static char fgcolor(mode_t mode)
+{
+       if (S_ISREG(mode) && (mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
+               return COLOR(0xF000);   /* File is executable ... */
+       return COLOR(mode);
+}
+static char bold(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++) {
+               const 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((char*)cur->fullname);     /* free the filename */
+               free(cur);              /* free the dnode */
+       }
+       free(dnp);                      /* free the array holding the dnode pointers */
+}
+#else
+#define dfree(...) ((void)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)) {
+                       const 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) ((void)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 = mbstrlen(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)
+                               bb_putchar('\n');
+                       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) {
+               exit_code = 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] || (entry->d_name[1] == '.' && !entry->d_name[2]))
+                        && !(all_fmt & DISP_DOT)
+                       ) {
+                               continue;
+                       }
+                       if (!(all_fmt & DISP_HIDDEN))
+                               continue;
+               }
+               fullname = concat_path_file(path, entry->d_name);
+               cur = my_stat(fullname, bb_basename(fullname), 0);
+               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 print_name(const char *name)
+{
+       if (option_mask32 & OPT_Q) {
+#if ENABLE_FEATURE_ASSUME_UNICODE
+               int len = 2 + mbstrlen(name);
+#else
+               int len = 2;
+#endif
+               putchar('"');
+               while (*name) {
+                       if (*name == '"') {
+                               putchar('\\');
+                               len++;
+                       }
+                       putchar(*name++);
+                       if (!ENABLE_FEATURE_ASSUME_UNICODE)
+                               len++;
+               }
+               putchar('"');
+               return len;
+       }
+       /* No -Q: */
+#if ENABLE_FEATURE_ASSUME_UNICODE
+       fputs(name, stdout);
+       return mbstrlen(name);
+#else
+       return printf("%s", name);
+#endif
+}
+
+
+static int list_single(const struct dnode *dn)
+{
+       int column = 0;
+       char *lpath = lpath; /* for compiler */
+#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
+
+       /* Do readlink early, so that if it fails, error message
+        * does not appear *inside* of the "ls -l" line */
+       if (all_fmt & LIST_SYMLINK)
+               if (S_ISLNK(dn->dstat.st_mode))
+                       lpath = xmalloc_readlink_or_warn(dn->fullname);
+
+       if (all_fmt & LIST_INO)
+               column += printf("%7lu ", (long) dn->dstat.st_ino);
+       if (all_fmt & LIST_BLOCKS)
+               column += printf("%4"OFF_FMT"u ", (off_t) dn->dstat.st_blocks >> 1);
+       if (all_fmt & LIST_MODEBITS)
+               column += printf("%-10s ", (char *) bb_mode_string(dn->dstat.st_mode));
+       if (all_fmt & LIST_NLINKS)
+               column += printf("%4lu ", (long) dn->dstat.st_nlink);
+#if ENABLE_FEATURE_LS_USERNAME
+       if (all_fmt & LIST_ID_NAME) {
+               if (option_mask32 & OPT_g) {
+                       column += printf("%-8.8s",
+                               get_cached_username(dn->dstat.st_uid));
+               } else {
+                       column += printf("%-8.8s %-8.8s",
+                               get_cached_username(dn->dstat.st_uid),
+                               get_cached_groupname(dn->dstat.st_gid));
+               }
+       }
+#endif
+       if (all_fmt & LIST_ID_NUMERIC) {
+               if (option_mask32 & OPT_g)
+                       column += printf("%-8u", (int) dn->dstat.st_uid);
+               else
+                       column += printf("%-8u %-8u",
+                                       (int) dn->dstat.st_uid,
+                                       (int) dn->dstat.st_gid);
+       }
+       if (all_fmt & (LIST_SIZE /*|LIST_DEV*/ )) {
+               if (S_ISBLK(dn->dstat.st_mode) || S_ISCHR(dn->dstat.st_mode)) {
+                       column += printf("%4u, %3u ",
+                                       (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"u ", (off_t) dn->dstat.st_size);
+                       }
+               }
+       }
+#if ENABLE_FEATURE_LS_TIMESTAMPS
+       if (all_fmt & LIST_FULLTIME)
+               column += printf("%24.24s ", filetime);
+       if (all_fmt & LIST_DATE_TIME)
+               if ((all_fmt & LIST_FULLTIME) == 0) {
+                       /* current_time_t ~== time(NULL) */
+                       age = current_time_t - 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;
+               }
+#endif
+#if ENABLE_SELINUX
+       if (all_fmt & LIST_CONTEXT) {
+               column += printf("%-32s ", dn->sid ? dn->sid : "unknown");
+               freecon(dn->sid);
+       }
+#endif
+       if (all_fmt & LIST_FILENAME) {
+#if ENABLE_FEATURE_LS_COLOR
+               if (show_color) {
+                       info.st_mode = 0; /* for fgcolor() */
+                       lstat(dn->fullname, &info);
+                       printf("\033[%u;%um", bold(info.st_mode),
+                                       fgcolor(info.st_mode));
+               }
+#endif
+               column += print_name(dn->name);
+               if (show_color) {
+                       printf("\033[0m");
+               }
+       }
+       if (all_fmt & LIST_SYMLINK) {
+               if (S_ISLNK(dn->dstat.st_mode) && lpath) {
+                       printf(" -> ");
+#if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR
+#if ENABLE_FEATURE_LS_COLOR
+                       info.st_mode = 0; /* for fgcolor() */
+#endif
+                       if (stat(dn->fullname, &info) == 0) {
+                               append = append_char(info.st_mode);
+                       }
+#endif
+#if ENABLE_FEATURE_LS_COLOR
+                       if (show_color) {
+                               printf("\033[%u;%um", bold(info.st_mode),
+                                          fgcolor(info.st_mode));
+                       }
+#endif
+                       column += print_name(lpath) + 4;
+                       if (show_color) {
+                               printf("\033[0m");
+                       }
+                       free(lpath);
+               }
+       }
+#if ENABLE_FEATURE_LS_FILETYPES
+       if (all_fmt & LIST_FILETYPE) {
+               if (append) {
+                       putchar(append);
+                       column++;
+               }
+       }
+#endif
+
+       return column;
+}
+
+
+int ls_main(int argc UNUSED_PARAM, char **argv)
+{
+       struct dnode **dnd;
+       struct dnode **dnf;
+       struct dnode **dnp;
+       struct dnode *dn;
+       struct dnode *cur;
+       unsigned opt;
+       int nfiles;
+       int dnfiles;
+       int dndirs;
+       int i;
+#if ENABLE_FEATURE_LS_COLOR
+       /* colored LS support by JaWi, janwillem.janssen@lxtreme.nl */
+       /* coreutils 6.10:
+        * # ls --color=BOGUS
+        * ls: invalid argument 'BOGUS' for '--color'
+        * Valid arguments are:
+        * 'always', 'yes', 'force'
+        * 'never', 'no', 'none'
+        * 'auto', 'tty', 'if-tty'
+        * (and substrings: "--color=alwa" work too)
+        */
+       static const char ls_longopts[] ALIGN1 =
+               "color\0" Optional_argument "\xff"; /* no short equivalent */
+       static const char color_str[] ALIGN1 =
+               "always\0""yes\0""force\0"
+               "auto\0""tty\0""if-tty\0";
+       /* need to initialize since --color has _an optional_ argument */
+       const char *color_opt = color_str; /* "always" */
+#endif
+
+       INIT_G();
+
+       all_fmt = LIST_SHORT |
+               (ENABLE_FEATURE_LS_SORTFILES * (SORT_NAME | SORT_FORWARD));
+
+#if ENABLE_FEATURE_AUTOWIDTH
+       /* Obtain the terminal width */
+       get_terminal_width_height(STDIN_FILENO, &terminal_width, NULL);
+       /* Go one less... */
+       terminal_width--;
+#endif
+
+       /* process options */
+       USE_FEATURE_LS_COLOR(applet_long_options = ls_longopts;)
+#if ENABLE_FEATURE_AUTOWIDTH
+       opt_complementary = "T+:w+"; /* -T N, -w N */
+       opt = getopt32(argv, ls_options, &tabstops, &terminal_width
+                               USE_FEATURE_LS_COLOR(, &color_opt));
+#else
+       opt = getopt32(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;
+                       /* huh?? opt cannot be 'l' */
+                       //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") != 0))
+                       show_color = 1;
+       }
+       if (opt & OPT_color) {
+               if (color_opt[0] == 'n')
+                       show_color = 0;
+               else switch (index_in_substrings(color_str, color_opt)) {
+               case 3:
+               case 4:
+               case 5:
+                       if (isatty(STDOUT_FILENO)) {
+               case 0:
+               case 1:
+               case 2:
+                               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);
+
+       argv += optind;
+       if (!argv[0])
+               *--argv = (char*)".";
+
+       if (argv[1])
+               all_fmt |= DISP_DIRNAME; /* 2 or more items? label directories */
+
+       /* stuff the command line file names into a dnode array */
+       dn = NULL;
+       nfiles = 0;
+       do {
+               /* NB: follow links on command line unless -l or -s */
+               cur = my_stat(*argv, *argv, !(all_fmt & (STYLE_LONG|LIST_BLOCKS)));
+               argv++;
+               if (!cur)
+                       continue;
+               cur->allocated = 0;
+               cur->next = dn;
+               dn = cur;
+               nfiles++;
+       } while (*argv);
+
+       /* 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 exit_code;
+}
diff --git a/coreutils/md5_sha1_sum.c b/coreutils/md5_sha1_sum.c
new file mode 100644 (file)
index 0000000..a988b9c
--- /dev/null
@@ -0,0 +1,192 @@
+/* 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 "libbb.h"
+
+typedef enum {
+       /* 4th letter of applet_name is... */
+       HASH_MD5 = 's', /* "md5>s<um" */
+       HASH_SHA1 = '1',
+       HASH_SHA256 = '2',
+       HASH_SHA512 = '5',
+} 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)
+{
+       /* xzalloc zero-terminates */
+       char *hex_value = xzalloc((hash_length * 2) + 1);
+       bin2hex(hex_value, (char*)hash_value, hash_length);
+       return (unsigned char *)hex_value;
+}
+
+static uint8_t *hash_file(const char *filename /*, hash_algo_t hash_algo*/)
+{
+       int src_fd, hash_len, count;
+       union _ctx_ {
+               sha512_ctx_t sha512;
+               sha256_ctx_t sha256;
+               sha1_ctx_t sha1;
+               md5_ctx_t md5;
+       } context;
+       uint8_t *hash_value = NULL;
+       RESERVE_CONFIG_UBUFFER(in_buf, 4096);
+       void FAST_FUNC (*update)(const void*, size_t, void*);
+       void FAST_FUNC (*final)(void*, void*);
+       hash_algo_t hash_algo = applet_name[3];
+
+       src_fd = open_or_warn_stdin(filename);
+       if (src_fd < 0) {
+               return NULL;
+       }
+
+       /* figure specific hash algorithims */
+       if (ENABLE_MD5SUM && hash_algo == HASH_MD5) {
+               md5_begin(&context.md5);
+               update = (void*)md5_hash;
+               final = (void*)md5_end;
+               hash_len = 16;
+       } else if (ENABLE_SHA1SUM && hash_algo == HASH_SHA1) {
+               sha1_begin(&context.sha1);
+               update = (void*)sha1_hash;
+               final = (void*)sha1_end;
+               hash_len = 20;
+       } else if (ENABLE_SHA256SUM && hash_algo == HASH_SHA256) {
+               sha256_begin(&context.sha256);
+               update = (void*)sha256_hash;
+               final = (void*)sha256_end;
+               hash_len = 32;
+       } else if (ENABLE_SHA512SUM && hash_algo == HASH_SHA512) {
+               sha512_begin(&context.sha512);
+               update = (void*)sha512_hash;
+               final = (void*)sha512_end;
+               hash_len = 64;
+       } 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) MAIN_EXTERNALLY_VISIBLE;
+int md5_sha1_sum_main(int argc UNUSED_PARAM, char **argv)
+{
+       int return_value = EXIT_SUCCESS;
+       uint8_t *hash_value;
+       unsigned flags;
+       /*hash_algo_t hash_algo = applet_name[3];*/
+
+       if (ENABLE_FEATURE_MD5_SHA1_SUM_CHECK)
+               flags = getopt32(argv, "scw");
+       else optind = 1;
+       argv += optind;
+       //argc -= optind;
+       if (!*argv)
+               *--argv = (char*)"-";
+
+       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 (ENABLE_FEATURE_MD5_SHA1_SUM_CHECK && (flags & FLAG_CHECK)) {
+               FILE *pre_computed_stream;
+               int count_total = 0;
+               int count_failed = 0;
+               char *line;
+
+               if (argv[1]) {
+                       bb_error_msg_and_die
+                               ("only one argument may be specified when using -c");
+               }
+
+               pre_computed_stream = xfopen_stdin(argv[0]);
+
+               while ((line = xmalloc_fgetline(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 {
+               do {
+                       hash_value = hash_file(*argv/*, hash_algo*/);
+                       if (hash_value == NULL) {
+                               return_value = EXIT_FAILURE;
+                       } else {
+                               printf("%s  %s\n", hash_value, *argv);
+                               free(hash_value);
+                       }
+               } while (*++argv);
+       }
+       return return_value;
+}
diff --git a/coreutils/mkdir.c b/coreutils/mkdir.c
new file mode 100644 (file)
index 0000000..72bd105
--- /dev/null
@@ -0,0 +1,80 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Mini mkdir implementation for busybox
+ *
+ * Copyright (C) 2001 Matt Kraai <kraai@alumni.carnegiemellon.edu>
+ *
+ * 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.
+ */
+
+/* Nov 28, 2006      Yoshinori Sato <ysato@users.sourceforge.jp>: Add SELinux Support.
+ */
+
+#include "libbb.h"
+
+/* This is a NOFORK applet. Be very careful! */
+
+#if ENABLE_FEATURE_MKDIR_LONG_OPTIONS
+static const char mkdir_longopts[] ALIGN1 =
+       "mode\0"    Required_argument "m"
+       "parents\0" No_argument       "p"
+#if ENABLE_SELINUX
+       "context\0" Required_argument "Z"
+#endif
+       ;
+#endif
+
+int mkdir_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+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_SELINUX
+       security_context_t scontext;
+#endif
+
+#if ENABLE_FEATURE_MKDIR_LONG_OPTIONS
+       applet_long_options = mkdir_longopts;
+#endif
+       opt = getopt32(argv, "m:p" USE_SELINUX("Z:"), &smode USE_SELINUX(,&scontext));
+       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 ENABLE_SELINUX
+       if (opt & 4) {
+               selinux_or_die();
+               setfscreatecon_or_die(scontext);
+       }
+#endif
+
+       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 (file)
index 0000000..6549460
--- /dev/null
@@ -0,0 +1,37 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * mkfifo implementation for busybox
+ *
+ * Copyright (C) 2003  Manuel Novoa III  <mjn3@codepoet.org>
+ *
+ * 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 "libbb.h"
+#include "libcoreutils/coreutils.h"
+
+int mkfifo_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int mkfifo_main(int argc UNUSED_PARAM, char **argv)
+{
+       mode_t mode;
+       int retval = EXIT_SUCCESS;
+
+       mode = getopt_mk_fifo_nod(argv);
+
+       argv += optind;
+       if (!*argv) {
+               bb_show_usage();
+       }
+
+       do {
+               if (mkfifo(*argv, mode) < 0) {
+                       bb_simple_perror_msg(*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 (file)
index 0000000..0c69494
--- /dev/null
@@ -0,0 +1,57 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * mknod implementation for busybox
+ *
+ * Copyright (C) 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 -- Matches GNU behavior. */
+
+#include <sys/sysmacros.h>  // For makedev
+
+#include "libbb.h"
+#include "libcoreutils/coreutils.h"
+
+static const char modes_chars[] ALIGN1 = { '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) MAIN_EXTERNALLY_VISIBLE;
+int mknod_main(int argc, char **argv)
+{
+       mode_t mode;
+       dev_t dev;
+       const char *name;
+
+       mode = getopt_mk_fifo_nod(argv);
+       argv += optind;
+       argc -= optind;
+
+       if (argc >= 2) {
+               name = strchr(modes_chars, argv[1][0]);
+               if (name != NULL) {
+                       mode |= modes_cubp[(int)(name[4])];
+
+                       dev = 0;
+                       if (*name != 'p') {
+                               argc -= 2;
+                               if (argc == 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_simple_perror_msg_and_die(name);
+                       }
+               }
+       }
+       bb_show_usage();
+}
diff --git a/coreutils/mv.c b/coreutils/mv.c
new file mode 100644 (file)
index 0000000..be10b03
--- /dev/null
@@ -0,0 +1,135 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Mini mv implementation for busybox
+ *
+ * Copyright (C) 2000 by Matt Kraai <kraai@alumni.carnegiemellon.edu>
+ * SELinux support by Yuichi Nakamura <ynakam@hitachisoft.jp>
+ *
+ * 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 "libbb.h"
+#include "libcoreutils/coreutils.h"
+
+#if ENABLE_FEATURE_MV_LONG_OPTIONS
+static const char mv_longopts[] ALIGN1 =
+       "interactive\0" No_argument "i"
+       "force\0"       No_argument "f"
+       ;
+#endif
+
+#define OPT_FILEUTILS_FORCE       1
+#define OPT_FILEUTILS_INTERACTIVE 2
+
+int mv_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int mv_main(int argc, char **argv)
+{
+       struct stat dest_stat;
+       const char *last;
+       const char *dest;
+       unsigned flags;
+       int dest_exists;
+       int status = 0;
+       int copy_flag = 0;
+
+#if ENABLE_FEATURE_MV_LONG_OPTIONS
+       applet_long_options = mv_longopts;
+#endif
+       // Need at least two arguments
+       // -f unsets -i, -i unsets -f
+       opt_complementary = "-2:f-i:i-f";
+       flags = getopt32(argv, "fi");
+       argc -= optind;
+       argv += optind;
+       last = argv[argc - 1];
+
+       if (argc == 2) {
+               dest_exists = cp_mv_stat(last, &dest_stat);
+               if (dest_exists < 0) {
+                       return EXIT_FAILURE;
+               }
+
+               if (!(dest_exists & 2)) { /* last is not a directory */
+                       dest = last;
+                       goto DO_MOVE;
+               }
+       }
+
+       do {
+               dest = concat_path_file(last, bb_get_last_path_component_strip(*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_stat2(*argv, &source_stat, lstat)) < 1
+                       ) {
+                               bb_perror_msg("cannot rename '%s'", *argv);
+                       } else {
+                               static const char fmt[] ALIGN1 =
+                                       "cannot overwrite %sdirectory with %sdirectory";
+
+                               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;
+                                       }
+                               }
+                               /* FILEUTILS_RECUR also prevents nasties like
+                                * "read from device and write contents to dst"
+                                * instead of "create same device node" */
+                               copy_flag = FILEUTILS_RECUR | FILEUTILS_PRESERVE_STATUS;
+#if ENABLE_SELINUX
+                               copy_flag |= FILEUTILS_PRESERVE_SECURITY_CONTEXT;
+#endif
+                               if ((copy_file(*argv, dest, copy_flag) >= 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 (file)
index 0000000..d24a95b
--- /dev/null
@@ -0,0 +1,55 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * nice implementation for busybox
+ *
+ * Copyright (C) 2005  Manuel Novoa III  <mjn3@codepoet.org>
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+#include <sys/resource.h>
+#include "libbb.h"
+
+int nice_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+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);
+               }
+       }
+
+       BB_EXECVP(*argv, argv);         /* Now exec the desired program. */
+
+       /* The exec failed... */
+       xfunc_error_retval = (errno == ENOENT) ? 127 : 126; /* SUSv3 */
+       bb_simple_perror_msg_and_die(*argv);
+}
diff --git a/coreutils/nohup.c b/coreutils/nohup.c
new file mode 100644 (file)
index 0000000..f44e2af
--- /dev/null
@@ -0,0 +1,78 @@
+/* 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 <rob@landley.net>
+ * Copyright 2006 Bernhard Reutner-Fischer
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.h"
+
+/* Compat info: nohup (GNU coreutils 6.8) does this:
+# nohup true
+nohup: ignoring input and appending output to `nohup.out'
+# nohup true 1>/dev/null
+nohup: ignoring input and redirecting stderr to stdout
+# nohup true 2>zz
+# cat zz
+nohup: ignoring input and appending output to `nohup.out'
+# nohup true 2>zz 1>/dev/null
+# cat zz
+nohup: ignoring input
+# nohup true </dev/null 1>/dev/null
+nohup: redirecting stderr to stdout
+# nohup true </dev/null 2>zz 1>/dev/null
+# cat zz
+  (nothing)
+#
+*/
+
+int nohup_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int nohup_main(int argc, char **argv)
+{
+       const char *nohupout;
+       char *home;
+
+       xfunc_error_retval = 127;
+
+       if (argc < 2) bb_show_usage();
+
+       /* If stdin is a tty, detach from it. */
+       if (isatty(STDIN_FILENO)) {
+               /* bb_error_msg("ignoring input"); */
+               close(STDIN_FILENO);
+               xopen(bb_dev_null, O_RDONLY); /* will be fd 0 (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 {
+                               xopen(bb_dev_null, O_RDONLY); /* will be fd 1 */
+                       }
+               }
+               bb_error_msg("appending output to %s", nohupout);
+       }
+
+       /* If we have a tty on stderr, redirect to stdout. */
+       if (isatty(STDERR_FILENO)) {
+               /* if (stdout_wasnt_a_tty)
+                       bb_error_msg("redirecting stderr to stdout"); */
+               dup2(STDOUT_FILENO, STDERR_FILENO);
+       }
+
+       signal(SIGHUP, SIG_IGN);
+
+       BB_EXECVP(argv[1], argv+1);
+       bb_simple_perror_msg_and_die(argv[1]);
+}
diff --git a/coreutils/od.c b/coreutils/od.c
new file mode 100644 (file)
index 0000000..e4179a3
--- /dev/null
@@ -0,0 +1,225 @@
+/* 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.
+ */
+
+
+#include "libbb.h"
+#if ENABLE_DESKTOP
+/* This one provides -t (busybox's own build script needs it) */
+#include "od_bloaty.c"
+#else
+
+#include "dump.h"
+
+#define isdecdigit(c) isdigit(c)
+#define ishexdigit(c) (isxdigit)(c)
+
+static void
+odoffset(dumper_t *dumper, 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;
+       /*
+        * 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;
+       }
+
+       /* skip over the number */
+       if (base == 16)
+               for (num = p; ishexdigit(*p); ++p)
+                       continue;
+       else
+               for (num = p; isdecdigit(*p); ++p)
+                       continue;
+
+       /* check for no number */
+       if (num == p)
+               return;
+
+       /* if terminates with a '.', base is decimal */
+       if (*p == '.') {
+               if (base)
+                       return;
+               base = 10;
+       }
+
+       dumper->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)
+               dumper->dump_skip = 0;
+       else {
+               if (*p) {
+                       if (*p == 'b') {
+                               dumper->dump_skip *= 512;
+                               ++p;
+                       } else if (*p == 'B') {
+                               dumper->dump_skip *= 1024;
+                               ++p;
+                       }
+               }
+               if (*p)
+                       dumper->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:
+                                       dumper->fshead->nextfu->fmt[TYPE_OFFSET]
+                                               = dumper->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[] ALIGN1 = "aBbcDdeFfHhIiLlOoXxv";
+
+static const char od_o2si[] ALIGN1 = {
+       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) MAIN_EXTERNALLY_VISIBLE;
+int od_main(int argc, char **argv)
+{
+       int ch;
+       int first = 1;
+       char *p;
+       dumper_t *dumper = alloc_dumper();
+
+       while ((ch = getopt(argc, argv, od_opts)) > 0) {
+               if (ch == 'v') {
+                       dumper->dump_vflag = ALL;
+               } else if (((p = strchr(od_opts, ch)) != NULL) && (*p != '\0')) {
+                       if (first) {
+                               first = 0;
+                               bb_dump_add(dumper, "\"%07.7_Ao\n\"");
+                               bb_dump_add(dumper, "\"%07.7_ao  \"");
+                       } else {
+                               bb_dump_add(dumper, "\"         \"");
+                       }
+                       bb_dump_add(dumper, add_strings[(int)od_o2si[(p - od_opts)]]);
+               } else {        /* P, p, s, w, or other unhandled */
+                       bb_show_usage();
+               }
+       }
+       if (!dumper->fshead) {
+               bb_dump_add(dumper, "\"%07.7_Ao\n\"");
+               bb_dump_add(dumper, "\"%07.7_ao  \" 8/2 \"%06o \" \"\\n\"");
+       }
+
+       argc -= optind;
+       argv += optind;
+
+       odoffset(dumper, argc, &argv);
+
+       return bb_dump_dump(dumper, 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 (file)
index 0000000..eb45798
--- /dev/null
@@ -0,0 +1,1428 @@
+/* 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 Denys 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 "libbb.h"
+
+#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[] ALIGN1 =
+{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[] ALIGN1 =
+{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[] ALIGN1 =
+{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[] ALIGN1 =
+{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[] ALIGN1 = {
+       -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 ERR_width_bytes_has_bad_size {
+       char ERR_width_bytes_has_bad_size[ARRAY_SIZE(width_bytes) == N_SIZE_SPECS ? 1 : -1];
+};
+
+static smallint flag_dump_strings;
+/* Non-zero if an old-style 'pseudo-address' was specified.  */
+static smallint flag_pseudo_start;
+static smallint limit_bytes_to_format;
+/* When zero 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 smallint verbose;
+static smallint ioerror;
+
+static size_t string_min;
+
+/* An array of specs describing how to format each input block.  */
+static size_t n_specs;
+static struct tspec *spec;
+
+/* 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 difference between the old-style pseudo starting address and
+   the number of bytes to skip.  */
+static off_t pseudo_offset;
+/* When zero, MAX_BYTES_TO_FORMAT and END_OFFSET are ignored, and all
+   input is formatted.  */
+
+/* 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 unsigned bytes_per_block = 32; /* have to use unsigned, not size_t */
+
+/* A NULL-terminated list of the file-arguments from the command line.  */
+static const char *const *file_list;
+
+/* The input stream associated with the current file.  */
+static FILE *in_stream;
+
+#define MAX_INTEGRAL_TYPE_SIZE sizeof(ulonglong_t)
+static const unsigned char integral_type_size[MAX_INTEGRAL_TYPE_SIZE + 1] ALIGN1 = {
+       [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 const unsigned char fp_type_size[MAX_FP_TYPE_SIZE + 1] ALIGN1 = {
+       /* 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 through 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 UNUSED_PARAM)
+{
+       /* Names for some non-printing characters.  */
+       static const char charname[33][3] ALIGN1 = {
+               "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 UNUSED_PARAM)
+{
+       // 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) {
+               if (!*file_list)
+                       return;
+               in_stream = fopen_or_warn_stdin(*file_list++);
+               if (in_stream) {
+                       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", (in_stream == stdin)
+                                       ? bb_msg_standard_input
+                                       : file_list[-1]
+                       );
+                       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, return pointer to
+   character following the just-decoded format.
+   For example, if S were "d4afL", we will return a rtp 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 const char *
+decode_one_format(const char *s_orig, const char *s, 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;
+
+
+       switch (*s) {
+       case 'd':
+       case 'o':
+       case 'u':
+       case 'x': {
+               static const char CSIL[] ALIGN1 = "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[4] = {
+                               sizeof(char),
+                               sizeof(short),
+                               sizeof(int),
+                               sizeof(long),
+                       };
+                       size = CSIL_sizeof[p - CSIL];
+                       s++; /* skip C/S/I/L */
+               }
+
+#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[] ALIGN1 = "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[] ALIGN1 = "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++;
+
+       return s;
+}
+
+/* Decode the modern od format string S.  Append the decoded
+   representation to the global array SPEC, reallocating SPEC if
+   necessary.  */
+
+static void
+decode_format_string(const char *s)
+{
+       const char *s_orig = s;
+
+       while (*s != '\0') {
+               struct tspec tspec;
+               const char *next;
+
+               next = decode_one_format(s_orig, s, &tspec);
+
+               assert(s != next);
+               s = next;
+               spec = xrealloc_vector(spec, 4, n_specs);
+               memcpy(&spec[n_specs], &tspec, sizeof(spec[0]));
+               n_specs++;
+       }
+}
+
+/* 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 positive size,
+                          position the file pointer by reading.  */
+                       char buf[1024];
+                       size_t n_bytes_to_read = 1024;
+                       size_t n_bytes_read;
+
+                       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 UNUSED_PARAM, char c UNUSED_PARAM)
+{
+}
+
+static char address_fmt[] ALIGN1 = "%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, ')');
+       if (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 (!verbose && !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 },
+               { }
+       };
+       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.  */
+
+static void
+dump(off_t current_offset, off_t end_offset)
+{
+       char *block[2];
+       int idx;
+       size_t n_bytes_read;
+
+       block[0] = xmalloc(2*bytes_per_block);
+       block[1] = block[0] + bytes_per_block;
+
+       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.  */
+
+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.  */
+
+static void
+dump_strings(off_t address, off_t end_offset)
+{
+       size_t bufsize = MAX(100, string_min);
+       char *buf = xmalloc(bufsize);
+
+       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 NUL-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: putchar(c);
+                       }
+               }
+               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) MAIN_EXTERNALLY_VISIBLE;
+int od_main(int argc, char **argv)
+{
+       static const struct suffix_mult bkm[] = {
+               { "b", 512 },
+               { "k", 1024 },
+               { "m", 1024*1024 },
+               { }
+       };
+       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 char od_longopts[] ALIGN1 =
+               "skip-bytes\0"        Required_argument "j"
+               "address-radix\0"     Required_argument "A"
+               "read-bytes\0"        Required_argument "N"
+               "format\0"            Required_argument "t"
+               "output-duplicates\0" No_argument       "v"
+               "strings\0"           Optional_argument "S"
+               "width\0"             Optional_argument "w"
+               "traditional\0"       No_argument       "\xff"
+               ;
+#endif
+       char *str_A, *str_N, *str_j, *str_S;
+       llist_t *lst_t = NULL;
+       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 = pseudo_start; // for gcc
+       /* The number of input bytes to skip before formatting and writing.  */
+       off_t n_bytes_to_skip = 0;
+       /* The offset of the first byte after the last byte to be formatted.  */
+       off_t end_offset = 0;
+       /* The maximum number of bytes that will be formatted.  */
+       off_t max_bytes_to_format = 0;
+
+       spec = NULL;
+       format_address = format_address_std;
+       address_base_char = 'o';
+       address_pad_len_char = '7';
+       /* flag_dump_strings = 0; - already is */
+
+       /* Parse command line */
+       opt_complementary = "w+:t::"; /* -w N, -t is a list */
+#if ENABLE_GETOPT_LONG
+       applet_long_options = od_longopts;
+#endif
+       opt = getopt32(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, &bytes_per_block);
+       argc -= optind;
+       argv += optind;
+       if (opt & OPT_A) {
+               static const char doxn[] ALIGN1 = "doxn";
+               static const char doxn_address_base_char[] ALIGN1 = {
+                       'u', 'o', 'x', /* '?' fourth one is not important */
+               };
+               static const uint8_t doxn_address_pad_len_char[] ALIGN1 = {
+                       '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)...
+       while (lst_t) {
+               decode_format_string(llist_pop(&lst_t));
+       }
+       if (opt & OPT_v) verbose = 1;
+       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 = bb_argv_dash;
+       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 EXIT_FAILURE;
+
+       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 */
+               if (!bytes_per_block || bytes_per_block % l_c_m != 0) {
+                       bb_error_msg("warning: invalid width %u; using %d instead",
+                                       (unsigned)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(n_bytes_to_skip, end_offset);
+       else
+               dump(n_bytes_to_skip, end_offset);
+
+       if (fclose(stdin) == EOF)
+               bb_perror_msg_and_die(bb_msg_standard_input);
+
+       return ioerror;
+}
diff --git a/coreutils/printenv.c b/coreutils/printenv.c
new file mode 100644 (file)
index 0000000..2430f3a
--- /dev/null
@@ -0,0 +1,37 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * printenv implementation for busybox
+ *
+ * Copyright (C) 2005 by Erik Andersen <andersen@codepoet.org>
+ * Copyright (C) 2005 by Mike Frysinger <vapier@gentoo.org>
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.h"
+
+int printenv_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int printenv_main(int argc UNUSED_PARAM, char **argv)
+{
+       int exit_code = EXIT_SUCCESS;
+
+       /* no variables specified, show whole env */
+       if (!argv[1]) {
+               int e = 0;
+               while (environ[e])
+                       puts(environ[e++]);
+       } else {
+               /* search for specified variables and print them out if found */
+               char *arg, *env;
+
+               while ((arg = *++argv) != NULL) {
+                       env = getenv(arg);
+                       if (env)
+                               puts(env);
+                       else
+                               exit_code = EXIT_FAILURE;
+               }
+       }
+
+       fflush_stdout_and_exit(exit_code);
+}
diff --git a/coreutils/printf.c b/coreutils/printf.c
new file mode 100644 (file)
index 0000000..0b004ea
--- /dev/null
@@ -0,0 +1,398 @@
+/* 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 <djm@gnu.ai.mit.edu>
+*/
+
+//   19990508 Busy Boxed! Dave Cinege
+
+#include "libbb.h"
+
+/* A note on bad input: neither bash 3.2 nor coreutils 6.10 stop on it.
+ * They report it:
+ *  bash: printf: XXX: invalid number
+ *  printf: XXX: expected a numeric value
+ *  bash: printf: 123XXX: invalid number
+ *  printf: 123XXX: value not completely converted
+ * but then they use 0 (or partially converted numeric prefix) as a value
+ * and continue. They exit with 1 in this case.
+ * Both accept insane field width/precision (e.g. %9999999999.9999999999d).
+ * Both print error message and assume 0 if %*.*f width/precision is "bad"
+ *  (but negative numbers are not "bad").
+ * Both accept negative numbers for %u specifier.
+ *
+ * We try to be compatible. We are not compatible here:
+ * - we do not accept -NUM for %u
+ * - exit code is 0 even if "invalid number" was seen (FIXME)
+ * See "if (errno)" checks in the code below.
+ */
+
+typedef void FAST_FUNC (*converter)(const char *arg, void *result);
+
+static int multiconvert(const char *arg, void *result, converter convert)
+{
+       if (*arg == '"' || *arg == '\'') {
+               arg = utoa((unsigned char)arg[1]);
+       }
+       errno = 0;
+       convert(arg, result);
+       if (errno) {
+               bb_error_msg("%s: invalid number", arg);
+               return 1;
+       }
+       return 0;
+}
+
+static void FAST_FUNC conv_strtoull(const char *arg, void *result)
+{
+       *(unsigned long long*)result = bb_strtoull(arg, NULL, 0);
+}
+static void FAST_FUNC conv_strtoll(const char *arg, void *result)
+{
+       *(long long*)result = bb_strtoll(arg, NULL, 0);
+}
+static void FAST_FUNC conv_strtod(const char *arg, void *result)
+{
+       char *end;
+       /* Well, this one allows leading whitespace... so what? */
+       /* What I like much less is that "-" accepted too! :( */
+       *(double*)result = strtod(arg, &end);
+       if (end[0]) {
+               errno = ERANGE;
+               *(double*)result = 0;
+       }
+}
+
+/* Callers should check errno to detect errors */
+static unsigned long long my_xstrtoull(const char *arg)
+{
+       unsigned long long result;
+       if (multiconvert(arg, &result, conv_strtoull))
+               result = 0;
+       return result;
+}
+static long long my_xstrtoll(const char *arg)
+{
+       long long result;
+       if (multiconvert(arg, &result, conv_strtoll))
+               result = 0;
+       return result;
+}
+static double my_xstrtod(const char *arg)
+{
+       double result;
+       multiconvert(arg, &result, conv_strtod);
+       return result;
+}
+
+static void print_esc_string(char *str)
+{
+       while (*str) {
+               if (*str == '\\') {
+                       str++;
+                       bb_putchar(bb_process_escape_sequence((const char **)&str));
+               } else {
+                       bb_putchar(*str);
+                       str++;
+               }
+       }
+}
+
+static void print_direc(char *format, unsigned fmt_length,
+               int field_width, int precision,
+               const char *argument)
+{
+       long long llv;
+       double dv;
+       char saved;
+       char *have_prec, *have_width;
+
+       saved = format[fmt_length];
+       format[fmt_length] = '\0';
+
+       have_prec = strstr(format, ".*");
+       have_width = strchr(format, '*');
+       if (have_width - 1 == have_prec)
+               have_width = NULL;
+
+       switch (format[fmt_length - 1]) {
+       case 'c':
+               printf(format, *argument);
+               break;
+       case 'd':
+       case 'i':
+               llv = my_xstrtoll(argument);
+ print_long:
+               /* if (errno) return; - see comment at the top */
+               if (!have_width) {
+                       if (!have_prec)
+                               printf(format, llv);
+                       else
+                               printf(format, precision, llv);
+               } else {
+                       if (!have_prec)
+                               printf(format, field_width, llv);
+                       else
+                               printf(format, field_width, precision, llv);
+               }
+               break;
+       case 'o':
+       case 'u':
+       case 'x':
+       case 'X':
+               llv = my_xstrtoull(argument);
+               /* cheat: unsigned long and long have same width, so... */
+               goto print_long;
+       case 's':
+               /* Are char* and long long the same? */
+               if (sizeof(argument) == sizeof(llv)) {
+                       llv = (long long)(ptrdiff_t)argument;
+                       goto print_long;
+               } else {
+                       /* Hope compiler will optimize it out by moving call
+                        * instruction after the ifs... */
+                       if (!have_width) {
+                               if (!have_prec)
+                                       printf(format, argument, /*unused:*/ argument, argument);
+                               else
+                                       printf(format, precision, argument, /*unused:*/ argument);
+                       } else {
+                               if (!have_prec)
+                                       printf(format, field_width, argument, /*unused:*/ argument);
+                               else
+                                       printf(format, field_width, precision, argument);
+                       }
+                       break;
+               }
+       case 'f':
+       case 'e':
+       case 'E':
+       case 'g':
+       case 'G':
+               dv = my_xstrtod(argument);
+               /* if (errno) return; */
+               if (!have_width) {
+                       if (!have_prec)
+                               printf(format, dv);
+                       else
+                               printf(format, precision, dv);
+               } else {
+                       if (!have_prec)
+                               printf(format, field_width, dv);
+                       else
+                               printf(format, field_width, precision, dv);
+               }
+               break;
+       } /* switch */
+
+       format[fmt_length] = saved;
+}
+
+/* Handle params for "%*.*f". Negative numbers are ok (compat). */
+static int get_width_prec(const char *str)
+{
+       int v = bb_strtoi(str, NULL, 10);
+       if (errno) {
+               bb_error_msg("%s: invalid number", str);
+               v = 0;
+       }
+       return v;
+}
+
+/* Print the text in FORMAT, using ARGV for arguments to any '%' directives.
+   Return advanced ARGV.  */
+static char **print_formatted(char *f, char **argv)
+{
+       char *direc_start;      /* Start of % directive.  */
+       unsigned direc_length;  /* Length of % directive.  */
+       int field_width;        /* Arg to first '*' */
+       int precision;          /* Arg to second '*' */
+       char **saved_argv = argv;
+
+       for (; *f; ++f) {
+               switch (*f) {
+               case '%':
+                       direc_start = f++;
+                       direc_length = 1;
+                       field_width = precision = 0;
+                       if (*f == '%') {
+                               bb_putchar('%');
+                               break;
+                       }
+                       if (*f == 'b') {
+                               if (*argv) {
+                                       print_esc_string(*argv);
+                                       ++argv;
+                               }
+                               break;
+                       }
+                       if (strchr("-+ #", *f)) {
+                               ++f;
+                               ++direc_length;
+                       }
+                       if (*f == '*') {
+                               ++f;
+                               ++direc_length;
+                               if (*argv)
+                                       field_width = get_width_prec(*argv++);
+                       } else {
+                               while (isdigit(*f)) {
+                                       ++f;
+                                       ++direc_length;
+                               }
+                       }
+                       if (*f == '.') {
+                               ++f;
+                               ++direc_length;
+                               if (*f == '*') {
+                                       ++f;
+                                       ++direc_length;
+                                       if (*argv)
+                                               precision = get_width_prec(*argv++);
+                               } else {
+                                       while (isdigit(*f)) {
+                                               ++f;
+                                               ++direc_length;
+                                       }
+                               }
+                       }
+
+                       /* Remove "lLhz" size modifiers, repeatedly.
+                        * bash does not like "%lld", but coreutils
+                        * would happily take even "%Llllhhzhhzd"!
+                        * We will be permissive like coreutils */
+                       while ((*f | 0x20) == 'l' || *f == 'h' || *f == 'z') {
+                               overlapping_strcpy(f, f + 1);
+                       }
+                       /* Add "ll" if integer modifier, then print */
+                       {
+                               static const char format_chars[] ALIGN1 = "diouxXfeEgGcs";
+                               char *p = strchr(format_chars, *f);
+                               /* needed - try "printf %" without it */
+                               if (p == NULL) {
+                                       bb_error_msg("%s: invalid format", direc_start);
+                                       /* causes main() to exit with error */
+                                       return saved_argv - 1;
+                               }
+                               ++direc_length;
+                               if (p - format_chars <= 5) {
+                                       /* it is one of "diouxX" */
+                                       p = xmalloc(direc_length + 3);
+                                       memcpy(p, direc_start, direc_length);
+                                       p[direc_length + 1] = p[direc_length - 1];
+                                       p[direc_length - 1] = 'l';
+                                       p[direc_length] = 'l';
+                                       //bb_error_msg("<%s>", p);
+                                       direc_length += 2;
+                                       direc_start = p;
+                               } else {
+                                       p = NULL;
+                               }
+                               if (*argv) {
+                                       print_direc(direc_start, direc_length, field_width,
+                                                               precision, *argv);
+                                       ++argv;
+                               } else {
+                                       print_direc(direc_start, direc_length, field_width,
+                                                               precision, "");
+                               }
+                               free(p);
+                       }
+                       break;
+               case '\\':
+                       if (*++f == 'c') {
+                               return saved_argv; /* causes main() to exit */
+                       }
+                       bb_putchar(bb_process_escape_sequence((const char **)&f));
+                       f--;
+                       break;
+               default:
+                       bb_putchar(*f);
+               }
+       }
+
+       return argv;
+}
+
+int printf_main(int argc UNUSED_PARAM, char **argv)
+{
+       char *format;
+       char **argv2;
+
+       /* We must check that stdout is not closed.
+        * The reason for this is highly non-obvious.
+        * printf_main is used from shell.
+        * Shell must correctly handle 'printf "%s" foo'
+        * if stdout is closed. With stdio, output gets shoveled into
+        * stdout buffer, and even fflush cannot clear it out. It seems that
+        * even if libc receives EBADF on write attempts, it feels determined
+        * to output data no matter what. So it will try later,
+        * and possibly will clobber future output. Not good. */
+// TODO: check fcntl() & O_ACCMODE == O_WRONLY or O_RDWR?
+       if (fcntl(1, F_GETFL) == -1)
+               return 1; /* match coreutils 6.10 (sans error msg to stderr) */
+       //if (dup2(1, 1) != 1) - old way
+       //      return 1;
+
+       /* bash builtin errors out on "printf '-%s-\n' foo",
+        * coreutils-6.9 works. Both work with "printf -- '-%s-\n' foo".
+        * We will mimic coreutils. */
+       if (argv[1] && argv[1][0] == '-' && argv[1][1] == '-' && !argv[1][2])
+               argv++;
+       if (!argv[1]) {
+               if (ENABLE_ASH_BUILTIN_PRINTF
+                && applet_name[0] != 'p'
+               ) {
+                       bb_error_msg("usage: printf FORMAT [ARGUMENT...]");
+                       return 2; /* bash compat */
+               }
+               bb_show_usage();
+       }
+
+       format = argv[1];
+       argv2 = argv + 2;
+
+       do {
+               argv = argv2;
+               argv2 = print_formatted(format, argv);
+       } while (argv2 > argv && *argv2);
+
+       /* coreutils compat (bash doesn't do this):
+       if (*argv)
+               fprintf(stderr, "excess args ignored");
+       */
+
+       return (argv2 < argv); /* if true, print_formatted errored out */
+}
diff --git a/coreutils/pwd.c b/coreutils/pwd.c
new file mode 100644 (file)
index 0000000..57953d2
--- /dev/null
@@ -0,0 +1,27 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Mini pwd implementation for busybox
+ *
+ * Copyright (C) 1995, 1996 by Bruce Perens <bruce@pixar.com>.
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.h"
+
+/* This is a NOFORK applet. Be very careful! */
+
+int pwd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int pwd_main(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
+{
+       char *buf;
+
+       buf = xrealloc_getcwd_or_warn(NULL);
+       if (buf != NULL) {
+               puts(buf);
+               free(buf);
+               return fflush(stdout);
+       }
+
+       return EXIT_FAILURE;
+}
diff --git a/coreutils/readlink.c b/coreutils/readlink.c
new file mode 100644 (file)
index 0000000..bcf352e
--- /dev/null
@@ -0,0 +1,72 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Mini readlink implementation for busybox
+ *
+ * Copyright (C) 2000,2001 Matt Kraai <kraai@alumni.carnegiemellon.edu>
+ *
+ * Licensed under GPL v2 or later, see file LICENSE in this tarball for details.
+ */
+#include "libbb.h"
+
+/*
+ * # readlink --version
+ * readlink (GNU coreutils) 6.10
+ * # readlink --help
+ *   -f, --canonicalize
+ *      canonicalize by following every symlink in
+ *      every component of the given name recursively;
+ *      all but the last component must exist
+ *   -e, --canonicalize-existing
+ *      canonicalize by following every symlink in
+ *      every component of the given name recursively,
+ *      all components must exist
+ *   -m, --canonicalize-missing
+ *      canonicalize by following every symlink in
+ *      every component of the given name recursively,
+ *      without requirements on components existence
+ *   -n, --no-newline              do not output the trailing newline
+ *   -q, --quiet, -s, --silent     suppress most error messages
+ *   -v, --verbose                 report error messages
+ *
+ * bbox supports: -f -n -v (fully), -q -s (accepts but ignores)
+ */
+
+int readlink_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int readlink_main(int argc UNUSED_PARAM, char **argv)
+{
+       char *buf;
+       char *fname;
+       char pathbuf[PATH_MAX];
+
+       USE_FEATURE_READLINK_FOLLOW(
+               unsigned opt;
+               /* We need exactly one non-option argument.  */
+               opt_complementary = "=1";
+               opt = getopt32(argv, "fnvsq");
+               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 */
+       if (!(opt & 4)) /* not -v */
+               logmode = LOGMODE_NONE;
+
+       if (opt & 1) { /* -f */
+               buf = realpath(fname, pathbuf);
+       } else {
+               buf = xmalloc_readlink_or_warn(fname);
+       }
+
+       if (!buf)
+               return EXIT_FAILURE;
+       printf((opt & 2) ? "%s" : "%s\n", buf);
+
+       if (ENABLE_FEATURE_CLEAN_UP && !opt)
+               free(buf);
+
+       fflush_stdout_and_exit(EXIT_SUCCESS);
+}
diff --git a/coreutils/realpath.c b/coreutils/realpath.c
new file mode 100644 (file)
index 0000000..28906ba
--- /dev/null
@@ -0,0 +1,46 @@
+/* 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 "libbb.h"
+
+int realpath_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int realpath_main(int argc UNUSED_PARAM, 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 (!*++argv) {
+               bb_show_usage();
+       }
+
+       do {
+               if (realpath(*argv, resolved_path) != NULL) {
+                       puts(resolved_path);
+               } else {
+                       retval = EXIT_FAILURE;
+                       bb_simple_perror_msg(*argv);
+               }
+       } while (*++argv);
+
+#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 (file)
index 0000000..6b3fbcf
--- /dev/null
@@ -0,0 +1,56 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Mini rm implementation for busybox
+ *
+ * Copyright (C) 2001 Matt Kraai <kraai@alumni.carnegiemellon.edu>
+ *
+ * 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 "libbb.h"
+
+/* This is a NOFORK applet. Be very careful! */
+
+int rm_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int rm_main(int argc UNUSED_PARAM, char **argv)
+{
+       int status = 0;
+       int flags = 0;
+       unsigned opt;
+
+       opt_complementary = "f-i:i-f";
+       /* -v (verbose) is ignored */
+       opt = getopt32(argv, "fiRrv");
+       argv += optind;
+       if (opt & 1)
+               flags |= FILEUTILS_FORCE;
+       if (opt & 2)
+               flags |= FILEUTILS_INTERACTIVE;
+       if (opt & (8|4))
+               flags |= FILEUTILS_RECUR;
+
+       if (*argv != NULL) {
+               do {
+                       const char *base = bb_get_last_path_component_strip(*argv);
+
+                       if (DOT_OR_DOTDOT(base)) {
+                               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 (file)
index 0000000..2450a43
--- /dev/null
@@ -0,0 +1,70 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * rmdir implementation for busybox
+ *
+ * Copyright (C) 2003  Manuel Novoa III  <mjn3@codepoet.org>
+ *
+ * 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 "libbb.h"
+
+/* This is a NOFORK applet. Be very careful! */
+
+
+#define PARENTS 0x01
+#define IGNORE_NON_EMPTY 0x02
+
+int rmdir_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int rmdir_main(int argc UNUSED_PARAM, char **argv)
+{
+       int status = EXIT_SUCCESS;
+       int flags;
+       char *path;
+
+#if ENABLE_FEATURE_RMDIR_LONG_OPTIONS
+       static const char rmdir_longopts[] ALIGN1 =
+               "parents\0"                  No_argument "p"
+               /* Debian etch: many packages fail to be purged or installed
+                * because they desperately want this option: */
+               "ignore-fail-on-non-empty\0" No_argument "\xff"
+               ;
+       applet_long_options = rmdir_longopts;
+#endif
+       flags = getopt32(argv, "p");
+       argv += optind;
+
+       if (!*argv) {
+               bb_show_usage();
+       }
+
+       do {
+               path = *argv;
+
+               while (1) {
+                       if (rmdir(path) < 0) {
+#if ENABLE_FEATURE_RMDIR_LONG_OPTIONS
+                               if ((flags & IGNORE_NON_EMPTY) && errno == ENOTEMPTY)
+                                       break;
+#endif
+                               bb_perror_msg("'%s'", path);    /* Match gnu rmdir msg. */
+                               status = EXIT_FAILURE;
+                       } else if (flags & PARENTS) {
+                               /* Note: path was not "" since rmdir succeeded. */
+                               path = dirname(path);
+                               /* Path is now just the parent component.  Dirname
+                                * returns "." if there are no parents.
+                                */
+                               if (NOT_LONE_CHAR(path, '.')) {
+                                       continue;
+                               }
+                       }
+                       break;
+               }
+       } while (*++argv);
+
+       return status;
+}
diff --git a/coreutils/seq.c b/coreutils/seq.c
new file mode 100644 (file)
index 0000000..4b853c6
--- /dev/null
@@ -0,0 +1,53 @@
+/* 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 "libbb.h"
+
+/* This is a NOFORK applet. Be very careful! */
+
+
+int seq_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int seq_main(int argc, char **argv)
+{
+       enum {
+               OPT_w = (1 << 0),
+               OPT_s = (1 << 1),
+       };
+       double last, increment, i;
+       const char *sep, *opt_s = "\n";
+       unsigned opt = getopt32(argv, "+ws:", &opt_s);
+       unsigned width = 0;
+
+       argc -= optind;
+       argv += optind;
+       i = increment = 1;
+       switch (argc) {
+               case 3:
+                       increment = atof(argv[1]);
+               case 2:
+                       i = atof(*argv);
+               case 1:
+                       last = atof(argv[argc-1]);
+                       break;
+               default:
+                       bb_show_usage();
+       }
+       if (opt & OPT_w) /* Pad to length of start or last */
+               width = MAX(strlen(*argv), strlen(argv[argc-1]));
+
+       /* You should note that this is pos-5.0.91 semantics, -- FK. */
+       sep = "";
+       while ((increment > 0 && i <= last) || (increment < 0 && i >= last)) {
+               printf("%s%0*g", sep, width, i);
+               sep = opt_s;
+               i += increment;
+       }
+       bb_putchar('\n');
+       return fflush(stdout);
+}
diff --git a/coreutils/sleep.c b/coreutils/sleep.c
new file mode 100644 (file)
index 0000000..de18dd0
--- /dev/null
@@ -0,0 +1,104 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * sleep implementation for busybox
+ *
+ * Copyright (C) 2003  Manuel Novoa III  <mjn3@codepoet.org>
+ *
+ * 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 "libbb.h"
+
+/* This is a NOFORK applet. Be very careful! */
+
+
+#if ENABLE_FEATURE_FANCY_SLEEP || ENABLE_FEATURE_FLOAT_SLEEP
+static const struct suffix_mult sfx[] = {
+       { "s", 1 },
+       { "m", 60 },
+       { "h", 60*60 },
+       { "d", 24*60*60 },
+       { }
+};
+#endif
+
+int sleep_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int sleep_main(int argc UNUSED_PARAM, char **argv)
+{
+#if ENABLE_FEATURE_FLOAT_SLEEP
+       double duration;
+       struct timespec ts;
+#else
+       unsigned duration;
+#endif
+
+       ++argv;
+       if (!*argv)
+               bb_show_usage();
+
+#if ENABLE_FEATURE_FLOAT_SLEEP
+
+       duration = 0;
+       do {
+               char *arg = *argv;
+               if (strchr(arg, '.')) {
+                       double d;
+                       int len = strspn(arg, "0123456789.");
+                       char sv = arg[len];
+                       arg[len] = '\0';
+                       d = bb_strtod(arg, NULL);
+                       if (errno)
+                               bb_show_usage();
+                       arg[len] = sv;
+                       len--;
+                       sv = arg[len];
+                       arg[len] = '1';
+                       duration += d * xatoul_sfx(&arg[len], sfx);
+                       arg[len] = sv;
+               } else
+                       duration += xatoul_sfx(arg, sfx);
+       } while (*++argv);
+
+       ts.tv_sec = MAXINT(typeof(ts.tv_sec));
+       ts.tv_nsec = 0;
+       if (duration >= 0 && duration < ts.tv_sec) {
+               ts.tv_sec = duration;
+               ts.tv_nsec = (duration - ts.tv_sec) * 1000000000;
+       }
+       do {
+               errno = 0;
+               nanosleep(&ts, &ts);
+       } while (errno == EINTR);
+
+#elif ENABLE_FEATURE_FANCY_SLEEP
+
+       duration = 0;
+       do {
+               duration += xatou_range_sfx(*argv, 0, UINT_MAX - duration, sfx);
+       } while (*++argv);
+       sleep(duration);
+
+#else /* simple */
+
+       duration = xatou(*argv);
+       sleep(duration);
+       // Off. If it's really needed, provide example why
+       //if (sleep(duration)) {
+       //      bb_perror_nomsg_and_die();
+       //}
+
+#endif
+
+       return EXIT_SUCCESS;
+}
diff --git a/coreutils/sort.c b/coreutils/sort.c
new file mode 100644 (file)
index 0000000..fad6d12
--- /dev/null
@@ -0,0 +1,406 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * SuS3 compliant sort implementation for busybox
+ *
+ * Copyright (C) 2004 by Rob Landley <rob@landley.net>
+ *
+ * MAINTAINER: Rob Landley <rob@landley.net>
+ *
+ * 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 "libbb.h"
+
+/* This is a NOEXEC applet. Be very careful! */
+
+
+/*
+       sort [-m][-o output][-bdfinru][-t char][-k keydef]... [file...]
+       sort -c [-bdfinru][-t char][-k keydef][file]
+*/
+
+/* These are sort types */
+static const char OPT_STR[] ALIGN1 = "ngMucszbrdfimS:T:o:k:t:";
+enum {
+       FLAG_n  = 1,            /* Numeric sort */
+       FLAG_g  = 2,            /* Sort using strtod() */
+       FLAG_M  = 4,            /* Sort date */
+/* ucsz apply to root level only, not keys.  b at root level implies bb */
+       FLAG_u  = 8,            /* Unique */
+       FLAG_c  = 0x10,         /* Check: no output, exit(!ordered) */
+       FLAG_s  = 0x20,         /* Stable sort, no ascii fallback at end */
+       FLAG_z  = 0x40,         /* Input and output is NUL terminated, not \n */
+/* These can be applied to search keys, the previous four can't */
+       FLAG_b  = 0x80,         /* Ignore leading blanks */
+       FLAG_r  = 0x100,        /* Reverse */
+       FLAG_d  = 0x200,        /* Ignore !(isalnum()|isspace()) */
+       FLAG_f  = 0x400,        /* Force uppercase */
+       FLAG_i  = 0x800,        /* Ignore !isprint() */
+       FLAG_m  = 0x1000,       /* ignored: merge already sorted files; do not sort */
+       FLAG_S  = 0x2000,       /* ignored: -S, --buffer-size=SIZE */
+       FLAG_T  = 0x4000,       /* ignored: -T, --temporary-directory=DIR */
+       FLAG_o  = 0x8000,
+       FLAG_k  = 0x10000,
+       FLAG_t  = 0x20000,
+       FLAG_bb = 0x80000000,   /* Ignore trailing blanks  */
+};
+
+#if ENABLE_FEATURE_SORT_BIG
+static char key_separator;
+
+static struct sort_key {
+       struct sort_key *next_key;      /* linked list */
+       unsigned range[4];      /* start word, start char, end word, end char */
+       unsigned flags;
+} *key_list;
+
+static char *get_key(char *str, struct sort_key *key, int flags)
+{
+       int start = 0, end = 0, len, j;
+       unsigned i;
+
+       /* 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++) {
+                               if (key_separator) {
+                                       /* Skip body of key and separator */
+                                       while (str[end]) {
+                                               if (str[end++] == key_separator)
+                                                       break;
+                                       }
+                               } else {
+                                       /* Skip leading blanks */
+                                       while (isspace(str[end]))
+                                               end++;
+                                       /* Skip body of key */
+                                       while (str[end]) {
+                                               if (isspace(str[end]))
+                                                       break;
+                                               end++;
+                                       }
+                               }
+                       }
+               }
+               if (!j) start = end;
+       }
+       /* 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) \
+       ((option_mask32 & FLAG_z) \
+       ? bb_get_chunk_from_file(fp, NULL) \
+       : xmalloc_fgetline(fp))
+#else
+#define GET_LINE(fp) xmalloc_fgetline(fp)
+#endif
+
+/* Iterate through keys list and perform comparisons */
+static int compare_keys(const void *xarg, const void *yarg)
+{
+       int flags = option_mask32, 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 : option_mask32;
+               /* 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:
+#if ENABLE_LOCALE_SUPPORT
+                       retval = strcoll(x, y);
+#else
+                       retval = strcmp(x, y);
+#endif
+                       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
+       } /* for */
+
+       /* Perform fallback sort if necessary */
+       if (!retval && !(option_mask32 & FLAG_s))
+               retval = strcmp(*(char **)xarg, *(char **)yarg);
+
+       if (flags & FLAG_r) return -retval;
+       return retval;
+}
+
+#if ENABLE_FEATURE_SORT_BIG
+static unsigned str2u(char **str)
+{
+       unsigned long lu;
+       if (!isdigit((*str)[0]))
+               bb_error_msg_and_die("bad field specification");
+       lu = strtoul(*str, str, 10);
+       if ((sizeof(long) > sizeof(int) && lu > INT_MAX) || !lu)
+               bb_error_msg_and_die("bad field specification");
+       return lu;
+}
+#endif
+
+int sort_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int sort_main(int argc UNUSED_PARAM, char **argv)
+{
+       FILE *fp, *outfile = stdout;
+       char *line, **lines = NULL;
+       char *str_ignored, *str_o, *str_t;
+       llist_t *lst_k = NULL;
+       int i, flag;
+       int linecount = 0;
+
+       xfunc_error_retval = 2;
+
+       /* Parse command line options */
+       /* -o and -t can be given at most once */
+       opt_complementary = "o--o:t--t:" /* -t, -o: maximum one of each */
+                       "k::"; /* -k takes list */
+       getopt32(argv, OPT_STR, &str_ignored, &str_ignored, &str_o, &lst_k, &str_t);
+#if ENABLE_FEATURE_SORT_BIG
+       if (option_mask32 & FLAG_o) outfile = xfopen_for_write(str_o);
+       if (option_mask32 & FLAG_t) {
+               if (!str_t[0] || str_t[1])
+                       bb_error_msg_and_die("bad -t parameter");
+               key_separator = str_t[0];
+       }
+       /* parse sort key */
+       while (lst_k) {
+               enum {
+                       FLAG_allowed_for_k =
+                               FLAG_n | /* Numeric sort */
+                               FLAG_g | /* Sort using strtod() */
+                               FLAG_M | /* Sort date */
+                               FLAG_b | /* Ignore leading blanks */
+                               FLAG_r | /* Reverse */
+                               FLAG_d | /* Ignore !(isalnum()|isspace()) */
+                               FLAG_f | /* Force uppercase */
+                               FLAG_i | /* Ignore !isprint() */
+                       0
+               };
+               struct sort_key *key = add_key();
+               char *str_k = llist_pop(&lst_k);
+               const char *temp2;
+
+               i = 0; /* i==0 before comma, 1 after (-k3,6) */
+               while (*str_k) {
+                       /* Start of range */
+                       /* Cannot use bb_strtou - suffix can be a letter */
+                       key->range[2*i] = str2u(&str_k);
+                       if (*str_k == '.') {
+                               str_k++;
+                               key->range[2*i+1] = str2u(&str_k);
+                       }
+                       while (*str_k) {
+                               if (*str_k == ',' && !i++) {
+                                       str_k++;
+                                       break;
+                               } /* no else needed: fall through to syntax error
+                                        because comma isn't in OPT_STR */
+                               temp2 = strchr(OPT_STR, *str_k);
+                               if (!temp2)
+                                       bb_error_msg_and_die("unknown key option");
+                               flag = 1 << (temp2 - OPT_STR);
+                               if (flag & ~FLAG_allowed_for_k)
+                                       bb_error_msg_and_die("unknown sort type");
+                               /* b after ',' means strip _trailing_ space */
+                               if (i && flag == FLAG_b) flag = FLAG_bb;
+                               key->flags |= flag;
+                               str_k++;
+                       }
+               }
+       }
+#endif
+       /* global b strips leading and trailing spaces */
+       if (option_mask32 & FLAG_b) option_mask32 |= FLAG_bb;
+
+       /* Open input files and read data */
+       argv += optind;
+       if (!*argv)
+               *--argv = (char*)"-";
+       do {
+               /* coreutils 6.9 compat: abort on first open error,
+                * do not continue to next file: */
+               fp = xfopen_stdin(*argv);
+               for (;;) {
+                       line = GET_LINE(fp);
+                       if (!line) break;
+                       lines = xrealloc_vector(lines, 6, linecount);
+                       lines[linecount++] = line;
+               }
+               fclose_if_not_stdin(fp);
+       } while (*++argv);
+
+#if ENABLE_FEATURE_SORT_BIG
+       /* if no key, perform alphabetic sort */
+       if (!key_list)
+               add_key()->range[0] = 1;
+       /* handle -c */
+       if (option_mask32 & FLAG_c) {
+               int j = (option_mask32 & 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 EXIT_FAILURE;
+                       }
+               return EXIT_SUCCESS;
+       }
+#endif
+       /* Perform the actual sort */
+       qsort(lines, linecount, sizeof(char *), compare_keys);
+       /* handle -u */
+       if (option_mask32 & FLAG_u) {
+               flag = 0;
+               /* coreutils 6.3 drop lines for which only key is the same */
+               /* -- disabling last-resort compare... */
+               option_mask32 |= FLAG_s;
+               for (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 */
+       flag = (option_mask32 & FLAG_z) ? '\0' : '\n';
+       for (i = 0; i < linecount; i++)
+               fprintf(outfile, "%s%c", lines[i], flag);
+
+       fflush_stdout_and_exit(EXIT_SUCCESS);
+}
diff --git a/coreutils/split.c b/coreutils/split.c
new file mode 100644 (file)
index 0000000..f1ec64b
--- /dev/null
@@ -0,0 +1,139 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * split - split a file into pieces
+ * Copyright (c) 2007 Bernhard Reutner-Fischer
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+/* BB_AUDIT: SUSv3 compliant
+ * SUSv3 requirements:
+ * http://www.opengroup.org/onlinepubs/009695399/utilities/split.html
+ */
+#include "libbb.h"
+
+static const struct suffix_mult split_suffices[] = {
+#if ENABLE_FEATURE_SPLIT_FANCY
+       { "b", 512 },
+#endif
+       { "k", 1024 },
+       { "m", 1024*1024 },
+#if ENABLE_FEATURE_SPLIT_FANCY
+       { "g", 1024*1024*1024 },
+#endif
+       { }
+};
+
+/* Increment the suffix part of the filename.
+ * Returns NULL if we are out of filenames.
+ */
+static char *next_file(char *old, unsigned suffix_len)
+{
+       size_t end = strlen(old);
+       unsigned i = 1;
+       char *curr;
+
+       do {
+               curr = old + end - i;
+               if (*curr < 'z') {
+                       *curr += 1;
+                       break;
+               }
+               i++;
+               if (i > suffix_len) {
+                       return NULL;
+               }
+               *curr = 'a';
+       } while (1);
+
+       return old;
+}
+
+#define read_buffer bb_common_bufsiz1
+enum { READ_BUFFER_SIZE = COMMON_BUFSIZE - 1 };
+
+#define SPLIT_OPT_l (1<<0)
+#define SPLIT_OPT_b (1<<1)
+#define SPLIT_OPT_a (1<<2)
+
+int split_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int split_main(int argc UNUSED_PARAM, char **argv)
+{
+       unsigned suffix_len = 2;
+       char *pfx;
+       char *count_p;
+       const char *sfx;
+       off_t cnt = 1000;
+       off_t remaining = 0;
+       unsigned opt;
+       ssize_t bytes_read, to_write;
+       char *src;
+
+       opt_complementary = "?2:a+"; /* max 2 args; -a N */
+       opt = getopt32(argv, "l:b:a:", &count_p, &count_p, &suffix_len);
+
+       if (opt & SPLIT_OPT_l)
+               cnt = XATOOFF(count_p);
+       if (opt & SPLIT_OPT_b) // FIXME: also needs XATOOFF
+               cnt = xatoull_sfx(count_p, split_suffices);
+       sfx = "x";
+
+       argv += optind;
+       if (argv[0]) {
+               if (argv[1])
+                       sfx = argv[1];
+               xmove_fd(xopen(argv[0], O_RDONLY), 0);
+       } else {
+               argv[0] = (char *) bb_msg_standard_input;
+       }
+
+       if (NAME_MAX < strlen(sfx) + suffix_len)
+               bb_error_msg_and_die("suffix too long");
+
+       {
+               char *char_p = xzalloc(suffix_len + 1);
+               memset(char_p, 'a', suffix_len);
+               pfx = xasprintf("%s%s", sfx, char_p);
+               if (ENABLE_FEATURE_CLEAN_UP)
+                       free(char_p);
+       }
+
+       while (1) {
+               bytes_read = safe_read(STDIN_FILENO, read_buffer, READ_BUFFER_SIZE);
+               if (!bytes_read)
+                       break;
+               if (bytes_read < 0)
+                       bb_simple_perror_msg_and_die(argv[0]);
+               src = read_buffer;
+               do {
+                       if (!remaining) {
+                               if (!pfx)
+                                       bb_error_msg_and_die("suffixes exhausted");
+                               xmove_fd(xopen(pfx, O_WRONLY | O_CREAT | O_TRUNC), 1);
+                               pfx = next_file(pfx, suffix_len);
+                               remaining = cnt;
+                       }
+
+                       if (opt & SPLIT_OPT_b) {
+                               /* split by bytes */
+                               to_write = (bytes_read < remaining) ? bytes_read : remaining;
+                               remaining -= to_write;
+                       } else {
+                               /* split by lines */
+                               /* can be sped up by using _memrchr_
+                                * and writing many lines at once... */
+                               char *end = memchr(src, '\n', bytes_read);
+                               if (end) {
+                                       --remaining;
+                                       to_write = end - src + 1;
+                               } else {
+                                       to_write = bytes_read;
+                               }
+                       }
+
+                       xwrite(STDOUT_FILENO, src, to_write);
+                       bytes_read -= to_write;
+                       src += to_write;
+               } while (bytes_read);
+       }
+       return EXIT_SUCCESS;
+}
diff --git a/coreutils/stat.c b/coreutils/stat.c
new file mode 100644 (file)
index 0000000..32e8b42
--- /dev/null
@@ -0,0 +1,669 @@
+/* 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 <andersen@codepoet.org>
+ * Copyright (C) 2005 by Mike Frysinger <vapier@gentoo.org>
+ * Copyright (C) 2006 by Yoshinori Sato <ysato@users.sourceforge.jp>
+ *
+ * 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 "libbb.h"
+
+/* vars to control behavior */
+#define OPT_FILESYS     (1 << 0)
+#define OPT_TERSE       (1 << 1)
+#define OPT_DEREFERENCE (1 << 2)
+#define OPT_SELINUX     (1 << 3)
+
+#if ENABLE_FEATURE_STAT_FORMAT
+typedef bool (*statfunc_ptr)(const char *, const char *);
+#else
+typedef bool (*statfunc_ptr)(const char *);
+#endif
+
+static const char *file_type(const struct stat *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 const char *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")] ALIGN1;*/
+#define buf bb_common_bufsiz1
+
+       strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S.000000000", localtime(&t));
+       return buf;
+#undef 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 const char *human_fstype(uint32_t f_type)
+{
+       static const struct types {
+               uint32_t type;
+               const char *const 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" }
+       };
+
+       int i;
+
+       for (i = 0; humantypes[i].type; ++i)
+               if (humantypes[i].type == f_type)
+                       break;
+       return humantypes[i].fs;
+}
+
+/* "man statfs" says that statfsbuf->f_fsid is a mess */
+/* coreutils treats it as an array of ints, most significant first */
+static unsigned long long get_f_fsid(const struct statfs *statfsbuf)
+{
+       const unsigned *p = (const void*) &statfsbuf->f_fsid;
+       unsigned sz = sizeof(statfsbuf->f_fsid) / sizeof(unsigned);
+       unsigned long long r = 0;
+
+       do
+               r = (r << (sizeof(unsigned)*8)) | *p++;
+       while (--sz > 0);
+       return r;
+}
+
+#if ENABLE_FEATURE_STAT_FORMAT
+static void strcatc(char *str, char c)
+{
+       int len = strlen(str);
+       str[len++] = c;
+       str[len] = '\0';
+}
+
+static void printfs(char *pformat, const char *msg)
+{
+       strcatc(pformat, 's');
+       printf(pformat, msg);
+}
+
+/* print statfs info */
+static void print_statfs(char *pformat, const char m,
+               const char *const filename, const void *data
+               USE_SELINUX(, security_context_t scontext))
+{
+       const struct statfs *statfsbuf = data;
+       if (m == 'n') {
+               printfs(pformat, filename);
+       } else if (m == 'i') {
+               strcat(pformat, "llx");
+               printf(pformat, get_f_fsid(statfsbuf));
+       } else if (m == 'l') {
+               strcat(pformat, "lu");
+               printf(pformat, (unsigned long) (statfsbuf->f_namelen));
+       } else if (m == 't') {
+               strcat(pformat, "lx");
+               printf(pformat, (unsigned long) (statfsbuf->f_type)); /* no equiv */
+       } else if (m == 'T') {
+               printfs(pformat, human_fstype(statfsbuf->f_type));
+       } else if (m == 'b') {
+               strcat(pformat, "jd");
+               printf(pformat, (intmax_t) (statfsbuf->f_blocks));
+       } else if (m == 'f') {
+               strcat(pformat, "jd");
+               printf(pformat, (intmax_t) (statfsbuf->f_bfree));
+       } else if (m == 'a') {
+               strcat(pformat, "jd");
+               printf(pformat, (intmax_t) (statfsbuf->f_bavail));
+       } else if (m == 's' || m == 'S') {
+               strcat(pformat, "lu");
+               printf(pformat, (unsigned long) (statfsbuf->f_bsize));
+       } else if (m == 'c') {
+               strcat(pformat, "jd");
+               printf(pformat, (intmax_t) (statfsbuf->f_files));
+       } else if (m == 'd') {
+               strcat(pformat, "jd");
+               printf(pformat, (intmax_t) (statfsbuf->f_ffree));
+#if ENABLE_SELINUX
+       } else if (m == 'C' && (option_mask32 & OPT_SELINUX)) {
+               printfs(pformat, scontext);
+#endif
+       } else {
+               strcatc(pformat, 'c');
+               printf(pformat, m);
+       }
+}
+
+/* print stat info */
+static void print_stat(char *pformat, const char m,
+               const char *const filename, const void *data
+               USE_SELINUX(, security_context_t scontext))
+{
+#define TYPE_SIGNED(t) (! ((t) 0 < (t) -1))
+       struct stat *statbuf = (struct stat *) data;
+       struct passwd *pw_ent;
+       struct group *gw_ent;
+
+       if (m == 'n') {
+               printfs(pformat, filename);
+       } else if (m == 'N') {
+               strcatc(pformat, 's');
+               if (S_ISLNK(statbuf->st_mode)) {
+                       char *linkname = xmalloc_readlink_or_warn(filename);
+                       if (linkname == NULL)
+                               return;
+                       /*printf("\"%s\" -> \"%s\"", filename, linkname); */
+                       printf(pformat, filename);
+                       printf(" -> ");
+                       printf(pformat, linkname);
+                       free(linkname);
+               } else {
+                       printf(pformat, filename);
+               }
+       } else if (m == 'd') {
+               strcat(pformat, "ju");
+               printf(pformat, (uintmax_t) statbuf->st_dev);
+       } else if (m == 'D') {
+               strcat(pformat, "jx");
+               printf(pformat, (uintmax_t) statbuf->st_dev);
+       } else if (m == 'i') {
+               strcat(pformat, "ju");
+               printf(pformat, (uintmax_t) statbuf->st_ino);
+       } else if (m == 'a') {
+               strcat(pformat, "lo");
+               printf(pformat, (unsigned long) (statbuf->st_mode & (S_ISUID|S_ISGID|S_ISVTX|S_IRWXU|S_IRWXG|S_IRWXO)));
+       } else if (m == 'A') {
+               printfs(pformat, bb_mode_string(statbuf->st_mode));
+       } else if (m == 'f') {
+               strcat(pformat, "lx");
+               printf(pformat, (unsigned long) statbuf->st_mode);
+       } else if (m == 'F') {
+               printfs(pformat, file_type(statbuf));
+       } else if (m == 'h') {
+               strcat(pformat, "lu");
+               printf(pformat, (unsigned long) statbuf->st_nlink);
+       } else if (m == 'u') {
+               strcat(pformat, "lu");
+               printf(pformat, (unsigned long) statbuf->st_uid);
+       } else if (m == 'U') {
+               setpwent();
+               pw_ent = getpwuid(statbuf->st_uid);
+               printfs(pformat, (pw_ent != NULL) ? pw_ent->pw_name : "UNKNOWN");
+       } else if (m == 'g') {
+               strcat(pformat, "lu");
+               printf(pformat, (unsigned long) statbuf->st_gid);
+       } else if (m == 'G') {
+               setgrent();
+               gw_ent = getgrgid(statbuf->st_gid);
+               printfs(pformat, (gw_ent != NULL) ? gw_ent->gr_name : "UNKNOWN");
+       } else if (m == 't') {
+               strcat(pformat, "lx");
+               printf(pformat, (unsigned long) major(statbuf->st_rdev));
+       } else if (m == 'T') {
+               strcat(pformat, "lx");
+               printf(pformat, (unsigned long) minor(statbuf->st_rdev));
+       } else if (m == 's') {
+               strcat(pformat, "ju");
+               printf(pformat, (uintmax_t) (statbuf->st_size));
+       } else if (m == 'B') {
+               strcat(pformat, "lu");
+               printf(pformat, (unsigned long) 512); //ST_NBLOCKSIZE
+       } else if (m == 'b') {
+               strcat(pformat, "ju");
+               printf(pformat, (uintmax_t) statbuf->st_blocks);
+       } else if (m == 'o') {
+               strcat(pformat, "lu");
+               printf(pformat, (unsigned long) statbuf->st_blksize);
+       } else if (m == 'x') {
+               printfs(pformat, human_time(statbuf->st_atime));
+       } else if (m == 'X') {
+               strcat(pformat, TYPE_SIGNED(time_t) ? "ld" : "lu");
+               printf(pformat, (unsigned long) statbuf->st_atime);
+       } else if (m == 'y') {
+               printfs(pformat, human_time(statbuf->st_mtime));
+       } else if (m == 'Y') {
+               strcat(pformat, TYPE_SIGNED(time_t) ? "ld" : "lu");
+               printf(pformat, (unsigned long) statbuf->st_mtime);
+       } else if (m == 'z') {
+               printfs(pformat, human_time(statbuf->st_ctime));
+       } else if (m == 'Z') {
+               strcat(pformat, TYPE_SIGNED(time_t) ? "ld" : "lu");
+               printf(pformat, (unsigned long) statbuf->st_ctime);
+#if ENABLE_SELINUX
+       } else if (m == 'C' && (option_mask32 & OPT_SELINUX)) {
+               printfs(pformat, scontext);
+#endif
+       } else {
+               strcatc(pformat, 'c');
+               printf(pformat, m);
+       }
+}
+
+static void print_it(const char *masterformat, const char *filename,
+               void (*print_func) (char*, char, const char*, const void* USE_SELINUX(, security_context_t scontext)),
+               const void *data
+               USE_SELINUX(, security_context_t scontext) )
+{
+       /* 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.  */
+       char *dest = xmalloc(strlen(format) + 2 + 1);
+       char *b;
+
+       b = format;
+       while (b) {
+               size_t len;
+               char *p = strchr(b, '%');
+               if (!p) {
+                       /* coreutils 6.3 always prints <cr> at the end */
+                       /*fputs(b, stdout);*/
+                       puts(b);
+                       break;
+               }
+               *p++ = '\0';
+               fputs(b, stdout);
+
+               /* dest = "%<modifiers>" */
+               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 '%':
+                       bb_putchar('%');
+                       break;
+               default:
+                       /* Completes "%<modifiers>" with specifier and printfs */
+                       print_func(dest, *p, filename, data USE_SELINUX(,scontext));
+                       break;
+               }
+       }
+
+       free(format);
+       free(dest);
+}
+#endif
+
+/* Stat the file system and print what we find.  */
+#if !ENABLE_FEATURE_STAT_FORMAT
+#define do_statfs(filename, format) do_statfs(filename)
+#endif
+static bool do_statfs(const char *filename, const char *format)
+{
+       struct statfs statfsbuf;
+
+#if !ENABLE_FEATURE_STAT_FORMAT
+       const char *format;
+#endif
+#if ENABLE_SELINUX
+       security_context_t scontext = NULL;
+
+       if (option_mask32 & OPT_SELINUX) {
+               if ((option_mask32 & OPT_DEREFERENCE
+                    ? lgetfilecon(filename, &scontext)
+                    : getfilecon(filename, &scontext)
+                   ) < 0
+               ) {
+                       bb_perror_msg(filename);
+                       return 0;
+               }
+       }
+#endif
+       if (statfs(filename, &statfsbuf) != 0) {
+               bb_perror_msg("cannot read file system information for '%s'", filename);
+               return 0;
+       }
+
+#if ENABLE_FEATURE_STAT_FORMAT
+       if (format == NULL) {
+#if !ENABLE_SELINUX
+               format = (option_mask32 & 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");
+#else
+               format = (option_mask32 & OPT_TERSE
+                       ? (option_mask32 & OPT_SELINUX ? "%n %i %l %t %s %b %f %a %c %d %C\n":
+                       "%n %i %l %t %s %b %f %a %c %d\n")
+                       : (option_mask32 & OPT_SELINUX ?
+                       "  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"
+                       "  S_context: %C\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\n")
+                       );
+#endif /* SELINUX */
+       }
+       print_it(format, filename, print_statfs, &statfsbuf USE_SELINUX(, scontext));
+#else /* FEATURE_STAT_FORMAT */
+       format = (option_mask32 & OPT_TERSE
+               ? "%s %llx %lu "
+               : "  File: \"%s\"\n"
+                 "    ID: %-8llx Namelen: %-7lu ");
+       printf(format,
+              filename,
+              get_f_fsid(&statfsbuf),
+              statfsbuf.f_namelen);
+
+       if (option_mask32 & OPT_TERSE)
+               printf("%lx ", (unsigned long) (statfsbuf.f_type));
+       else
+               printf("Type: %s\n", human_fstype(statfsbuf.f_type));
+
+#if !ENABLE_SELINUX
+       format = (option_mask32 & 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) (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));
+#else
+       format = (option_mask32 & OPT_TERSE
+               ? (option_mask32 & OPT_SELINUX ? "%lu %ld %ld %ld %ld %ld %C\n":
+               "%lu %ld %ld %ld %ld %ld\n")
+               : (option_mask32 & OPT_SELINUX ?
+               "Block size: %-10lu\n"
+               "Blocks: Total: %-10jd Free: %-10jd Available: %jd\n"
+               "Inodes: Total: %-10jd Free: %jd"
+               "S_context: %C\n":
+               "Block size: %-10lu\n"
+               "Blocks: Total: %-10jd Free: %-10jd Available: %jd\n"
+               "Inodes: Total: %-10jd Free: %jd\n"));
+       printf(format,
+              (unsigned long) (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),
+               scontext);
+
+       if (scontext)
+               freecon(scontext);
+#endif
+#endif /* FEATURE_STAT_FORMAT */
+       return 1;
+}
+
+/* stat the file and print what we find */
+#if !ENABLE_FEATURE_STAT_FORMAT
+#define do_stat(filename, format) do_stat(filename)
+#endif
+static bool do_stat(const char *filename, const char *format)
+{
+       struct stat statbuf;
+#if ENABLE_SELINUX
+       security_context_t scontext = NULL;
+
+       if (option_mask32 & OPT_SELINUX) {
+               if ((option_mask32 & OPT_DEREFERENCE
+                    ? lgetfilecon(filename, &scontext)
+                    : getfilecon(filename, &scontext)
+                   ) < 0
+               ) {
+                       bb_perror_msg(filename);
+                       return 0;
+               }
+       }
+#endif
+       if ((option_mask32 & OPT_DEREFERENCE ? stat : lstat) (filename, &statbuf) != 0) {
+               bb_perror_msg("cannot stat '%s'", filename);
+               return 0;
+       }
+
+#if ENABLE_FEATURE_STAT_FORMAT
+       if (format == NULL) {
+#if !ENABLE_SELINUX
+               if (option_mask32 & 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";
+                       }
+               }
+#else
+               if (option_mask32 & OPT_TERSE) {
+                       format = (option_mask32 & OPT_SELINUX ?
+                                 "%n %s %b %f %u %g %D %i %h %t %T %X %Y %Z %o %C\n":
+                                 "%n %s %b %f %u %g %D %i %h %t %T %X %Y %Z %o\n");
+               } else {
+                       if (S_ISBLK(statbuf.st_mode) || S_ISCHR(statbuf.st_mode)) {
+                               format = (option_mask32 & OPT_SELINUX ?
+                                         "  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"
+                                         "   S_Context: %C\n"
+                                         "Access: %x\n" "Modify: %y\n" "Change: %z\n":
+                                         "  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 = (option_mask32 & OPT_SELINUX ?
+                                         "  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"
+                                         "S_Context: %C\n"
+                                         "Access: %x\n" "Modify: %y\n" "Change: %z\n":
+                                         "  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");
+                       }
+               }
+#endif
+       }
+       print_it(format, filename, print_stat, &statbuf USE_SELINUX(, scontext));
+#else  /* FEATURE_STAT_FORMAT */
+       if (option_mask32 & OPT_TERSE) {
+               printf("%s %ju %ju %lx %lu %lu %jx %ju %lu %lx %lx %lu %lu %lu %lu"
+                      SKIP_SELINUX("\n"),
+                      filename,
+                      (uintmax_t) (statbuf.st_size),
+                      (uintmax_t) statbuf.st_blocks,
+                      (unsigned long) statbuf.st_mode,
+                      (unsigned long) statbuf.st_uid,
+                      (unsigned long) statbuf.st_gid,
+                      (uintmax_t) statbuf.st_dev,
+                      (uintmax_t) statbuf.st_ino,
+                      (unsigned long) statbuf.st_nlink,
+                      (unsigned long) major(statbuf.st_rdev),
+                      (unsigned long) minor(statbuf.st_rdev),
+                      (unsigned long) statbuf.st_atime,
+                      (unsigned long) statbuf.st_mtime,
+                      (unsigned long) statbuf.st_ctime,
+                      (unsigned long) statbuf.st_blksize
+               );
+#if ENABLE_SELINUX
+               if (option_mask32 & OPT_SELINUX)
+                       printf(" %lc\n", *scontext);
+               else
+                       bb_putchar('\n');
+#endif
+       } 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 = xmalloc_readlink_or_warn(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) statbuf.st_blksize,
+                      file_type(&statbuf),
+                      (uintmax_t) statbuf.st_dev,
+                      (uintmax_t) statbuf.st_dev,
+                      (uintmax_t) statbuf.st_ino,
+                      (unsigned long) statbuf.st_nlink);
+               if (S_ISBLK(statbuf.st_mode) || S_ISCHR(statbuf.st_mode))
+                       printf(" Device type: %lx,%lx\n",
+                              (unsigned long) major(statbuf.st_rdev),
+                              (unsigned long) minor(statbuf.st_rdev));
+               else
+                       bb_putchar('\n');
+               printf("Access: (%04lo/%10.10s)  Uid: (%5lu/%8s)   Gid: (%5lu/%8s)\n",
+                      (unsigned long) (statbuf.st_mode & (S_ISUID|S_ISGID|S_ISVTX|S_IRWXU|S_IRWXG|S_IRWXO)),
+                      bb_mode_string(statbuf.st_mode),
+                      (unsigned long) statbuf.st_uid,
+                      (pw_ent != NULL) ? pw_ent->pw_name : "UNKNOWN",
+                      (unsigned long) statbuf.st_gid,
+                      (gw_ent != NULL) ? gw_ent->gr_name : "UNKNOWN");
+#if ENABLE_SELINUX
+               printf("   S_Context: %lc\n", *scontext);
+#endif
+               printf("Access: %s\n" "Modify: %s\n" "Change: %s\n",
+                      human_time(statbuf.st_atime),
+                      human_time(statbuf.st_mtime),
+                      human_time(statbuf.st_ctime));
+       }
+#endif /* FEATURE_STAT_FORMAT */
+       return 1;
+}
+
+int stat_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int stat_main(int argc, char **argv)
+{
+       USE_FEATURE_STAT_FORMAT(char *format = NULL;)
+       int i;
+       int ok = 1;
+       statfunc_ptr statfunc = do_stat;
+
+       getopt32(argv, "ftL"
+               USE_SELINUX("Z")
+               USE_FEATURE_STAT_FORMAT("c:", &format)
+       );
+
+       if (option_mask32 & OPT_FILESYS) /* -f */
+               statfunc = do_statfs;
+       if (argc == optind)           /* files */
+               bb_show_usage();
+
+#if ENABLE_SELINUX
+       if (option_mask32 & OPT_SELINUX) {
+               selinux_or_die();
+       }
+#endif /* ENABLE_SELINUX */
+       for (i = optind; i < argc; ++i)
+               ok &= statfunc(argv[i] USE_FEATURE_STAT_FORMAT(, format));
+
+       return (ok ? EXIT_SUCCESS : EXIT_FAILURE);
+}
diff --git a/coreutils/stty.c b/coreutils/stty.c
new file mode 100644 (file)
index 0000000..3605e3c
--- /dev/null
@@ -0,0 +1,1439 @@
+/* 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 <djm@gnu.ai.mit.edu>
+
+   Special for busybox ported by Vladimir Oleynik <dzo@simtreas.ru> 2001
+
+   */
+
+#include "libbb.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
+};
+
+/* 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.
+ * This structure should be kept as small as humanly possible.
+ */
+struct mode_info {
+       const uint8_t type;           /* Which structure element to change    */
+       const uint8_t flags;          /* Setting and display options          */
+       /* only these values are ever used, so... */
+#if   (CSIZE | NLDLY | CRDLY | TABDLY | BSDLY | VTDLY | FFDLY) < 0x100
+       const uint8_t mask;
+#elif (CSIZE | NLDLY | CRDLY | TABDLY | BSDLY | VTDLY | FFDLY) < 0x10000
+       const uint16_t mask;
+#else
+       const tcflag_t mask;          /* Other bits to turn off for this mode */
+#endif
+       /* was using short here, but ppc32 was unhappy */
+       const tcflag_t bits;          /* Bits to set for this mode            */
+};
+
+enum {
+       /* Must match mode_name[] and mode_info[] order! */
+       IDX_evenp = 0,
+       IDX_parity,
+       IDX_oddp,
+       IDX_nl,
+       IDX_ek,
+       IDX_sane,
+       IDX_cooked,
+       IDX_raw,
+       IDX_pass8,
+       IDX_litout,
+       IDX_cbreak,
+       IDX_crt,
+       IDX_dec,
+#ifdef IXANY
+       IDX_decctlq,
+#endif
+#if defined(TABDLY) || defined(OXTABS)
+       IDX_tabs,
+#endif
+#if defined(XCASE) && defined(IUCLC) && defined(OLCUC)
+       IDX_lcase,
+       IDX_LCASE,
+#endif
+};
+
+#define MI_ENTRY(N,T,F,B,M) N "\0"
+
+/* Mode names given on command line */
+static const char mode_name[] =
+       MI_ENTRY("evenp",    combination, REV        | OMIT, 0,          0 )
+       MI_ENTRY("parity",   combination, REV        | OMIT, 0,          0 )
+       MI_ENTRY("oddp",     combination, REV        | OMIT, 0,          0 )
+       MI_ENTRY("nl",       combination, REV        | OMIT, 0,          0 )
+       MI_ENTRY("ek",       combination, OMIT,              0,          0 )
+       MI_ENTRY("sane",     combination, OMIT,              0,          0 )
+       MI_ENTRY("cooked",   combination, REV        | OMIT, 0,          0 )
+       MI_ENTRY("raw",      combination, REV        | OMIT, 0,          0 )
+       MI_ENTRY("pass8",    combination, REV        | OMIT, 0,          0 )
+       MI_ENTRY("litout",   combination, REV        | OMIT, 0,          0 )
+       MI_ENTRY("cbreak",   combination, REV        | OMIT, 0,          0 )
+       MI_ENTRY("crt",      combination, OMIT,              0,          0 )
+       MI_ENTRY("dec",      combination, OMIT,              0,          0 )
+#ifdef IXANY
+       MI_ENTRY("decctlq",  combination, REV        | OMIT, 0,          0 )
+#endif
+#if defined(TABDLY) || defined(OXTABS)
+       MI_ENTRY("tabs",     combination, REV        | OMIT, 0,          0 )
+#endif
+#if defined(XCASE) && defined(IUCLC) && defined(OLCUC)
+       MI_ENTRY("lcase",    combination, REV        | OMIT, 0,          0 )
+       MI_ENTRY("LCASE",    combination, REV        | OMIT, 0,          0 )
+#endif
+       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
+       ;
+
+#undef MI_ENTRY
+#define MI_ENTRY(N,T,F,B,M) { T, F, M, B },
+
+static const struct mode_info mode_info[] = {
+       /* This should be verbatim cut-n-paste copy of the above MI_ENTRYs */
+       MI_ENTRY("evenp",    combination, REV        | OMIT, 0,          0 )
+       MI_ENTRY("parity",   combination, REV        | OMIT, 0,          0 )
+       MI_ENTRY("oddp",     combination, REV        | OMIT, 0,          0 )
+       MI_ENTRY("nl",       combination, REV        | OMIT, 0,          0 )
+       MI_ENTRY("ek",       combination, OMIT,              0,          0 )
+       MI_ENTRY("sane",     combination, OMIT,              0,          0 )
+       MI_ENTRY("cooked",   combination, REV        | OMIT, 0,          0 )
+       MI_ENTRY("raw",      combination, REV        | OMIT, 0,          0 )
+       MI_ENTRY("pass8",    combination, REV        | OMIT, 0,          0 )
+       MI_ENTRY("litout",   combination, REV        | OMIT, 0,          0 )
+       MI_ENTRY("cbreak",   combination, REV        | OMIT, 0,          0 )
+       MI_ENTRY("crt",      combination, OMIT,              0,          0 )
+       MI_ENTRY("dec",      combination, OMIT,              0,          0 )
+#ifdef IXANY
+       MI_ENTRY("decctlq",  combination, REV        | OMIT, 0,          0 )
+#endif
+#if defined(TABDLY) || defined(OXTABS)
+       MI_ENTRY("tabs",     combination, REV        | OMIT, 0,          0 )
+#endif
+#if defined(XCASE) && defined(IUCLC) && defined(OLCUC)
+       MI_ENTRY("lcase",    combination, REV        | OMIT, 0,          0 )
+       MI_ENTRY("LCASE",    combination, REV        | OMIT, 0,          0 )
+#endif
+       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
+};
+
+enum {
+       NUM_mode_info = ARRAY_SIZE(mode_info)
+};
+
+
+/* Control characters */
+struct control_info {
+       const uint8_t saneval;  /* Value to set for 'stty sane' */
+       const uint8_t offset;   /* Offset in c_cc */
+};
+
+enum {
+       /* Must match control_name[] and control_info[] order! */
+       CIDX_intr = 0,
+       CIDX_quit,
+       CIDX_erase,
+       CIDX_kill,
+       CIDX_eof,
+       CIDX_eol,
+#ifdef VEOL2
+       CIDX_eol2,
+#endif
+#ifdef VSWTCH
+       CIDX_swtch,
+#endif
+       CIDX_start,
+       CIDX_stop,
+       CIDX_susp,
+#ifdef VDSUSP
+       CIDX_dsusp,
+#endif
+#ifdef VREPRINT
+       CIDX_rprnt,
+#endif
+#ifdef VWERASE
+       CIDX_werase,
+#endif
+#ifdef VLNEXT
+       CIDX_lnext,
+#endif
+#ifdef VFLUSHO
+       CIDX_flush,
+#endif
+#ifdef VSTATUS
+       CIDX_status,
+#endif
+       CIDX_min,
+       CIDX_time,
+};
+
+#define CI_ENTRY(n,s,o) n "\0"
+
+/* Name given on command line */
+static const char control_name[] =
+       CI_ENTRY("intr",     CINTR,   VINTR   )
+       CI_ENTRY("quit",     CQUIT,   VQUIT   )
+       CI_ENTRY("erase",    CERASE,  VERASE  )
+       CI_ENTRY("kill",     CKILL,   VKILL   )
+       CI_ENTRY("eof",      CEOF,    VEOF    )
+       CI_ENTRY("eol",      CEOL,    VEOL    )
+#ifdef VEOL2
+       CI_ENTRY("eol2",     CEOL2,   VEOL2   )
+#endif
+#ifdef VSWTCH
+       CI_ENTRY("swtch",    CSWTCH,  VSWTCH  )
+#endif
+       CI_ENTRY("start",    CSTART,  VSTART  )
+       CI_ENTRY("stop",     CSTOP,   VSTOP   )
+       CI_ENTRY("susp",     CSUSP,   VSUSP   )
+#ifdef VDSUSP
+       CI_ENTRY("dsusp",    CDSUSP,  VDSUSP  )
+#endif
+#ifdef VREPRINT
+       CI_ENTRY("rprnt",    CRPRNT,  VREPRINT)
+#endif
+#ifdef VWERASE
+       CI_ENTRY("werase",   CWERASE, VWERASE )
+#endif
+#ifdef VLNEXT
+       CI_ENTRY("lnext",    CLNEXT,  VLNEXT  )
+#endif
+#ifdef VFLUSHO
+       CI_ENTRY("flush",    CFLUSHO, VFLUSHO )
+#endif
+#ifdef VSTATUS
+       CI_ENTRY("status",   CSTATUS, VSTATUS )
+#endif
+       /* These must be last because of the display routines */
+       CI_ENTRY("min",      1,       VMIN    )
+       CI_ENTRY("time",     0,       VTIME   )
+       ;
+
+#undef CI_ENTRY
+#define CI_ENTRY(n,s,o) { s, o },
+
+static const struct control_info control_info[] = {
+       /* This should be verbatim cut-n-paste copy of the above CI_ENTRYs */
+       CI_ENTRY("intr",     CINTR,   VINTR   )
+       CI_ENTRY("quit",     CQUIT,   VQUIT   )
+       CI_ENTRY("erase",    CERASE,  VERASE  )
+       CI_ENTRY("kill",     CKILL,   VKILL   )
+       CI_ENTRY("eof",      CEOF,    VEOF    )
+       CI_ENTRY("eol",      CEOL,    VEOL    )
+#ifdef VEOL2
+       CI_ENTRY("eol2",     CEOL2,   VEOL2   )
+#endif
+#ifdef VSWTCH
+       CI_ENTRY("swtch",    CSWTCH,  VSWTCH  )
+#endif
+       CI_ENTRY("start",    CSTART,  VSTART  )
+       CI_ENTRY("stop",     CSTOP,   VSTOP   )
+       CI_ENTRY("susp",     CSUSP,   VSUSP   )
+#ifdef VDSUSP
+       CI_ENTRY("dsusp",    CDSUSP,  VDSUSP  )
+#endif
+#ifdef VREPRINT
+       CI_ENTRY("rprnt",    CRPRNT,  VREPRINT)
+#endif
+#ifdef VWERASE
+       CI_ENTRY("werase",   CWERASE, VWERASE )
+#endif
+#ifdef VLNEXT
+       CI_ENTRY("lnext",    CLNEXT,  VLNEXT  )
+#endif
+#ifdef VFLUSHO
+       CI_ENTRY("flush",    CFLUSHO, VFLUSHO )
+#endif
+#ifdef VSTATUS
+       CI_ENTRY("status",   CSTATUS, VSTATUS )
+#endif
+       /* These must be last because of the display routines */
+       CI_ENTRY("min",      1,       VMIN    )
+       CI_ENTRY("time",     0,       VTIME   )
+};
+
+enum {
+       NUM_control_info = ARRAY_SIZE(control_info)
+};
+
+
+struct globals {
+       const char *device_name; // = bb_msg_standard_input;
+       /* The width of the screen, for output wrapping */
+       unsigned max_col; // = 80;
+       /* Current position, to know when to wrap */
+       unsigned current_col;
+       char buf[10];
+};
+#define G (*(struct globals*)&bb_common_bufsiz1)
+#define INIT_G() do { \
+       G.device_name = bb_msg_standard_input; \
+       G.max_col = 80; \
+} while (0)
+
+
+/* Return a string that is the printable representation of character CH */
+/* Adapted from 'cat' by Torbjorn Granlund */
+static const char *visible(unsigned ch)
+{
+       char *bpout = G.buf;
+
+       if (ch == _POSIX_VDISABLE)
+               return "<undef>";
+
+       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 G.buf;
+}
+
+static tcflag_t *mode_type_flag(unsigned type, const struct termios *mode)
+{
+       static const uint8_t tcflag_offsets[] ALIGN1 = {
+               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 void set_speed_or_die(enum speed_setting type, const char *const arg,
+                                       struct termios * const mode)
+{
+       speed_t baud;
+
+       baud = tty_value_to_baud(xatou(arg));
+
+       if (type != output_speed) {     /* either input or both */
+               cfsetispeed(mode, baud);
+       }
+       if (type != input_speed) {      /* either output or both */
+               cfsetospeed(mode, baud);
+       }
+}
+
+static NORETURN void perror_on_device_and_die(const char *fmt)
+{
+       bb_perror_msg_and_die(fmt, G.device_name);
+}
+
+static void perror_on_device(const char *fmt)
+{
+       bb_perror_msg(fmt, G.device_name);
+}
+
+/* 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;
+       unsigned buflen;
+
+       va_start(args, message);
+       buflen = vsnprintf(buf, sizeof(buf), message, args);
+       va_end(args);
+       /* We seem to be called only with suitable lengths, but check if
+          somebody failed to adhere to this assumption just to be sure.  */
+       if (!buflen || buflen >= sizeof(buf)) return;
+
+       if (G.current_col > 0) {
+               G.current_col++;
+               if (buf[0] != '\n') {
+                       if (G.current_col + buflen >= G.max_col) {
+                               bb_putchar('\n');
+                               G.current_col = 0;
+                       } else
+                               bb_putchar(' ');
+               }
+       }
+       fputs(buf, stdout);
+       G.current_col += buflen;
+       if (buf[buflen-1] == '\n')
+               G.current_col = 0;
+}
+
+static void set_window_size(const int rows, const int cols)
+{
+       struct winsize win = { 0, 0, 0, 0 };
+
+       if (ioctl(STDIN_FILENO, TIOCGWINSZ, &win)) {
+               if (errno != EINVAL) {
+                       goto bail;
+               }
+               memset(&win, 0, sizeof(win));
+       }
+
+       if (rows >= 0)
+               win.ws_row = rows;
+       if (cols >= 0)
+               win.ws_col = cols;
+
+       if (ioctl(STDIN_FILENO, TIOCSWINSZ, (char *) &win))
+bail:
+               perror_on_device("%s");
+}
+
+static void display_window_size(const int fancy)
+{
+       const char *fmt_str = "%s\0%s: no size information for this device";
+       unsigned width, height;
+
+       if (get_terminal_width_height(STDIN_FILENO, &width, &height)) {
+               if ((errno != EINVAL) || ((fmt_str += 2), !fancy)) {
+                       perror_on_device(fmt_str);
+               }
+       } else {
+               wrapf(fancy ? "rows %d; columns %d;" : "%d %d\n",
+                               height, width);
+       }
+}
+
+static const struct suffix_mult stty_suffixes[] = {
+       { "b",  512 },
+       { "k", 1024 },
+       { "B", 1024 },
+       { }
+};
+
+static const struct mode_info *find_mode(const char *name)
+{
+       int i = index_in_strings(mode_name, name);
+       return i >= 0 ? &mode_info[i] : NULL;
+}
+
+static const struct control_info *find_control(const char *name)
+{
+       int i = index_in_strings(control_name, name);
+       return i >= 0 ? &control_info[i] : NULL;
+}
+
+enum {
+       param_need_arg = 0x80,
+       param_line    = 1 | 0x80,
+       param_rows    = 2 | 0x80,
+       param_cols    = 3 | 0x80,
+       param_columns = 4 | 0x80,
+       param_size    = 5,
+       param_speed   = 6,
+       param_ispeed  = 7 | 0x80,
+       param_ospeed  = 8 | 0x80,
+};
+
+static int find_param(const char *const name)
+{
+       static const char params[] ALIGN1 =
+               "line\0"    /* 1 */
+               "rows\0"    /* 2 */
+               "cols\0"    /* 3 */
+               "columns\0" /* 4 */
+               "size\0"    /* 5 */
+               "speed\0"   /* 6 */
+               "ispeed\0"
+               "ospeed\0";
+       int i = index_in_strings(params, name) + 1;
+       if (i == 0)
+               return 0;
+       if (i != 5 && i != 6)
+               i |= 0x80;
+       return i;
+}
+
+static int recover_mode(const char *arg, struct termios *mode)
+{
+       int i, n;
+       unsigned 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 display_recoverable(const struct termios *mode,
+                               int UNUSED_PARAM dummy)
+{
+       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]);
+       bb_putchar('\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 do_display(const struct termios *mode, const int all)
+{
+       int i;
+       tcflag_t *bitsp;
+       unsigned long mask;
+       int prev_type = control;
+
+       display_speed(mode, 1);
+       if (all)
+               display_window_size(1);
+#ifdef HAVE_C_LINE
+       wrapf("line = %d;\n", mode->c_line);
+#else
+       wrapf("\n");
+#endif
+
+       for (i = 0; i != CIDX_min; ++i) {
+               /* If swtch is the same as susp, don't print both */
+#if VSWTCH == VSUSP
+               if (i == CIDX_swtch)
+                       continue;
+#endif
+               /* If eof uses the same slot as min, only print whichever applies */
+#if VEOF == VMIN
+               if ((mode->c_lflag & ICANON) == 0
+                && (i == CIDX_eof || i == CIDX_eol)
+               ) {
+                       continue;
+               }
+#endif
+               wrapf("%s = %s;", nth_string(control_name, i),
+                         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 (G.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"); */
+                       if (G.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 (all || (mode_info[i].flags & SANE_UNSET))
+                               wrapf("-%s"+1, nth_string(mode_name, i));
+               } else {
+                       if ((all && mode_info[i].flags & REV)
+                        || (!all && (mode_info[i].flags & (SANE_SET | REV)) == (SANE_SET | REV))
+                       ) {
+                               wrapf("-%s", nth_string(mode_name, i));
+                       }
+               }
+       }
+       if (G.current_col) wrapf("\n");
+}
+
+static void sane_mode(struct termios *mode)
+{
+       int i;
+       tcflag_t *bitsp;
+
+       for (i = 0; i < NUM_control_info; ++i) {
+#if VMIN == VEOF
+               if (i == CIDX_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;
+               }
+       }
+}
+
+/* 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 & ~info->mask & ~info->bits;
+               else
+                       *bitsp = (*bitsp & ~info->mask) | info->bits;
+               return;
+       }
+
+       /* Combination mode */
+       if (info == &mode_info[IDX_evenp] || info == &mode_info[IDX_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 == &mode_info[IDX_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 == &mode_info[IDX_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 == &mode_info[IDX_ek]) {
+               mode->c_cc[VERASE] = CERASE;
+               mode->c_cc[VKILL] = CKILL;
+       } else if (info == &mode_info[IDX_sane]) {
+               sane_mode(mode);
+       } else if (info == &mode_info[IDX_cbreak]) {
+               if (reversed)
+                       mode->c_lflag |= ICANON;
+               else
+                       mode->c_lflag &= ~ICANON;
+       } else if (info == &mode_info[IDX_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 == &mode_info[IDX_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 == &mode_info[IDX_raw] || info == &mode_info[IDX_cooked]) {
+               if ((info == &mode_info[IDX_raw] && reversed)
+                || (info == &mode_info[IDX_cooked] && !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 == &mode_info[IDX_decctlq]) {
+               if (reversed)
+                       mode->c_iflag |= IXANY;
+               else
+                       mode->c_iflag &= ~IXANY;
+       }
+       else if (TABDLY && info == &mode_info[IDX_tabs]) {
+               if (reversed)
+                       mode->c_oflag = (mode->c_oflag & ~TABDLY) | TAB3;
+               else
+                       mode->c_oflag = (mode->c_oflag & ~TABDLY) | TAB0;
+       }
+       else if (OXTABS && info == &mode_info[IDX_tabs]) {
+               if (reversed)
+                       mode->c_oflag |= OXTABS;
+               else
+                       mode->c_oflag &= ~OXTABS;
+       } else
+       if (XCASE && IUCLC && OLCUC
+        && (info == &mode_info[IDX_lcase] || info == &mode_info[IDX_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 == &mode_info[IDX_crt]) {
+               mode->c_lflag |= ECHOE | ECHOCTL | ECHOKE;
+       } else if (info == &mode_info[IDX_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 == &control_info[CIDX_min] || info == &control_info[CIDX_time])
+               value = xatoul_range_sfx(arg, 0, 0xff, stty_suffixes);
+       else if (arg[0] == '\0' || arg[1] == '\0')
+               value = arg[0];
+       else if (!strcmp(arg, "^-") || !strcmp(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;
+}
+
+#define STTY_require_set_attr   (1 << 0)
+#define STTY_speed_was_set      (1 << 1)
+#define STTY_verbose_output     (1 << 2)
+#define STTY_recoverable_output (1 << 3)
+#define STTY_noargs             (1 << 4)
+
+int stty_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int stty_main(int argc, char **argv)
+{
+       struct termios mode;
+       void (*output_func)(const struct termios *, const int);
+       const char *file_name = NULL;
+       int display_all = 0;
+       int stty_state;
+       int k;
+
+       INIT_G();
+
+       stty_state = STTY_noargs;
+       output_func = do_display;
+
+       /* 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))
+                                       goto invalid_argument;
+                               stty_state &= ~STTY_noargs;
+                               continue;
+                       }
+                       /* It is an option - parse it */
+                       i = 0;
+                       while (arg[++i]) {
+                               switch (arg[i]) {
+                               case 'a':
+                                       stty_state |= STTY_verbose_output;
+                                       output_func = do_display;
+                                       display_all = 1;
+                                       break;
+                               case 'g':
+                                       stty_state |= STTY_recoverable_output;
+                                       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:
+                                       goto invalid_argument;
+                               }
+                       }
+ end_option:
+                       continue;
+               }
+
+               mp = find_mode(arg);
+               if (mp) {
+                       stty_state &= ~STTY_noargs;
+                       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);
+                       stty_state &= ~STTY_noargs;
+                       ++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:
+               case param_columns:
+                       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 (tty_value_to_baud(xatou(arg)) != (speed_t) -1) break;
+ invalid_argument:
+                       bb_error_msg_and_die("invalid argument '%s'", arg);
+               }
+               stty_state &= ~STTY_noargs;
+       }
+
+       /* Specifying both -a and -g is an error */
+       if ((stty_state & (STTY_verbose_output | STTY_recoverable_output)) ==
+               (STTY_verbose_output | STTY_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 (!(stty_state & STTY_noargs) &&
+               (stty_state & (STTY_verbose_output | STTY_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;
+               G.device_name = file_name;
+               fd = xopen(G.device_name, O_RDONLY | O_NONBLOCK);
+               if (fd != STDIN_FILENO) {
+                       dup2(fd, STDIN_FILENO);
+                       close(fd);
+               }
+               fdflags = fcntl(STDIN_FILENO, F_GETFL);
+               if (fdflags < 0 ||
+                       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 (stty_state & (STTY_verbose_output | STTY_recoverable_output | STTY_noargs)) {
+               get_terminal_width_height(STDOUT_FILENO, &G.max_col, NULL);
+               output_func(&mode, display_all);
+               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);
+                               stty_state |= STTY_require_set_attr;
+                       }
+                       /* It is an option - already parsed. Skip it */
+                       continue;
+               }
+
+               mp = find_mode(arg);
+               if (mp) {
+                       set_mode(mp, 0 /* non-reversed */, &mode);
+                       stty_state |= STTY_require_set_attr;
+                       continue;
+               }
+
+               cp = find_control(arg);
+               if (cp) {
+                       ++k;
+                       set_control_char_or_die(cp, argnext, &mode);
+                       stty_state |= STTY_require_set_attr;
+                       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);
+                       stty_state |= STTY_require_set_attr;
+                       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);
+                       stty_state |= (STTY_require_set_attr | STTY_speed_was_set);
+                       break;
+               case param_ospeed:
+                       set_speed_or_die(output_speed, argnext, &mode);
+                       stty_state |= (STTY_require_set_attr | STTY_speed_was_set);
+                       break;
+               default:
+                       if (recover_mode(arg, &mode) == 1)
+                               stty_state |= STTY_require_set_attr;
+                       else /* true: if (tty_value_to_baud(xatou(arg)) != (speed_t) -1) */{
+                               set_speed_or_die(both_speeds, arg, &mode);
+                               stty_state |= (STTY_require_set_attr | STTY_speed_was_set);
+                       } /* else - impossible (caught in the first pass):
+                               bb_error_msg_and_die("invalid argument '%s'", arg); */
+               }
+       }
+
+       if (stty_state & STTY_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 ((stty_state & STTY_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;
+}
diff --git a/coreutils/sum.c b/coreutils/sum.c
new file mode 100644 (file)
index 0000000..60f3b30
--- /dev/null
@@ -0,0 +1,99 @@
+/* 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 <andersen@codepoet.org>
+ * Copyright (C) 2005 by Mike Frysinger <vapier@gentoo.org>
+ *
+ * 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 "libbb.h"
+
+enum { SUM_BSD, PRINT_NAME, SUM_SYSV };
+
+/* BSD: calculate and print the rotated checksum and the size in 1K blocks
+   The checksum varies depending on sizeof (int). */
+/* SYSV: calculate and print the checksum and the size in 512-byte blocks */
+/* Return 1 if successful.  */
+static unsigned sum_file(const char *file, unsigned type)
+{
+#define buf bb_common_bufsiz1
+       unsigned long long total_bytes = 0;
+       int fd, r;
+       /* The sum of all the input bytes, modulo (UINT_MAX + 1).  */
+       unsigned s = 0;
+
+       fd = open_or_warn_stdin(file);
+       if (fd == -1)
+               return 0;
+
+       while (1) {
+               size_t bytes_read = safe_read(fd, buf, BUFSIZ);
+
+               if ((ssize_t)bytes_read <= 0) {
+                       r = (fd && close(fd) != 0);
+                       if (!bytes_read && !r)
+                               /* no error */
+                               break;
+                       bb_perror_msg(file);
+                       return 0;
+               }
+
+               total_bytes += bytes_read;
+               if (type >= SUM_SYSV) {
+                       do s += buf[--bytes_read]; while (bytes_read);
+               } else {
+                       r = 0;
+                       do {
+                               s = (s >> 1) + ((s & 1) << 15);
+                               s += buf[r++];
+                               s &= 0xffff; /* Keep it within bounds. */
+                       } while (--bytes_read);
+               }
+       }
+
+       if (type < PRINT_NAME)
+               file = "";
+       if (type >= SUM_SYSV) {
+               r = (s & 0xffff) + ((s & 0xffffffff) >> 16);
+               s = (r & 0xffff) + (r >> 16);
+               printf("%d %llu %s\n", s, (total_bytes + 511) / 512, file);
+       } else
+               printf("%05d %5llu %s\n", s, (total_bytes + 1023) / 1024, file);
+       return 1;
+#undef buf
+}
+
+int sum_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int sum_main(int argc UNUSED_PARAM, char **argv)
+{
+       unsigned n;
+       unsigned type = SUM_BSD;
+
+       n = getopt32(argv, "sr");
+       argv += optind;
+       if (n & 1) type = SUM_SYSV;
+       /* give the bsd priority over sysv func */
+       if (n & 2) type = SUM_BSD;
+
+       if (!argv[0]) {
+               /* Do not print the name */
+               n = sum_file("-", type);
+       } else {
+               /* Need to print the name if either
+                  - more than one file given
+                  - doing sysv */
+               type += (argv[1] || type == SUM_SYSV);
+               n = 1;
+               do {
+                       n &= sum_file(*argv, type);
+               } while (*++argv);
+       }
+       return !n;
+}
diff --git a/coreutils/sync.c b/coreutils/sync.c
new file mode 100644 (file)
index 0000000..f00a3d0
--- /dev/null
@@ -0,0 +1,25 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Mini sync implementation for busybox
+ *
+ * Copyright (C) 1995, 1996 by Bruce Perens <bruce@pixar.com>.
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+/* BB_AUDIT SUSv3 N/A -- Matches GNU behavior. */
+
+#include "libbb.h"
+
+/* This is a NOFORK applet. Be very careful! */
+
+int sync_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int sync_main(int argc, char **argv UNUSED_PARAM)
+{
+       /* coreutils-6.9 compat */
+       bb_warn_ignoring_args(argc - 1);
+
+       sync();
+
+       return EXIT_SUCCESS;
+}
diff --git a/coreutils/tac.c b/coreutils/tac.c
new file mode 100644 (file)
index 0000000..d70e23a
--- /dev/null
@@ -0,0 +1,106 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * tac implementation for busybox
+ *
+ * Copyright (C) 2003  Yang Xiaopeng  <yxp at hanwang.com.cn>
+ * Copyright (C) 2007  Natanael Copa  <natanael.copa@gmail.com>
+ * Copyright (C) 2007  Tito Ragusa    <farmatito@tiscali.it>
+ *
+ * Licensed under GPLv2, see file License in this tarball for details.
+ *
+ */
+
+/* tac - concatenate and print files in reverse */
+
+/* Based on Yang Xiaopeng's (yxp at hanwang.com.cn) patch
+ * http://www.uclibc.org/lists/busybox/2003-July/008813.html
+ */
+
+#include "libbb.h"
+
+/* This is a NOEXEC applet. Be very careful! */
+
+struct lstring {
+       int size;
+       char buf[1];
+};
+
+int tac_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int tac_main(int argc UNUSED_PARAM, char **argv)
+{
+       char **name;
+       FILE *f;
+       struct lstring *line = NULL;
+       llist_t *list = NULL;
+       int retval = EXIT_SUCCESS;
+
+#if ENABLE_DESKTOP
+/* tac from coreutils 6.9 supports:
+       -b, --before
+              attach the separator before instead of after
+       -r, --regex
+              interpret the separator as a regular expression
+       -s, --separator=STRING
+              use STRING as the separator instead of newline
+We support none, but at least we will complain or handle "--":
+*/
+       getopt32(argv, "");
+       argv += optind;
+#else
+       argv++;
+#endif
+       if (!*argv)
+               *--argv = (char *)"-";
+       /* We will read from last file to first */
+       name = argv;
+       while (*name)
+               name++;
+
+       do {
+               int ch, i;
+
+               name--;
+               f = fopen_or_warn_stdin(*name);
+               if (f == NULL) {
+                       /* error message is printed by fopen_or_warn_stdin */
+                       retval = EXIT_FAILURE;
+                       continue;
+               }
+
+               errno = i = 0;
+               do {
+                       ch = fgetc(f);
+                       if (ch != EOF) {
+                               if (!(i & 0x7f))
+                                       /* Grow on every 128th char */
+                                       line = xrealloc(line, i + 0x7f + sizeof(int) + 1);
+                               line->buf[i++] = ch;
+                       }
+                       if (ch == '\n' || (ch == EOF && i != 0)) {
+                               line = xrealloc(line, i + sizeof(int));
+                               line->size = i;
+                               llist_add_to(&list, line);
+                               line = NULL;
+                               i = 0;
+                       }
+               } while (ch != EOF);
+               /* fgetc sets errno to ENOENT on EOF, we don't want
+                * to warn on this non-error! */
+               if (errno && errno != ENOENT) {
+                       bb_simple_perror_msg(*name);
+                       retval = EXIT_FAILURE;
+               }
+       } while (name != argv);
+
+       while (list) {
+               line = (struct lstring *)list->data;
+               xwrite(STDOUT_FILENO, line->buf, line->size);
+               if (ENABLE_FEATURE_CLEAN_UP) {
+                       free(llist_pop(&list));
+               } else {
+                       list = list->link;
+               }
+       }
+
+       return retval;
+}
diff --git a/coreutils/tail.c b/coreutils/tail.c
new file mode 100644 (file)
index 0000000..5dae2d3
--- /dev/null
@@ -0,0 +1,284 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Mini tail implementation for busybox
+ *
+ * Copyright (C) 2001 by Matt Kraai <kraai@alumni.carnegiemellon.edu>
+ *
+ * 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 "libbb.h"
+
+static const struct suffix_mult tail_suffixes[] = {
+       { "b", 512 },
+       { "k", 1024 },
+       { "m", 1024*1024 },
+       { }
+};
+
+struct globals {
+       bool status;
+};
+#define G (*(struct globals*)&bb_common_bufsiz1)
+
+static void tail_xprint_header(const char *fmt, const char *filename)
+{
+       if (fdprintf(STDOUT_FILENO, fmt, filename) < 0)
+               bb_perror_nomsg_and_die();
+}
+
+static ssize_t tail_read(int fd, char *buf, size_t count)
+{
+       ssize_t r;
+       off_t current;
+       struct stat sbuf;
+
+       /* (A good comment is missing here) */
+       current = lseek(fd, 0, SEEK_CUR);
+       /* /proc files report zero st_size, don't lseek them. */
+       if (fstat(fd, &sbuf) == 0 && sbuf.st_size)
+               if (sbuf.st_size < current)
+                       lseek(fd, 0, SEEK_SET);
+
+       r = full_read(fd, buf, count);
+       if (r < 0) {
+               bb_perror_msg(bb_msg_read_error);
+               G.status = EXIT_FAILURE;
+       }
+
+       return r;
+}
+
+static const char header_fmt[] ALIGN1 = "\n==> %s <==\n";
+
+static unsigned eat_num(const char *p)
+{
+       if (*p == '-')
+               p++;
+       else if (*p == '+') {
+               p++;
+               G.status = 1; /* mark that we saw "+" */
+       }
+       return xatou_sfx(p, tail_suffixes);
+}
+
+int tail_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int tail_main(int argc, char **argv)
+{
+       unsigned count = 10;
+       unsigned sleep_period = 1;
+       bool from_top;
+       int header_threshhold = 1;
+       const char *str_c, *str_n;
+
+       char *tailbuf;
+       size_t tailbufsize;
+       int taillen = 0;
+       int newlines_seen = 0;
+       int nfiles, nread, nwrite, i, opt;
+       unsigned seen;
+
+       int *fds;
+       char *s, *buf;
+       const char *fmt;
+
+#if ENABLE_INCLUDE_SUSv2 || ENABLE_FEATURE_FANCY_TAIL
+       /* Allow legacy syntax of an initial numeric option without -n. */
+       if (argv[1] && (argv[1][0] == '+' || argv[1][0] == '-')
+        && isdigit(argv[1][1])
+       ) {
+               count = eat_num(argv[1]);
+               argv++;
+               argc--;
+       }
+#endif
+
+       USE_FEATURE_FANCY_TAIL(opt_complementary = "s+";) /* -s N */
+       opt = getopt32(argv, "fc:n:" USE_FEATURE_FANCY_TAIL("qs:v"),
+                       &str_c, &str_n USE_FEATURE_FANCY_TAIL(,&sleep_period));
+#define FOLLOW (opt & 0x1)
+#define COUNT_BYTES (opt & 0x2)
+       //if (opt & 0x1) // -f
+       if (opt & 0x2) count = eat_num(str_c); // -c
+       if (opt & 0x4) count = eat_num(str_n); // -n
+#if ENABLE_FEATURE_FANCY_TAIL
+       if (opt & 0x8) header_threshhold = INT_MAX; // -q
+       if (opt & 0x20) header_threshhold = 0; // -v
+#endif
+       argc -= optind;
+       argv += optind;
+       from_top = G.status; /* 1 if there was "-c +N" or "-n +N" */
+       G.status = EXIT_SUCCESS;
+
+       /* open all the files */
+       fds = xmalloc(sizeof(int) * (argc + 1));
+       if (!argv[0]) {
+               struct stat statbuf;
+
+               if (!fstat(STDIN_FILENO, &statbuf) && S_ISFIFO(statbuf.st_mode)) {
+                       opt &= ~1; /* clear FOLLOW */
+               }
+               *argv = (char *) bb_msg_standard_input;
+       }
+       nfiles = i = 0;
+       do {
+               int fd = open_or_warn_stdin(argv[i]);
+               if (fd < 0) {
+                       G.status = EXIT_FAILURE;
+                       continue;
+               }
+               fds[nfiles] = fd;
+               argv[nfiles++] = argv[i];
+       } while (++i < argc);
+
+       if (!nfiles)
+               bb_error_msg_and_die("no files");
+
+       /* prepare the buffer */
+       tailbufsize = BUFSIZ;
+       if (!from_top && COUNT_BYTES) {
+               if (tailbufsize < count + BUFSIZ) {
+                       tailbufsize = count + BUFSIZ;
+               }
+       }
+       tailbuf = xmalloc(tailbufsize);
+
+       /* tail the files */
+       fmt = header_fmt + 1;   /* Skip header leading newline on first output. */
+       i = 0;
+       do {
+               if (nfiles > header_threshhold) {
+                       tail_xprint_header(fmt, argv[i]);
+                       fmt = header_fmt;
+               }
+
+               /* Optimizing count-bytes case if the file is seekable.
+                * Beware of backing up too far.
+                * Also we exclude files with size 0 (because of /proc/xxx) */
+               if (COUNT_BYTES && !from_top) {
+                       off_t current = lseek(fds[i], 0, SEEK_END);
+                       if (current > 0) {
+                               if (count == 0)
+                                       continue; /* showing zero lines is easy :) */
+                               current -= count;
+                               if (current < 0)
+                                       current = 0;
+                               xlseek(fds[i], current, SEEK_SET);
+                               bb_copyfd_size(fds[i], STDOUT_FILENO, count);
+                               continue;
+                       }
+               }
+
+               buf = tailbuf;
+               taillen = 0;
+               seen = 1;
+               newlines_seen = 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);
+                                       }
+                               }
+                               xwrite(STDOUT_FILENO, buf + nread - nwrite, nwrite);
+                       } else if (count) {
+                               if (COUNT_BYTES) {
+                                       taillen += nread;
+                                       if (taillen > (int)count) {
+                                               memmove(tailbuf, tailbuf + taillen - count, count);
+                                               taillen = count;
+                                       }
+                               } else {
+                                       int k = nread;
+                                       int newlines_in_buf = 0;
+
+                                       do { /* count '\n' in last read */
+                                               k--;
+                                               if (buf[k] == '\n') {
+                                                       newlines_in_buf++;
+                                               }
+                                       } while (k);
+
+                                       if (newlines_seen + newlines_in_buf < (int)count) {
+                                               newlines_seen += newlines_in_buf;
+                                               taillen += nread;
+                                       } else {
+                                               int extra = (buf[nread-1] != '\n');
+
+                                               k = newlines_seen + newlines_in_buf + extra - count;
+                                               s = tailbuf;
+                                               while (k) {
+                                                       if (*s == '\n') {
+                                                               k--;
+                                                       }
+                                                       s++;
+                                               }
+                                               taillen += nread - (s - tailbuf);
+                                               memmove(tailbuf, s, taillen);
+                                               newlines_seen = count - extra;
+                                       }
+                                       if (tailbufsize < (size_t)taillen + BUFSIZ) {
+                                               tailbufsize = taillen + BUFSIZ;
+                                               tailbuf = xrealloc(tailbuf, tailbufsize);
+                                       }
+                               }
+                               buf = tailbuf + taillen;
+                       }
+               } /* while (tail_read() > 0) */
+               if (!from_top) {
+                       xwrite(STDOUT_FILENO, tailbuf, taillen);
+               }
+       } while (++i < nfiles);
+
+       buf = xrealloc(tailbuf, BUFSIZ);
+
+       fmt = NULL;
+
+       if (FOLLOW) while (1) {
+               sleep(sleep_period);
+               i = 0;
+               do {
+                       if (nfiles > header_threshhold) {
+                               fmt = header_fmt;
+                       }
+                       while ((nread = tail_read(fds[i], buf, BUFSIZ)) > 0) {
+                               if (fmt) {
+                                       tail_xprint_header(fmt, argv[i]);
+                                       fmt = NULL;
+                               }
+                               xwrite(STDOUT_FILENO, buf, nread);
+                       }
+               } while (++i < nfiles);
+       }
+       if (ENABLE_FEATURE_CLEAN_UP) {
+               free(fds);
+       }
+       return G.status;
+}
diff --git a/coreutils/tee.c b/coreutils/tee.c
new file mode 100644 (file)
index 0000000..0f24246
--- /dev/null
@@ -0,0 +1,106 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * tee implementation for busybox
+ *
+ * Copyright (C) 2003  Manuel Novoa III  <mjn3@codepoet.org>
+ *
+ * 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 "libbb.h"
+
+int tee_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int tee_main(int argc, char **argv)
+{
+       const char *mode = "w\0a";
+       FILE **files;
+       FILE **fp;
+       char **names;
+       char **np;
+       char retval;
+//TODO: make unconditional
+#if ENABLE_FEATURE_TEE_USE_BLOCK_IO
+       ssize_t c;
+# define buf bb_common_bufsiz1
+#else
+       int c;
+#endif
+       retval = getopt32(argv, "ia");  /* 'a' must be 2nd */
+       argc -= optind;
+       argv += optind;
+
+       mode += (retval & 2);   /* Since 'a' is the 2nd option... */
+
+       if (retval & 1) {
+               signal(SIGINT, SIG_IGN); /* TODO - switch to sigaction. (why?) */
+       }
+       retval = EXIT_SUCCESS;
+       /* 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);
+
+       /* 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 = stdout;
+               if (NOT_LONE_DASH(*argv)) {
+                       *fp = fopen_or_warn(*argv, mode);
+                       if (*fp == NULL) {
+                               retval = EXIT_FAILURE;
+                               argv++;
+                               continue;
+                       }
+               }
+               *np = *argv++;
+ GOT_NEW_FILE:
+               setbuf(*fp, NULL);      /* tee must not buffer output. */
+               fp++;
+               np++;
+       } while (*argv);
+       /* names[0] will be filled later */
+
+#if ENABLE_FEATURE_TEE_USE_BLOCK_IO
+       while ((c = safe_read(STDIN_FILENO, buf, sizeof(buf))) > 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 (file)
index 0000000..ab7b416
--- /dev/null
@@ -0,0 +1,777 @@
+/* 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 <andersen@codepoet.org> to be used
+ *     in busybox.
+ *     modified by Bernhard Reutner-Fischer to be useable (i.e. a bit less bloaty).
+ *
+ * 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 "libbb.h"
+#include <setjmp.h>
+
+/* This is a NOFORK applet. Be very careful! */
+
+/* test_main() is called from shells, and we need to be extra careful here.
+ * This is true regardless of PREFER_APPLETS and STANDALONE_SHELL
+ * state. */
+
+
+/* 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 ::= <any legal UNIX file name>
+*/
+
+#define TEST_DEBUG 0
+
+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
+};
+#define is_int_op(a)      (((unsigned char)((a) - INTEQ)) <= 5)
+#define is_str_op(a)      (((unsigned char)((a) - STREZ)) <= 5)
+#define is_file_op(a)     (((unsigned char)((a) - FILNT)) <= 2)
+#define is_file_access(a) (((unsigned char)((a) - FILRD)) <= 2)
+#define is_file_type(a)   (((unsigned char)((a) - FILREG)) <= 5)
+#define is_file_bit(a)    (((unsigned char)((a) - FILSUID)) <= 2)
+
+#if TEST_DEBUG
+int depth;
+#define nest_msg(...) do { \
+       depth++; \
+       fprintf(stderr, "%*s", depth*2, ""); \
+       fprintf(stderr, __VA_ARGS__); \
+} while (0)
+#define unnest_msg(...) do { \
+       fprintf(stderr, "%*s", depth*2, ""); \
+       fprintf(stderr, __VA_ARGS__); \
+       depth--; \
+} while (0)
+#define dbg_msg(...) do { \
+       fprintf(stderr, "%*s", depth*2, ""); \
+       fprintf(stderr, __VA_ARGS__); \
+} while (0)
+#define unnest_msg_and_return(expr, ...) do { \
+       number_t __res = (expr); \
+       fprintf(stderr, "%*s", depth*2, ""); \
+       fprintf(stderr, __VA_ARGS__, res); \
+       depth--; \
+       return __res; \
+} while (0)
+static const char *const TOKSTR[] = {
+       "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"
+};
+#else
+#define nest_msg(...)   ((void)0)
+#define unnest_msg(...) ((void)0)
+#define dbg_msg(...)    ((void)0)
+#define unnest_msg_and_return(expr, ...) return expr
+#endif
+
+enum token_types {
+       UNOP,
+       BINOP,
+       BUNOP,
+       BBINOP,
+       PAREN
+};
+
+struct operator_t {
+       char op_text[4];
+       unsigned char op_num, op_type;
+};
+
+static const struct operator_t 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  },
+};
+
+
+#if ENABLE_FEATURE_TEST_64
+typedef int64_t number_t;
+#else
+typedef int number_t;
+#endif
+
+
+/* We try to minimize both static and stack usage. */
+struct test_statics {
+       char **args;
+       /* set only by check_operator(), either to bogus struct
+        * or points to matching operator_t struct. Never NULL. */
+       const struct operator_t *last_operator;
+       gid_t *group_array;
+       int ngroups;
+       jmp_buf leaving;
+};
+
+/* See test_ptr_hack.c */
+extern struct test_statics *const test_ptr_to_statics;
+
+#define S (*test_ptr_to_statics)
+#define args            (S.args         )
+#define last_operator   (S.last_operator)
+#define group_array     (S.group_array  )
+#define ngroups         (S.ngroups      )
+#define leaving         (S.leaving      )
+
+#define INIT_S() do { \
+       (*(struct test_statics**)&test_ptr_to_statics) = xzalloc(sizeof(S)); \
+       barrier(); \
+} while (0)
+#define DEINIT_S() do { \
+       free(test_ptr_to_statics); \
+} while (0)
+
+static number_t primary(enum token n);
+
+static void syntax(const char *op, const char *msg) NORETURN;
+static void syntax(const char *op, const char *msg)
+{
+       if (op && *op) {
+               bb_error_msg("%s: %s", op, msg);
+       } else {
+               bb_error_msg("%s: %s"+4, msg);
+       }
+       longjmp(leaving, 2);
+}
+
+/* atoi with error detection */
+//XXX: FIXME: duplicate of existing libbb function?
+static number_t getn(const char *s)
+{
+       char *p;
+#if ENABLE_FEATURE_TEST_64
+       long long r;
+#else
+       long r;
+#endif
+
+       errno = 0;
+#if ENABLE_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;
+}
+
+/* UNUSED
+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);
+}
+*/
+
+
+static enum token check_operator(char *s)
+{
+       static const struct operator_t no_op = {
+               .op_num = -1,
+               .op_type = -1
+       };
+       const struct operator_t *op;
+
+       last_operator = &no_op;
+       if (s == NULL) {
+               return EOI;
+       }
+
+       op = ops;
+       do {
+               if (strcmp(s, op->op_text) == 0) {
+                       last_operator = op;
+                       return op->op_num;
+               }
+               op++;
+       } while (op < ops + ARRAY_SIZE(ops));
+
+       return OPERAND;
+}
+
+
+static int binop(void)
+{
+       const char *opnd1, *opnd2;
+       const struct operator_t *op;
+       number_t val1, val2;
+
+       opnd1 = *args;
+       check_operator(*++args);
+       op = last_operator;
+
+       opnd2 = *++args;
+       if (opnd2 == NULL)
+               syntax(op->op_text, "argument expected");
+
+       if (is_int_op(op->op_num)) {
+               val1 = getn(opnd1);
+               val2 = getn(opnd2);
+               if (op->op_num == INTEQ)
+                       return val1 == val2;
+               if (op->op_num == INTNE)
+                       return val1 != val2;
+               if (op->op_num == INTGE)
+                       return val1 >= val2;
+               if (op->op_num == INTGT)
+                       return val1 >  val2;
+               if (op->op_num == INTLE)
+                       return val1 <= val2;
+               if (op->op_num == INTLT)
+                       return val1 <  val2;
+       }
+       if (is_str_op(op->op_num)) {
+               val1 = strcmp(opnd1, opnd2);
+               if (op->op_num == STREQ)
+                       return val1 == 0;
+               if (op->op_num == STRNE)
+                       return val1 != 0;
+               if (op->op_num == STRLT)
+                       return val1 < 0;
+               if (op->op_num == STRGT)
+                       return val1 > 0;
+       }
+       /* We are sure that these three are by now the only binops we didn't check
+        * yet, so we do not check if the class is correct:
+        */
+/*     if (is_file_op(op->op_num)) */
+       {
+               struct stat b1, b2;
+
+               if (stat(opnd1, &b1) || stat(opnd2, &b2))
+                       return 0; /* false, since at least one stat failed */
+               if (op->op_num == FILNT)
+                       return b1.st_mtime > b2.st_mtime;
+               if (op->op_num == FILOT)
+                       return b1.st_mtime < b2.st_mtime;
+               if (op->op_num == FILEQ)
+                       return b1.st_dev == b2.st_dev && b1.st_ino == b2.st_ino;
+       }
+       return 1; /* NOTREACHED */
+}
+
+
+static void initialize_group_array(void)
+{
+       ngroups = getgroups(0, NULL);
+       if (ngroups > 0) {
+               /* FIXME: ash tries so hard to not die on OOM,
+                * and we spoil it with just one xrealloc here */
+               /* We realloc, because test_main can be entered repeatedly by shell.
+                * Testcase (ash): 'while true; do test -x some_file; done'
+                * and watch top. (some_file must have owner != you) */
+               group_array = xrealloc(group_array, ngroups * sizeof(gid_t));
+               getgroups(ngroups, group_array);
+       }
+}
+
+
+/* Return non-zero if GID is one that we have in our groups list. */
+//XXX: FIXME: duplicate of existing libbb function?
+// see toplevel TODO file:
+// possible code duplication ingroup() and is_a_group_member()
+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;
+}
+
+
+/* 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 int filstat(char *nm, enum token mode)
+{
+       struct stat s;
+       unsigned i = i; /* gcc 3.x thinks it can be used uninitialized */
+
+       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;
+       if (mode == FILEXIST)
+               return 1;
+       if (is_file_access(mode)) {
+               if (mode == FILRD)
+                       i = R_OK;
+               if (mode == FILWR)
+                       i = W_OK;
+               if (mode == FILEX)
+                       i = X_OK;
+               return test_eaccess(nm, i) == 0;
+       }
+       if (is_file_type(mode)) {
+               if (mode == FILREG)
+                       i = S_IFREG;
+               if (mode == FILDIR)
+                       i = S_IFDIR;
+               if (mode == FILCDEV)
+                       i = S_IFCHR;
+               if (mode == FILBDEV)
+                       i = S_IFBLK;
+               if (mode == FILFIFO) {
+#ifdef S_IFIFO
+                       i = S_IFIFO;
+#else
+                       return 0;
+#endif
+               }
+               if (mode == FILSOCK) {
+#ifdef S_IFSOCK
+                       i = S_IFSOCK;
+#else
+                       return 0;
+#endif
+               }
+ filetype:
+               return ((s.st_mode & S_IFMT) == i);
+       }
+       if (is_file_bit(mode)) {
+               if (mode == FILSUID)
+                       i = S_ISUID;
+               if (mode == FILSGID)
+                       i = S_ISGID;
+               if (mode == FILSTCK)
+                       i = S_ISVTX;
+               return ((s.st_mode & i) != 0);
+       }
+       if (mode == FILGZ)
+               return s.st_size > 0L;
+       if (mode == FILUID)
+               return s.st_uid == geteuid();
+       if (mode == FILGID)
+               return s.st_gid == getegid();
+       return 1; /* NOTREACHED */
+}
+
+
+static number_t nexpr(enum token n)
+{
+       number_t res;
+
+       nest_msg(">nexpr(%s)\n", TOKSTR[n]);
+       if (n == UNOT) {
+               n = check_operator(*++args);
+               if (n == EOI) {
+                       /* special case: [ ! ], [ a -a ! ] are valid */
+                       /* IOW, "! ARG" may miss ARG */
+                       unnest_msg("<nexpr:1 (!EOI)\n");
+                       return 1;
+               }
+               res = !nexpr(n);
+               unnest_msg("<nexpr:%lld\n", res);
+               return res;
+       }
+       res = primary(n);
+       unnest_msg("<nexpr:%lld\n", res);
+       return res;
+}
+
+
+static number_t aexpr(enum token n)
+{
+       number_t res;
+
+       nest_msg(">aexpr(%s)\n", TOKSTR[n]);
+       res = nexpr(n);
+       dbg_msg("aexpr: nexpr:%lld, next args:%s\n", res, args[1]);
+       if (check_operator(*++args) == BAND) {
+               dbg_msg("aexpr: arg is AND, next args:%s\n", args[1]);
+               res = aexpr(check_operator(*++args)) && res;
+               unnest_msg("<aexpr:%lld\n", res);
+               return res;
+       }
+       args--;
+       unnest_msg("<aexpr:%lld, args:%s\n", res, args[0]);
+       return res;
+}
+
+
+static number_t oexpr(enum token n)
+{
+       number_t res;
+
+       nest_msg(">oexpr(%s)\n", TOKSTR[n]);
+       res = aexpr(n);
+       dbg_msg("oexpr: aexpr:%lld, next args:%s\n", res, args[1]);
+       if (check_operator(*++args) == BOR) {
+               dbg_msg("oexpr: next arg is OR, next args:%s\n", args[1]);
+               res = oexpr(check_operator(*++args)) || res;
+               unnest_msg("<oexpr:%lld\n", res);
+               return res;
+       }
+       args--;
+       unnest_msg("<oexpr:%lld, args:%s\n", res, args[0]);
+       return res;
+}
+
+
+static number_t primary(enum token n)
+{
+#if TEST_DEBUG
+       number_t res = res; /* for compiler */
+#else
+       number_t res;
+#endif
+       const struct operator_t *args0_op;
+
+       nest_msg(">primary(%s)\n", TOKSTR[n]);
+       if (n == EOI) {
+               syntax(NULL, "argument expected");
+       }
+       if (n == LPAREN) {
+               res = oexpr(check_operator(*++args));
+               if (check_operator(*++args) != RPAREN)
+                       syntax(NULL, "closing paren expected");
+               unnest_msg("<primary:%lld\n", res);
+               return res;
+       }
+
+       /* coreutils 6.9 checks "is args[1] binop and args[2] exist?" first,
+        * do the same */
+       args0_op = last_operator;
+       /* last_operator = operator at args[1] */
+       if (check_operator(args[1]) != EOI) { /* if args[1] != NULL */
+               if (args[2]) {
+                       // coreutils also does this:
+                       // if (args[3] && args[0]="-l" && args[2] is BINOP)
+                       //      return binop(1 /* prepended by -l */);
+                       if (last_operator->op_type == BINOP)
+                               unnest_msg_and_return(binop(), "<primary: binop:%lld\n");
+               }
+       }
+       /* check "is args[0] unop?" second */
+       if (args0_op->op_type == UNOP) {
+               /* unary expression */
+               if (args[1] == NULL)
+//                     syntax(args0_op->op_text, "argument expected");
+                       goto check_emptiness;
+               args++;
+               if (n == STREZ)
+                       unnest_msg_and_return(args[0][0] == '\0', "<primary:%lld\n");
+               if (n == STRNZ)
+                       unnest_msg_and_return(args[0][0] != '\0', "<primary:%lld\n");
+               if (n == FILTT)
+                       unnest_msg_and_return(isatty(getn(*args)), "<primary: isatty(%s)%lld\n", *args);
+               unnest_msg_and_return(filstat(*args, n), "<primary: filstat(%s):%lld\n", *args);
+       }
+
+       /*check_operator(args[1]); - already done */
+       if (last_operator->op_type == BINOP) {
+               /* args[2] is known to be NULL, isn't it bound to fail? */
+               unnest_msg_and_return(binop(), "<primary:%lld\n");
+       }
+ check_emptiness:
+       unnest_msg_and_return(args[0][0] != '\0', "<primary:%lld\n");
+}
+
+
+int test_main(int argc, char **argv)
+{
+       int res;
+       const char *arg0;
+//     bool negate = 0;
+
+       arg0 = bb_basename(argv[0]);
+       if (arg0[0] == '[') {
+               --argc;
+               if (!arg0[1]) { /* "[" ? */
+                       if (NOT_LONE_CHAR(argv[argc], ']')) {
+                               bb_error_msg("missing ]");
+                               return 2;
+                       }
+               } else { /* assuming "[[" */
+                       if (strcmp(argv[argc], "]]") != 0) {
+                               bb_error_msg("missing ]]");
+                               return 2;
+                       }
+               }
+               argv[argc] = NULL;
+       }
+
+       /* We must do DEINIT_S() prior to returning */
+       INIT_S();
+
+       res = setjmp(leaving);
+       if (res)
+               goto ret;
+
+       /* 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;
+
+       //argc--;
+       argv++;
+
+       /* Implement special cases from POSIX.2, section 4.62.4 */
+       if (!argv[0]) { /* "test" */
+               res = 1;
+               goto ret;
+       }
+#if 0
+// Now it's fixed in the parser and should not be needed
+       if (LONE_CHAR(argv[0], '!') && argv[1]) {
+               negate = 1;
+               //argc--;
+               argv++;
+       }
+       if (!argv[1]) { /* "test [!] arg" */
+               res = (*argv[0] == '\0');
+               goto ret;
+       }
+       if (argv[2] && !argv[3]) {
+               check_operator(argv[1]);
+               if (last_operator->op_type == BINOP) {
+                       /* "test [!] arg1 <binary_op> arg2" */
+                       args = argv;
+                       res = (binop() == 0);
+                       goto ret;
+               }
+       }
+
+       /* Some complex expression. Undo '!' removal */
+       if (negate) {
+               negate = 0;
+               //argc++;
+               argv--;
+       }
+#endif
+       args = argv;
+       res = !oexpr(check_operator(*args));
+
+       if (*args != NULL && *++args != NULL) {
+               /* TODO: example when this happens? */
+               bb_error_msg("%s: unknown operand", *args);
+               res = 2;
+       }
+ ret:
+       DEINIT_S();
+//     return negate ? !res : res;
+       return res;
+}
diff --git a/coreutils/test_ptr_hack.c b/coreutils/test_ptr_hack.c
new file mode 100644 (file)
index 0000000..a05203d
--- /dev/null
@@ -0,0 +1,23 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Copyright (C) 2008 by Denys Vlasenko <vda.linux@googlemail.com>
+ *
+ * Licensed under GPLv2, see file LICENSE in this tarball for details.
+ */
+
+struct test_statics;
+
+#ifndef GCC_COMBINE
+
+/* We cheat here. It is declared as const ptr in libbb.h,
+ * but here we make it live in R/W memory */
+struct test_statics *test_ptr_to_statics;
+
+#else
+
+/* gcc -combine will see through and complain */
+/* Using alternative method which is more likely to break
+ * on weird architectures, compilers, linkers and so on */
+struct test_statics *const test_ptr_to_statics __attribute__ ((section (".data")));
+
+#endif
diff --git a/coreutils/touch.c b/coreutils/touch.c
new file mode 100644 (file)
index 0000000..2019154
--- /dev/null
@@ -0,0 +1,105 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Mini touch 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.
+ */
+
+/* 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 "libbb.h"
+
+/* This is a NOFORK applet. Be very careful! */
+
+/* coreutils implements:
+ * -a   change only the access time
+ * -c, --no-create
+ *      do not create any files
+ * -d, --date=STRING
+ *      parse STRING and use it instead of current time
+ * -f   (ignored, BSD compat)
+ * -m   change only the modification time
+ * -r, --reference=FILE
+ *      use this file's times instead of current time
+ * -t STAMP
+ *      use [[CC]YY]MMDDhhmm[.ss] instead of current time
+ * --time=WORD
+ *      change the specified time: WORD is access, atime, or use
+ */
+
+int touch_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int touch_main(int argc UNUSED_PARAM, char **argv)
+{
+#if ENABLE_DESKTOP
+#if ENABLE_GETOPT_LONG
+       static const char longopts[] ALIGN1 =
+               /* name, has_arg, val */
+               "no-create\0"         No_argument       "c"
+               "reference\0"         Required_argument "r"
+       ;
+#endif
+       struct utimbuf timebuf;
+       char *reference_file = NULL;
+#else
+#define reference_file NULL
+#define timebuf        (*(struct utimbuf*)NULL)
+#endif
+       int fd;
+       int status = EXIT_SUCCESS;
+       int opts;
+
+#if ENABLE_DESKTOP
+#if ENABLE_GETOPT_LONG
+       applet_long_options = longopts;
+#endif
+#endif
+       opts = getopt32(argv, "c" USE_DESKTOP("r:")
+                               /*ignored:*/ "fma"
+                               USE_DESKTOP(, &reference_file));
+
+       opts &= 1; /* only -c bit is left */
+       argv += optind;
+       if (!*argv) {
+               bb_show_usage();
+       }
+
+       if (reference_file) {
+               struct stat stbuf;
+               xstat(reference_file, &stbuf);
+               timebuf.actime = stbuf.st_atime;
+               timebuf.modtime = stbuf.st_mtime;
+       }
+
+       do {
+               if (utime(*argv, reference_file ? &timebuf : NULL)) {
+                       if (errno == ENOENT) { /* no such file */
+                               if (opts) { /* 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)) {
+                                       if (reference_file)
+                                               utime(*argv, &timebuf);
+                                       continue;
+                               }
+                       }
+                       status = EXIT_FAILURE;
+                       bb_simple_perror_msg(*argv);
+               }
+       } while (*++argv);
+
+       return status;
+}
diff --git a/coreutils/tr.c b/coreutils/tr.c
new file mode 100644 (file)
index 0000000..d89b80b
--- /dev/null
@@ -0,0 +1,299 @@
+/* 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 <andersen@codepoet.org> to be used in busybox.
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+/* http://www.opengroup.org/onlinepubs/009695399/utilities/tr.html
+ * TODO: graph, print
+ */
+#include "libbb.h"
+
+enum {
+       ASCII = 256,
+       /* string buffer needs to be at least as big as the whole "alphabet".
+        * BUFSIZ == ASCII is ok, but we will realloc in expand
+        * even for smallest patterns, let's avoid that by using *2:
+        */
+       TR_BUFSIZ = (BUFSIZ > ASCII*2) ? BUFSIZ : ASCII*2,
+};
+
+static void map(char *pvector,
+               char *string1, unsigned string1_len,
+               char *string2, unsigned string2_len)
+{
+       char last = '0';
+       unsigned i, j;
+
+       for (j = 0, i = 0; i < string1_len; i++) {
+               if (string2_len <= j)
+                       pvector[(unsigned char)(string1[i])] = last;
+               else
+                       pvector[(unsigned char)(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
+ *   Equiv classess, e.g. [=A=] ==> A   (hmmmmmmm?)
+ * not supported:
+ *   \ooo-\ooo - octal ranges
+ *   [x*N] - repeat char x N times
+ *   [x*] - repeat char x until it fills STRING2:
+ * # echo qwe123 | /usr/bin/tr 123456789 '[d]'
+ * qwe[d]
+ * # echo qwe123 | /usr/bin/tr 123456789 '[d*]'
+ * qweddd
+ */
+static unsigned expand(const char *arg, char **buffer_p)
+{
+       char *buffer = *buffer_p;
+       unsigned pos = 0;
+       unsigned size = TR_BUFSIZ;
+       unsigned i; /* can't be unsigned char: must be able to hold 256 */
+       unsigned char ac;
+
+       while (*arg) {
+               if (pos + ASCII > size) {
+                       size += ASCII;
+                       *buffer_p = buffer = xrealloc(buffer, size);
+               }
+               if (*arg == '\\') {
+                       arg++;
+                       buffer[pos++] = bb_process_escape_sequence(&arg);
+                       continue;
+               }
+               if (arg[1] == '-') { /* "0-9..." */
+                       ac = arg[2];
+                       if (ac == '\0') { /* "0-": copy verbatim */
+                               buffer[pos++] = *arg++; /* copy '0' */
+                               continue; /* next iter will copy '-' and stop */
+                       }
+                       i = (unsigned char) *arg;
+                       while (i <= ac) /* ok: i is unsigned _int_ */
+                               buffer[pos++] = i++;
+                       arg += 3; /* skip 0-9 */
+                       continue;
+               }
+               if ((ENABLE_FEATURE_TR_CLASSES || ENABLE_FEATURE_TR_EQUIV)
+                && *arg == '['
+               ) {
+                       arg++;
+                       i = (unsigned char) *arg++;
+                       /* "[xyz...". i=x, arg points to y */
+                       if (ENABLE_FEATURE_TR_CLASSES && i == ':') { /* [:class:] */
+#define CLO ":]\0"
+                               static const char classes[] ALIGN1 =
+                                       "alpha"CLO "alnum"CLO "digit"CLO
+                                       "lower"CLO "upper"CLO "space"CLO
+                                       "blank"CLO "punct"CLO "cntrl"CLO
+                                       "xdigit"CLO;
+                               enum {
+                                       CLASS_invalid = 0, /* we increment the retval */
+                                       CLASS_alpha = 1,
+                                       CLASS_alnum = 2,
+                                       CLASS_digit = 3,
+                                       CLASS_lower = 4,
+                                       CLASS_upper = 5,
+                                       CLASS_space = 6,
+                                       CLASS_blank = 7,
+                                       CLASS_punct = 8,
+                                       CLASS_cntrl = 9,
+                                       CLASS_xdigit = 10,
+                                       //CLASS_graph = 11,
+                                       //CLASS_print = 12,
+                               };
+                               smalluint j;
+                               char *tmp;
+
+                               /* xdigit needs 8, not 7 */
+                               i = 7 + (arg[0] == 'x');
+                               tmp = xstrndup(arg, i);
+                               j = index_in_strings(classes, tmp) + 1;
+                               free(tmp);
+
+                               if (j == CLASS_invalid)
+                                       goto skip_bracket;
+
+                               arg += i;
+                               if (j == CLASS_alnum || j == CLASS_digit || j == CLASS_xdigit) {
+                                       for (i = '0'; i <= '9'; i++)
+                                               buffer[pos++] = i;
+                               }
+                               if (j == CLASS_alpha || j == CLASS_alnum || j == CLASS_upper) {
+                                       for (i = 'A'; i <= 'Z'; i++)
+                                               buffer[pos++] = i;
+                               }
+                               if (j == CLASS_alpha || j == CLASS_alnum || j == CLASS_lower) {
+                                       for (i = 'a'; i <= 'z'; i++)
+                                               buffer[pos++] = i;
+                               }
+                               if (j == CLASS_space || j == CLASS_blank) {
+                                       buffer[pos++] = '\t';
+                                       if (j == CLASS_space) {
+                                               buffer[pos++] = '\n';
+                                               buffer[pos++] = '\v';
+                                               buffer[pos++] = '\f';
+                                               buffer[pos++] = '\r';
+                                       }
+                                       buffer[pos++] = ' ';
+                               }
+                               if (j == CLASS_punct || j == CLASS_cntrl) {
+                                       for (i = '\0'; i < ASCII; i++) {
+                                               if ((j == CLASS_punct && isprint(i) && !isalnum(i) && !isspace(i))
+                                                || (j == CLASS_cntrl && iscntrl(i))
+                                               ) {
+                                                       buffer[pos++] = i;
+                                               }
+                                       }
+                               }
+                               if (j == CLASS_xdigit) {
+                                       for (i = 'A'; i <= 'F'; i++) {
+                                               buffer[pos + 6] = i | 0x20;
+                                               buffer[pos++] = i;
+                                       }
+                                       pos += 6;
+                               }
+                               continue;
+                       }
+                       /* "[xyz...", i=x, arg points to y */
+                       if (ENABLE_FEATURE_TR_EQUIV && i == '=') { /* [=CHAR=] */
+                               buffer[pos++] = *arg; /* copy CHAR */
+                               if (!arg[0] || arg[1] != '=' || arg[2] != ']')
+                                       bb_show_usage();
+                               arg += 3;       /* skip CHAR=] */
+                               continue;
+                       }
+                       /* The rest of "[xyz..." cases is treated as normal
+                        * string, "[" has no special meaning here:
+                        * tr "[a-z]" "[A-Z]" can be written as tr "a-z" "A-Z",
+                        * also try tr "[a-z]" "_A-Z+" and you'll see that
+                        * [] is not special here.
+                        */
+ skip_bracket:
+                       arg -= 2; /* points to "[" in "[xyz..." */
+               }
+               buffer[pos++] = *arg++;
+       }
+       return pos;
+}
+
+/* NB: buffer is guaranteed to be at least TR_BUFSIZE
+ * (which is >= ASCII) big.
+ */
+static int complement(char *buffer, int buffer_len)
+{
+       int len;
+       char conv[ASCII];
+       unsigned char ch;
+
+       len = 0;
+       ch = '\0';
+       while (1) {
+               if (memchr(buffer, ch, buffer_len) == NULL)
+                       conv[len++] = ch;
+               if (++ch == '\0')
+                       break;
+       }
+       memcpy(buffer, conv, len);
+       return len;
+}
+
+int tr_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int tr_main(int argc UNUSED_PARAM, char **argv)
+{
+       int i;
+       smalluint opts;
+       ssize_t read_chars;
+       size_t in_index, out_index;
+       unsigned last = UCHAR_MAX + 1; /* not equal to any char */
+       unsigned char coded, c;
+       char *str1 = xmalloc(TR_BUFSIZ);
+       char *str2 = xmalloc(TR_BUFSIZ);
+       int str2_length;
+       int str1_length;
+       char *vector = xzalloc(ASCII * 3);
+       char *invec  = vector + ASCII;
+       char *outvec = vector + ASCII * 2;
+
+#define TR_OPT_complement      (3 << 0)
+#define TR_OPT_delete          (1 << 2)
+#define TR_OPT_squeeze_reps    (1 << 3)
+
+       for (i = 0; i < ASCII; i++) {
+               vector[i] = i;
+               /*invec[i] = outvec[i] = FALSE; - done by xzalloc */
+       }
+
+       /* -C/-c difference is that -C complements "characters",
+        * and -c complements "values" (binary bytes I guess).
+        * In POSIX locale, these are the same.
+        */
+
+       opt_complementary = "-1";
+       opts = getopt32(argv, "+Ccds"); /* '+': stop at first non-option */
+       argv += optind;
+
+       str1_length = expand(*argv++, &str1);
+       str2_length = 0;
+       if (opts & TR_OPT_complement)
+               str1_length = complement(str1, str1_length);
+       if (*argv) {
+               if (argv[0][0] == '\0')
+                       bb_error_msg_and_die("STRING2 cannot be empty");
+               str2_length = expand(*argv, &str2);
+               map(vector, str1, str1_length,
+                               str2, str2_length);
+       }
+       for (i = 0; i < str1_length; i++)
+               invec[(unsigned char)(str1[i])] = TRUE;
+       for (i = 0; i < str2_length; i++)
+               outvec[(unsigned char)(str2[i])] = TRUE;
+
+       goto start_from;
+
+       /* In this loop, str1 space is reused as input buffer,
+        * str2 - as output one. */
+       for (;;) {
+               /* If we're out of input, flush output and read more input. */
+               if ((ssize_t)in_index == read_chars) {
+                       if (out_index) {
+                               xwrite(STDOUT_FILENO, str2, out_index);
+ start_from:
+                               out_index = 0;
+                       }
+                       read_chars = safe_read(STDIN_FILENO, str1, TR_BUFSIZ);
+                       if (read_chars <= 0) {
+                               if (read_chars < 0)
+                                       bb_perror_msg_and_die(bb_msg_read_error);
+                               break;
+                       }
+                       in_index = 0;
+               }
+               c = str1[in_index++];
+               if ((opts & TR_OPT_delete) && invec[c])
+                       continue;
+               coded = vector[c];
+               if ((opts & TR_OPT_squeeze_reps) && last == coded
+                && (invec[c] || outvec[coded])
+               ) {
+                       continue;
+               }
+               str2[out_index++] = last = coded;
+       }
+
+       return EXIT_SUCCESS;
+}
diff --git a/coreutils/true.c b/coreutils/true.c
new file mode 100644 (file)
index 0000000..8a7e6ae
--- /dev/null
@@ -0,0 +1,21 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Mini true 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.
+ */
+
+/* BB_AUDIT SUSv3 compliant */
+/* http://www.opengroup.org/onlinepubs/007904975/utilities/true.html */
+
+#include "libbb.h"
+
+/* This is a NOFORK applet. Be very careful! */
+
+int true_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int true_main(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
+{
+       return EXIT_SUCCESS;
+}
diff --git a/coreutils/tty.c b/coreutils/tty.c
new file mode 100644 (file)
index 0000000..d49fb50
--- /dev/null
@@ -0,0 +1,44 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * tty implementation for busybox
+ *
+ * Copyright (C) 2003  Manuel Novoa III  <mjn3@codepoet.org>
+ *
+ * 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 "libbb.h"
+
+int tty_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int tty_main(int argc, char **argv SKIP_INCLUDE_SUSv2(UNUSED_PARAM))
+{
+       const char *s;
+       USE_INCLUDE_SUSv2(int silent;)  /* Note: No longer relevant in SUSv3. */
+       int retval;
+
+       xfunc_error_retval = 2; /* SUSv3 requires > 1 for error. */
+
+       USE_INCLUDE_SUSv2(silent = getopt32(argv, "s");)
+       USE_INCLUDE_SUSv2(argc -= optind;)
+       SKIP_INCLUDE_SUSv2(argc -= 1;)
+
+       /* gnu tty outputs a warning that it is ignoring all args. */
+       bb_warn_ignoring_args(argc);
+
+       retval = 0;
+
+       s = xmalloc_ttyname(0);
+       if (s == NULL) {
+       /* According to SUSv3, ttyname can fail with EBADF or ENOTTY.
+        * We know the file descriptor is good, so failure means not a tty. */
+               s = "not a tty";
+               retval = 1;
+       }
+       USE_INCLUDE_SUSv2(if (!silent) puts(s);)
+       SKIP_INCLUDE_SUSv2(puts(s);)
+
+       fflush_stdout_and_exit(retval);
+}
diff --git a/coreutils/uname.c b/coreutils/uname.c
new file mode 100644 (file)
index 0000000..33d026f
--- /dev/null
@@ -0,0 +1,154 @@
+/* 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 <djm@gnu.ai.mit.edu>
+ *
+ * GNU coreutils 6.10:
+ * Option:                      struct   Example(s):
+ *                              utsname
+ *                              field:
+ * -s, --kernel-name            sysname  Linux
+ * -n, --nodename               nodename localhost.localdomain
+ * -r, --kernel-release         release  2.6.29
+ * -v, --kernel-version         version  #1 SMP Sun Jan 11 20:52:37 EST 2009
+ * -m, --machine                machine  x86_64   i686
+ * -p, --processor              (none)   x86_64   i686
+ * -i, --hardware-platform      (none)   x86_64   i386
+ *      NB: vanilla coreutils reports "unknown" -p and -i,
+ *      x86_64 and i686/i386 shown above are Fedora's inventions.
+ * -o, --operating-system       (none)   GNU/Linux
+ * -a, --all: all of the above, in the order shown.
+ *      If -p or -i is not known, don't show them
+ */
+
+/* Busyboxed by Erik Andersen
+ *
+ * Before 2003: Glenn McGrath and Manuel Novoa III
+ *  Further size reductions.
+ * Mar 16, 2003: Manuel Novoa III (mjn3@codepoet.org)
+ *  Now does proper error checking on i/o.  Plus some further space savings.
+ * Jan 2009:
+ *  Fix handling of -a to not print "unknown", add -o and -i support.
+ */
+
+#include <sys/utsname.h>
+#include "libbb.h"
+
+typedef struct {
+       struct utsname name;
+       char processor[sizeof(((struct utsname*)NULL)->machine)];
+       char platform[sizeof(((struct utsname*)NULL)->machine)];
+       char os[sizeof("GNU/Linux")];
+} uname_info_t;
+
+static const char options[] ALIGN1 = "snrvmpioa";
+static const unsigned short utsname_offset[] = {
+       offsetof(uname_info_t, name.sysname), /* -s */
+       offsetof(uname_info_t, name.nodename), /* -n */
+       offsetof(uname_info_t, name.release), /* -r */
+       offsetof(uname_info_t, name.version), /* -v */
+       offsetof(uname_info_t, name.machine), /* -m */
+       offsetof(uname_info_t, processor), /* -p */
+       offsetof(uname_info_t, platform), /* -i */
+       offsetof(uname_info_t, os), /* -o */
+};
+
+int uname_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int uname_main(int argc UNUSED_PARAM, char **argv)
+{
+#if ENABLE_GETOPT_LONG
+       static const char longopts[] ALIGN1 =
+               /* name, has_arg, val */
+               "all\0"               No_argument       "a"
+               "kernel-name\0"       No_argument       "s"
+               "nodename\0"          No_argument       "n"
+               "kernel-release\0"    No_argument       "r"
+               "release\0"           No_argument       "r"
+               "kernel-version\0"    No_argument       "v"
+               "machine\0"           No_argument       "m"
+               "processor\0"         No_argument       "p"
+               "hardware-platform\0" No_argument       "i"
+               "operating-system\0"  No_argument       "o"
+       ;
+#endif
+       uname_info_t uname_info;
+#if defined(__sparc__) && defined(__linux__)
+       char *fake_sparc = getenv("FAKE_SPARC");
+#endif
+       const char *unknown_str = "unknown";
+       const char *fmt;
+       const unsigned short *delta;
+       unsigned toprint;
+
+       USE_GETOPT_LONG(applet_long_options = longopts);
+       toprint = getopt32(argv, options);
+
+       if (argv[optind]) { /* coreutils-6.9 compat */
+               bb_show_usage();
+       }
+
+       if (toprint & (1 << 8)) { /* -a => all opts on */
+               toprint = (1 << 8) - 1;
+               unknown_str = ""; /* -a does not print unknown fields */
+       }
+
+       if (toprint == 0) { /* no opts => -s (sysname) */
+               toprint = 1;
+       }
+
+       uname(&uname_info.name); /* never fails */
+
+#if defined(__sparc__) && defined(__linux__)
+       if (fake_sparc && (fake_sparc[0] | 0x20) == 'y') {
+               strcpy(uname_info.name.machine, "sparc");
+       }
+#endif
+       strcpy(uname_info.processor, unknown_str);
+       strcpy(uname_info.platform, unknown_str);
+       strcpy(uname_info.os, "GNU/Linux");
+#if 0
+       /* Fedora does something like this */
+       strcpy(uname_info.processor, uname_info.name.machine);
+       strcpy(uname_info.platform, uname_info.name.machine);
+       if (uname_info.platform[0] == 'i'
+        && uname_info.platform[1]
+        && uname_info.platform[2] == '8'
+        && uname_info.platform[3] == '6'
+       ) {
+               uname_info.platform[1] = '3';
+       }
+#endif
+
+       delta = utsname_offset;
+       fmt = " %s" + 1;
+       do {
+               if (toprint & 1) {
+                       const char *p = (char *)(&uname_info) + *delta;
+                       if (p[0]) {
+                               printf(fmt, p);
+                               fmt = " %s";
+                       }
+               }
+               ++delta;
+       } while (toprint >>= 1);
+       bb_putchar('\n');
+
+       fflush_stdout_and_exit(EXIT_SUCCESS); /* coreutils-6.9 compat */
+}
diff --git a/coreutils/uniq.c b/coreutils/uniq.c
new file mode 100644 (file)
index 0000000..126eaee
--- /dev/null
@@ -0,0 +1,102 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * uniq implementation for busybox
+ *
+ * Copyright (C) 2005  Manuel Novoa III  <mjn3@codepoet.org>
+ *
+ * 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 "libbb.h"
+
+static FILE *xgetoptfile_uniq_s(char **argv, int read0write2)
+{
+       const char *n;
+
+       n = *argv;
+       if (n != NULL) {
+               if ((*n != '-') || n[1]) {
+                       return xfopen(n, "r\0w" + read0write2);
+               }
+       }
+       return (read0write2) ? stdout : stdin;
+}
+
+int uniq_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int uniq_main(int argc UNUSED_PARAM, char **argv)
+{
+       FILE *in, *out;
+       const char *s0, *e0, *s1, *e1, *input_filename;
+       unsigned long dups;
+       unsigned skip_fields, skip_chars, max_chars;
+       unsigned opt;
+       unsigned i;
+
+       enum {
+               OPT_c = 0x1,
+               OPT_d = 0x2,
+               OPT_u = 0x4,
+               OPT_f = 0x8,
+               OPT_s = 0x10,
+               OPT_w = 0x20,
+       };
+
+       skip_fields = skip_chars = 0;
+       max_chars = INT_MAX;
+
+       opt_complementary = "f+:s+:w+";
+       opt = getopt32(argv, "cduf:s:w:", &skip_fields, &skip_chars, &max_chars);
+       argv += optind;
+
+       input_filename = *argv;
+
+       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_fgetline(in)) != NULL) {
+                       e1 = s1;
+                       for (i = skip_fields; i; i--) {
+                               e1 = skip_whitespace(e1);
+                               e1 = skip_non_whitespace(e1);
+                       }
+                       for (i = skip_chars; *e1 && i; i--) {
+                               ++e1;
+                       }
+
+                       if (!s0 || strncmp(e0, e1, max_chars)) {
+                               break;
+                       }
+
+                       ++dups;  /* note: testing for overflow seems excessive. */
+               }
+
+               if (s0) {
+                       if (!(opt & (OPT_d << !!dups))) { /* (if dups, opt & OPT_e) */
+                               fprintf(out, "\0%ld " + (opt & 1), dups + 1); /* 1 == OPT_c */
+                               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 (file)
index 0000000..e7acd5f
--- /dev/null
@@ -0,0 +1,28 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * usleep implementation for busybox
+ *
+ * Copyright (C) 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 -- Apparently a busybox extension. */
+
+#include "libbb.h"
+
+/* This is a NOFORK applet. Be very careful! */
+
+int usleep_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int usleep_main(int argc UNUSED_PARAM, char **argv)
+{
+       if (!argv[1]) {
+               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 (file)
index 0000000..0298a4b
--- /dev/null
@@ -0,0 +1,224 @@
+/* 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 "libbb.h"
+
+static void read_stduu(FILE *src_stream, FILE *dst_stream)
+{
+       char *line;
+
+       while ((line = xmalloc_fgetline(src_stream)) != NULL) {
+               int encoded_len, str_len;
+               char *line_ptr, *dst;
+
+               if (strcmp(line, "end") == 0) {
+                       return; /* the only non-error exit */
+               }
+
+               line_ptr = line;
+               while (*line_ptr) {
+                       *line_ptr = (*line_ptr - 0x20) & 0x3f;
+                       line_ptr++;
+               }
+               str_len = line_ptr - line;
+
+               encoded_len = line[0] * 4 / 3;
+               /* Check that line is not too short. (we tolerate
+                * overly _long_ line to accomodate possible extra '`').
+                * Empty line case is also caught here. */
+               if (str_len <= encoded_len) {
+                       break; /* go to bb_error_msg_and_die("short file"); */
+               }
+               if (encoded_len <= 0) {
+                       /* Ignore the "`\n" line, why is it even in the encode file ? */
+                       free(line);
+                       continue;
+               }
+               if (encoded_len > 60) {
+                       bb_error_msg_and_die("line too long");
+               }
+
+               dst = line;
+               line_ptr = line + 1;
+               do {
+                       /* Merge four 6 bit chars to three 8 bit chars */
+                       *dst++ = line_ptr[0] << 2 | line_ptr[1] >> 4;
+                       encoded_len--;
+                       if (encoded_len == 0) {
+                               break;
+                       }
+
+                       *dst++ = line_ptr[1] << 4 | line_ptr[2] >> 2;
+                       encoded_len--;
+                       if (encoded_len == 0) {
+                               break;
+                       }
+
+                       *dst++ = line_ptr[2] << 6 | line_ptr[3];
+                       line_ptr += 4;
+                       encoded_len -= 2;
+               } while (encoded_len > 0);
+               fwrite(line, 1, dst - line, dst_stream);
+               free(line);
+       }
+       bb_error_msg_and_die("short file");
+}
+
+static void read_base64(FILE *src_stream, FILE *dst_stream)
+{
+       int term_count = 1;
+
+       while (1) {
+               char translated[4];
+               int count = 0;
+
+               while (count < 4) {
+                       char *table_ptr;
+                       int ch;
+
+                       /* Get next _valid_ character.
+                        * global vector bb_uuenc_tbl_base64[] contains this string:
+                        * "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=\n"
+                        */
+                       do {
+                               ch = fgetc(src_stream);
+                               if (ch == EOF) {
+                                       bb_error_msg_and_die("short file");
+                               }
+                               table_ptr = strchr(bb_uuenc_tbl_base64, ch);
+                       } while (table_ptr == NULL);
+
+                       /* Convert encoded character to decimal */
+                       ch = table_ptr - bb_uuenc_tbl_base64;
+
+                       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;
+                               }
+                               term_count = 1;
+                               continue;
+                       } else {
+                               translated[count] = ch;
+                               count++;
+                               term_count = 0;
+                       }
+               }
+
+               /* Merge 6 bit chars to 8 bit */
+               if (count > 1) {
+                       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) MAIN_EXTERNALLY_VISIBLE;
+int uudecode_main(int argc UNUSED_PARAM, char **argv)
+{
+       FILE *src_stream;
+       char *outname = NULL;
+       char *line;
+
+       opt_complementary = "?1"; /* 1 argument max */
+       getopt32(argv, "o:", &outname);
+       argv += optind;
+
+       if (!*argv)
+               *--argv = (char*)"-";
+       src_stream = xfopen_stdin(*argv);
+
+       /* Search for the start of the encoding */
+       while ((line = xmalloc_fgetline(src_stream)) != NULL) {
+               void (*decode_fn_ptr)(FILE *src, FILE *dst);
+               char *line_ptr;
+               FILE *dst_stream;
+               int mode;
+
+               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;
+               }
+
+               /* begin line found. decode and exit */
+               mode = bb_strtou(line_ptr, NULL, 8);
+               if (outname == NULL) {
+                       outname = strchr(line_ptr, ' ');
+                       if ((outname == NULL) || (*outname == '\0')) {
+                               break;
+                       }
+                       outname++;
+               }
+               dst_stream = stdout;
+               if (NOT_LONE_DASH(outname)) {
+                       dst_stream = xfopen_for_write(outname);
+                       fchmod(fileno(dst_stream), mode & (S_IRWXU | S_IRWXG | S_IRWXO));
+               }
+               free(line);
+               decode_fn_ptr(src_stream, dst_stream);
+               /* fclose_if_not_stdin(src_stream); - redundant */
+               return EXIT_SUCCESS;
+       }
+       bb_error_msg_and_die("no 'begin' line");
+}
+
+/* Test script.
+Put this into an empty dir with busybox binary, an run.
+
+#!/bin/sh
+test -x busybox || { echo "No ./busybox?"; exit; }
+ln -sf busybox uudecode
+ln -sf busybox uuencode
+>A_null
+echo -n A >A
+echo -n AB >AB
+echo -n ABC >ABC
+echo -n ABCD >ABCD
+echo -n ABCDE >ABCDE
+echo -n ABCDEF >ABCDEF
+cat busybox >A_bbox
+for f in A*; do
+    echo uuencode $f
+    ./uuencode    $f <$f >u_$f
+    ./uuencode -m $f <$f >m_$f
+done
+mkdir unpk_u unpk_m 2>/dev/null
+for f in u_*; do
+    ./uudecode <$f -o unpk_u/${f:2}
+    diff -a ${f:2} unpk_u/${f:2} >/dev/null 2>&1
+    echo uudecode $f: $?
+done
+for f in m_*; do
+    ./uudecode <$f -o unpk_m/${f:2}
+    diff -a ${f:2} unpk_m/${f:2} >/dev/null 2>&1
+    echo uudecode $f: $?
+done
+*/
diff --git a/coreutils/uuencode.c b/coreutils/uuencode.c
new file mode 100644 (file)
index 0000000..e19f996
--- /dev/null
@@ -0,0 +1,61 @@
+/* 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 "libbb.h"
+
+enum {
+       SRC_BUF_SIZE = 45,  /* This *MUST* be a multiple of 3 */
+       DST_BUF_SIZE = 4 * ((SRC_BUF_SIZE + 2) / 3),
+};
+
+int uuencode_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int uuencode_main(int argc, char **argv)
+{
+       struct stat stat_buf;
+       int src_fd = STDIN_FILENO;
+       const char *tbl;
+       mode_t mode;
+       char src_buf[SRC_BUF_SIZE];
+       char dst_buf[DST_BUF_SIZE + 1];
+
+       tbl = bb_uuenc_tbl_std;
+       mode = 0666 & ~umask(0666);
+       opt_complementary = "-1:?2"; /* must have 1 or 2 args */
+       if (getopt32(argv, "m")) {
+               tbl = bb_uuenc_tbl_base64;
+       }
+       argv += optind;
+       if (argc == optind + 2) {
+               src_fd = xopen(*argv, O_RDONLY);
+               fstat(src_fd, &stat_buf);
+               mode = stat_buf.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO);
+               argv++;
+       }
+
+       printf("begin%s %o %s", tbl == bb_uuenc_tbl_std ? "" : "-base64", mode, *argv);
+       while (1) {
+               size_t size = full_read(src_fd, src_buf, SRC_BUF_SIZE);
+               if (!size)
+                       break;
+               if ((ssize_t)size < 0)
+                       bb_perror_msg_and_die(bb_msg_read_error);
+               /* Encode the buffer we just read in */
+               bb_uuencode(dst_buf, src_buf, size, tbl);
+               bb_putchar('\n');
+               if (tbl == bb_uuenc_tbl_std) {
+                       bb_putchar(tbl[size]);
+               }
+               fflush(stdout);
+               xwrite(STDOUT_FILENO, dst_buf, 4 * ((size + 2) / 3));
+       }
+       printf(tbl == bb_uuenc_tbl_std ? "\n`\nend\n" : "\n====\n");
+
+       fflush_stdout_and_exit(EXIT_SUCCESS);
+}
diff --git a/coreutils/wc.c b/coreutils/wc.c
new file mode 100644 (file)
index 0000000..d0e5482
--- /dev/null
@@ -0,0 +1,205 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * wc implementation for busybox
+ *
+ * Copyright (C) 2003  Manuel Novoa III  <mjn3@codepoet.org>
+ *
+ * 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 "libbb.h"
+
+#if ENABLE_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) MAIN_EXTERNALLY_VISIBLE;
+int wc_main(int argc UNUSED_PARAM, char **argv)
+{
+       FILE *fp;
+       const char *s, *arg;
+       const char *start_fmt = " %9"COUNT_FMT + 1;
+       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;
+       smallint status = EXIT_SUCCESS;
+       smallint in_word;
+       unsigned print_type;
+
+       print_type = getopt32(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_simple_perror_msg(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 (file)
index 0000000..85a0025
--- /dev/null
@@ -0,0 +1,79 @@
+/* vi: set sw=4 ts=4: */
+/*----------------------------------------------------------------------
+ * Mini who is used to display user name, login time,
+ * idle time and host name.
+ *
+ * Author: Da Chen  <dchen@ayrnetworks.com>
+ *
+ * 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.
+ *
+ *----------------------------------------------------------------------
+ */
+/* BB_AUDIT SUSv3 _NOT_ compliant -- missing options -b, -d, -H, -l, -m, -p, -q, -r, -s, -t, -T, -u; Missing argument 'file'.  */
+
+#include "libbb.h"
+#include <utmp.h>
+
+static void idle_string(char *str6, time_t t)
+{
+       t = time(NULL) - t;
+
+       /*if (t < 60) {
+               str6[0] = '.';
+               str6[1] = '\0';
+               return;
+       }*/
+       if (t >= 0 && t < (24 * 60 * 60)) {
+               sprintf(str6, "%02d:%02d",
+                               (int) (t / (60 * 60)),
+                               (int) ((t % (60 * 60)) / 60));
+               return;
+       }
+       strcpy(str6, "old");
+}
+
+int who_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int who_main(int argc UNUSED_PARAM, char **argv)
+{
+       char str6[6];
+       struct utmp *ut;
+       struct stat st;
+       char *name;
+       unsigned opt;
+
+       opt_complementary = "=0";
+       opt = getopt32(argv, "a");
+
+       setutent();
+       printf("USER       TTY      IDLE      TIME            HOST\n");
+       while ((ut = getutent()) != NULL) {
+               if (ut->ut_user[0] && (opt || ut->ut_type == USER_PROCESS)) {
+                       time_t tmp;
+                       /* ut->ut_line is device name of tty - "/dev/" */
+                       name = concat_path_file("/dev", ut->ut_line);
+                       str6[0] = '?';
+                       str6[1] = '\0';
+                       if (stat(name, &st) == 0)
+                               idle_string(str6, st.st_atime);
+                       /* manpages say ut_tv.tv_sec *is* time_t,
+                        * but some systems have it wrong */
+                       tmp = ut->ut_tv.tv_sec;
+                       /* 15 chars for time:   Nov 10 19:33:20 */
+                       printf("%-10s %-8s %-9s %-15.15s %s\n",
+                                       ut->ut_user, ut->ut_line, str6,
+                                       ctime(&tmp) + 4, ut->ut_host);
+                       if (ENABLE_FEATURE_CLEAN_UP)
+                               free(name);
+               }
+       }
+       if (ENABLE_FEATURE_CLEAN_UP)
+               endutent();
+       return EXIT_SUCCESS;
+}
diff --git a/coreutils/whoami.c b/coreutils/whoami.c
new file mode 100644 (file)
index 0000000..0dbcba9
--- /dev/null
@@ -0,0 +1,26 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Mini whoami implementation for busybox
+ *
+ * Copyright (C) 2000  Edward Betts <edward@debian.org>.
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+/* BB_AUDIT SUSv3 N/A -- Matches GNU behavior. */
+
+#include "libbb.h"
+
+/* This is a NOFORK applet. Be very careful! */
+
+int whoami_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int whoami_main(int argc, char **argv UNUSED_PARAM)
+{
+       if (argc > 1)
+               bb_show_usage();
+
+       /* Will complain and die if username not found */
+       puts(xuid2uname(geteuid()));
+
+       return fflush(stdout);
+}
diff --git a/coreutils/yes.c b/coreutils/yes.c
new file mode 100644 (file)
index 0000000..9d3f675
--- /dev/null
@@ -0,0 +1,42 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * yes implementation for busybox
+ *
+ * Copyright (C) 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 -- Matches GNU behavior. */
+
+/* Mar 16, 2003      Manuel Novoa III   (mjn3@codepoet.org)
+ *
+ * Size reductions and removed redundant applet name prefix from error messages.
+ */
+
+#include "libbb.h"
+
+/* This is a NOFORK applet. Be very careful! */
+
+int yes_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int yes_main(int argc, char **argv)
+{
+       char **pp;
+
+       argv[0] = (char*)"y";
+       if (argc != 1) {
+               ++argv;
+       }
+
+       do {
+               pp = argv;
+               while (1) {
+                       fputs(*pp, stdout);
+                       if (!*++pp)
+                               break;
+                       putchar(' ');
+               }
+       } while (putchar('\n') != EOF);
+
+       bb_perror_nomsg_and_die();
+}
diff --git a/debianutils/Config.in b/debianutils/Config.in
new file mode 100644 (file)
index 0000000..8deb38f
--- /dev/null
@@ -0,0 +1,84 @@
+#
+# 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 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 FEATURE_RUN_PARTS_FANCY
+       bool "Support additional arguments"
+       default n
+       depends on RUN_PARTS
+       help
+         Support additional options:
+         -l --list print the names of the all matching files (not
+                   limited to executables), but don't actually run them.
+
+config START_STOP_DAEMON
+       bool "start-stop-daemon"
+       default n
+       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 n
+       depends on START_STOP_DAEMON
+       help
+         Support additional arguments.
+         -o|--oknodo ignored since we exit with 0 anyway
+         -v|--verbose
+         -N|--nicelevel N
+
+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 (file)
index 0000000..bcf6126
--- /dev/null
@@ -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_MKTEMP)            += mktemp.o
+lib-$(CONFIG_PIPE_PROGRESS)     += pipe_progress.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 (file)
index 0000000..0dcb1e8
--- /dev/null
@@ -0,0 +1,69 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Mini mktemp implementation for busybox
+ *
+ *
+ * Copyright (C) 2000 by Daniel Jacobowitz
+ * Written by Daniel Jacobowitz <dan@debian.org>
+ *
+ * Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
+ */
+
+/* Coreutils 6.12 man page says:
+ *        mktemp [OPTION]... [TEMPLATE]
+ * Create a temporary file or directory, safely, and print its name. If
+ * TEMPLATE is not specified, use tmp.XXXXXXXXXX.
+ * -d, --directory
+ *        create a directory, not a file
+ * -q, --quiet
+ *        suppress diagnostics about file/dir-creation failure
+ * -u, --dry-run
+ *        do not create anything; merely print a name (unsafe)
+ * --tmpdir[=DIR]
+ *        interpret TEMPLATE relative to DIR. If DIR is not specified,
+ *        use  $TMPDIR if set, else /tmp.  With this option, TEMPLATE must
+ *        not be an absolute name. Unlike with -t, TEMPLATE may contain
+ *        slashes, but even here, mktemp still creates only the final com-
+ *        ponent.
+ * -p DIR use DIR as a prefix; implies -t [deprecated]
+ * -t     interpret TEMPLATE as a single file name component, relative  to
+ *        a  directory:  $TMPDIR, if set; else the directory specified via
+ *        -p; else /tmp [deprecated]
+ */
+
+
+#include "libbb.h"
+
+int mktemp_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int mktemp_main(int argc UNUSED_PARAM, char **argv)
+{
+       const char *path;
+       char *chp;
+       unsigned opt;
+
+       opt_complementary = "?1"; /* 1 argument max */
+       opt = getopt32(argv, "dqtp:", &path);
+       chp = argv[optind] ? argv[optind] : xstrdup("tmp.XXXXXX");
+
+       if (opt & (4|8)) { /* -t and/or -p */
+               const char *dir = getenv("TMPDIR");
+               if (dir && *dir != '\0')
+                       path = dir;
+               else if (!(opt & 8)) /* no -p */
+                       path = "/tmp/";
+               /* else path comes from -p DIR */
+               chp = concat_path_file(path, chp);
+       }
+
+       if (opt & 1) { /* -d */
+               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 (file)
index 0000000..fa98e8b
--- /dev/null
@@ -0,0 +1,39 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Monitor a pipe with a simple progress display.
+ *
+ * Copyright (C) 2003 by Rob Landley <rob@landley.net>, Joey Hess
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.h"
+
+#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) MAIN_EXTERNALLY_VISIBLE;
+int pipe_progress_main(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
+{
+       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/run_parts.c b/debianutils/run_parts.c
new file mode 100644 (file)
index 0000000..7c38fa1
--- /dev/null
@@ -0,0 +1,173 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Mini run-parts implementation for busybox
+ *
+ * Copyright (C) 2007 Bernhard Reutner-Fischer
+ *
+ * Based on a older version that was in busybox which was 1k big..
+ *   Copyright (C) 2001 by Emanuele Aina <emanuele.aina@tiscali.it>
+ *
+ * Based on the Debian run-parts program, version 1.15
+ *   Copyright (C) 1996 Jeff Noxon <jeff@router.patch.net>,
+ *   Copyright (C) 1996-1999 Guy Maor <maor@debian.org>
+ *
+ *
+ * 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.
+ */
+
+#include "libbb.h"
+
+struct globals {
+       char **names;
+       int    cur;
+       char  *cmd[1];
+};
+#define G (*(struct globals*)&bb_common_bufsiz1)
+#define names (G.names)
+#define cur   (G.cur  )
+#define cmd   (G.cmd  )
+
+enum { NUM_CMD = (COMMON_BUFSIZE - sizeof(G)) / sizeof(cmd[0]) - 1 };
+
+enum {
+       OPT_r = (1 << 0),
+       OPT_a = (1 << 1),
+       OPT_u = (1 << 2),
+       OPT_t = (1 << 3),
+       OPT_l = (1 << 4) * ENABLE_FEATURE_RUN_PARTS_FANCY,
+};
+
+#if ENABLE_FEATURE_RUN_PARTS_FANCY
+#define list_mode (option_mask32 & OPT_l)
+#else
+#define list_mode 0
+#endif
+
+/* Is this a valid filename (upper/lower alpha, digits,
+ * underscores, and hyphens only?)
+ */
+static bool invalid_name(const char *c)
+{
+       c = bb_basename(c);
+
+       while (*c && (isalnum(*c) || *c == '_' || *c == '-'))
+               c++;
+
+       return *c; /* TRUE (!0) if terminating NUL is not reached */
+}
+
+static int bb_alphasort(const void *p1, const void *p2)
+{
+       int r = strcmp(*(char **) p1, *(char **) p2);
+       return (option_mask32 & OPT_r) ? -r : r;
+}
+
+static int FAST_FUNC act(const char *file, struct stat *statbuf, void *args UNUSED_PARAM, int depth)
+{
+       if (depth == 1)
+               return TRUE;
+
+       if (depth == 2
+        && (  !(statbuf->st_mode & (S_IFREG | S_IFLNK))
+           || invalid_name(file)
+           || (!list_mode && access(file, X_OK) != 0))
+       ) {
+               return SKIP;
+       }
+
+       names = xrealloc_vector(names, 4, cur);
+       names[cur++] = xstrdup(file);
+       /*names[cur] = NULL; - xrealloc_vector did it */
+
+       return TRUE;
+}
+
+#if ENABLE_FEATURE_RUN_PARTS_LONG_OPTIONS
+static const char runparts_longopts[] ALIGN1 =
+       "arg\0"     Required_argument "a"
+       "umask\0"   Required_argument "u"
+       "test\0"    No_argument       "t"
+#if ENABLE_FEATURE_RUN_PARTS_FANCY
+       "list\0"    No_argument       "l"
+       "reverse\0" No_argument       "r"
+//TODO: "verbose\0" No_argument       "v"
+#endif
+       ;
+#endif
+
+int run_parts_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int run_parts_main(int argc UNUSED_PARAM, char **argv)
+{
+       const char *umask_p = "22";
+       llist_t *arg_list = NULL;
+       unsigned n;
+       int ret;
+
+#if ENABLE_FEATURE_RUN_PARTS_LONG_OPTIONS
+       applet_long_options = runparts_longopts;
+#endif
+       /* We require exactly one argument: the directory name */
+       /* We require exactly one argument: the directory name */
+       opt_complementary = "=1:a::";
+       getopt32(argv, "ra:u:t"USE_FEATURE_RUN_PARTS_FANCY("l"), &arg_list, &umask_p);
+
+       umask(xstrtou_range(umask_p, 8, 0, 07777));
+
+       n = 1;
+       while (arg_list && n < NUM_CMD) {
+               cmd[n++] = llist_pop(&arg_list);
+       }
+       /* cmd[n] = NULL; - is already zeroed out */
+
+       /* run-parts has to sort executables by name before running them */
+
+       recursive_action(argv[optind],
+                       ACTION_RECURSE|ACTION_FOLLOWLINKS,
+                       act,            /* file action */
+                       act,            /* dir action */
+                       NULL,           /* user data */
+                       1               /* depth */
+               );
+
+       if (!names)
+               return 0;
+
+       qsort(names, cur, sizeof(char *), bb_alphasort);
+
+       n = 0;
+       while (1) {
+               char *name = *names++;
+               if (!name)
+                       break;
+               if (option_mask32 & (OPT_t | OPT_l)) {
+                       puts(name);
+                       continue;
+               }
+               cmd[0] = name;
+               ret = wait4pid(spawn(cmd));
+               if (ret == 0)
+                       continue;
+               n = 1;
+               if (ret < 0)
+                       bb_perror_msg("can't exec %s", name);
+               else /* ret > 0 */
+                       bb_error_msg("%s exited with code %d", name, ret);
+       }
+
+       return n;
+}
diff --git a/debianutils/start_stop_daemon.c b/debianutils/start_stop_daemon.c
new file mode 100644 (file)
index 0000000..ab607bd
--- /dev/null
@@ -0,0 +1,447 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Mini start-stop-daemon implementation(s) for busybox
+ *
+ * Written by Marek Michalkiewicz <marekm@i17linuxb.ists.pwr.wroc.pl>,
+ * Adapted for busybox David Kimdon <dwhedon@gordian.com>
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+/*
+This is how it is supposed to work:
+
+start-stop-daemon [OPTIONS] [--start|--stop] [[--] arguments...]
+
+One (only) of these must be given:
+        -S,--start              Start
+        -K,--stop               Stop
+
+Search for matching processes.
+If --stop is given, stop all matching processes (by sending a signal).
+If --start is given, start a new process unless a matching process was found.
+
+Options controlling process matching
+(if multiple conditions are specified, all must match):
+        -u,--user USERNAME|UID  Only consider this user's processes
+        -n,--name PROCESS_NAME  Look for processes by matching PROCESS_NAME
+                                with comm field in /proc/$PID/stat.
+                                Only basename is compared:
+                                "ntpd" == "./ntpd" == "/path/to/ntpd".
+[TODO: can PROCESS_NAME be a full pathname? Should we require full match then
+with /proc/$PID/exe or argv[0] (comm can't be matched, it never contains path)]
+        -x,--exec EXECUTABLE    Look for processes that were started with this
+                                command in /proc/$PID/cmdline.
+                                Unlike -n, we match against the full path:
+                                "ntpd" != "./ntpd" != "/path/to/ntpd"
+        -p,--pidfile PID_FILE   Look for processes with PID from this file
+
+Options which are valid for --start only:
+        -x,--exec EXECUTABLE    Program to run (1st arg of execvp). Mandatory.
+        -a,--startas NAME       argv[0] (defaults to EXECUTABLE)
+        -b,--background         Put process into background
+        -N,--nicelevel N        Add N to process' nice level
+        -c,--chuid USER[:[GRP]] Change to specified user [and group]
+        -m,--make-pidfile       Write PID to the pidfile
+                                (both -m and -p must be given!)
+
+Options which are valid for --stop only:
+        -s,--signal SIG         Signal to send (default:TERM)
+        -t,--test               Exit with status 0 if process is found
+                                (we don't actually start or stop daemons)
+
+Misc options:
+        -o,--oknodo             Exit with status 0 if nothing is done
+        -q,--quiet              Quiet
+        -v,--verbose            Verbose
+*/
+
+#include <sys/resource.h>
+
+/* Override ENABLE_FEATURE_PIDFILE */
+#define WANT_PIDFILE 1
+#include "libbb.h"
+
+struct pid_list {
+       struct pid_list *next;
+       pid_t pid;
+};
+
+enum {
+       CTX_STOP       = (1 <<  0),
+       CTX_START      = (1 <<  1),
+       OPT_BACKGROUND = (1 <<  2), // -b
+       OPT_QUIET      = (1 <<  3), // -q
+       OPT_TEST       = (1 <<  4), // -t
+       OPT_MAKEPID    = (1 <<  5), // -m
+       OPT_a          = (1 <<  6), // -a
+       OPT_n          = (1 <<  7), // -n
+       OPT_s          = (1 <<  8), // -s
+       OPT_u          = (1 <<  9), // -u
+       OPT_c          = (1 << 10), // -c
+       OPT_x          = (1 << 11), // -x
+       OPT_p          = (1 << 12), // -p
+       OPT_OKNODO     = (1 << 13) * ENABLE_FEATURE_START_STOP_DAEMON_FANCY, // -o
+       OPT_VERBOSE    = (1 << 14) * ENABLE_FEATURE_START_STOP_DAEMON_FANCY, // -v
+       OPT_NICELEVEL  = (1 << 15) * ENABLE_FEATURE_START_STOP_DAEMON_FANCY, // -N
+};
+#define QUIET (option_mask32 & OPT_QUIET)
+#define TEST  (option_mask32 & OPT_TEST)
+
+struct globals {
+       struct pid_list *found;
+       char *userspec;
+       char *cmdname;
+       char *execname;
+       char *pidfile;
+       int user_id;
+       smallint signal_nr;
+};
+#define G (*(struct globals*)&bb_common_bufsiz1)
+#define found             (G.found               )
+#define userspec          (G.userspec            )
+#define cmdname           (G.cmdname             )
+#define execname          (G.execname            )
+#define pidfile           (G.pidfile             )
+#define user_id           (G.user_id             )
+#define signal_nr         (G.signal_nr           )
+#define INIT_G() do { \
+       user_id = -1; \
+       signal_nr = 15; \
+} while (0)
+
+#ifdef OLDER_VERSION_OF_X
+/* -x,--exec EXECUTABLE
+ * Look for processes with matching /proc/$PID/exe.
+ * Match is performed using device+inode.
+ */
+static int pid_is_exec(pid_t pid)
+{
+       struct stat st;
+       char buf[sizeof("/proc//exe") + sizeof(int)*3];
+
+       sprintf(buf, "/proc/%u/exe", (unsigned)pid);
+       if (stat(buf, &st) < 0)
+               return 0;
+       if (st.st_dev == execstat.st_dev
+        && st.st_ino == execstat.st_ino)
+               return 1;
+       return 0;
+}
+#endif
+
+static int pid_is_exec(pid_t pid)
+{
+       ssize_t bytes;
+       char buf[PATH_MAX];
+
+       sprintf(buf, "/proc/%u/cmdline", (unsigned)pid);
+       bytes = open_read_close(buf, buf, sizeof(buf) - 1);
+       if (bytes > 0) {
+               buf[bytes] = '\0';
+               return strcmp(buf, execname) == 0;
+       }
+       return 0;
+}
+
+static int pid_is_name(pid_t pid)
+{
+       /* /proc/PID/stat is "PID (comm_15_bytes_max) ..." */
+       char buf[32]; /* should be enough */
+       char *p, *pe;
+
+       sprintf(buf, "/proc/%u/stat", (unsigned)pid);
+       if (open_read_close(buf, buf, sizeof(buf) - 1) < 0)
+               return 0;
+       buf[sizeof(buf) - 1] = '\0'; /* paranoia */
+       p = strchr(buf, '(');
+       if (!p)
+               return 0;
+       pe = strrchr(++p, ')');
+       if (!pe)
+               return 0;
+       *pe = '\0';
+       /* we require comm to match and to not be truncated */
+       /* in Linux, if comm is 15 chars, it may be a truncated
+        * name, so we don't allow that to match */
+       if (strlen(p) >= COMM_LEN - 1) /* COMM_LEN is 16 */
+               return 0;
+       return strcmp(p, cmdname) == 0;
+}
+
+static int pid_is_user(int pid)
+{
+       struct stat sb;
+       char buf[sizeof("/proc/") + sizeof(int)*3];
+
+       sprintf(buf, "/proc/%u", (unsigned)pid);
+       if (stat(buf, &sb) != 0)
+               return 0;
+       return (sb.st_uid == (uid_t)user_id);
+}
+
+static void check(int pid)
+{
+       struct pid_list *p;
+
+       if (execname && !pid_is_exec(pid)) {
+               return;
+       }
+       if (cmdname && !pid_is_name(pid)) {
+               return;
+       }
+       if (userspec && !pid_is_user(pid)) {
+               return;
+       }
+       p = xmalloc(sizeof(*p));
+       p->next = found;
+       p->pid = pid;
+       found = p;
+}
+
+static void do_pidfile(void)
+{
+       FILE *f;
+       unsigned pid;
+
+       f = fopen_for_read(pidfile);
+       if (f) {
+               if (fscanf(f, "%u", &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 pid;
+
+       if (pidfile) {
+               do_pidfile();
+               return;
+       }
+
+       procdir = xopendir("/proc");
+
+       pid = 0;
+       while (1) {
+               errno = 0; /* clear any previous error */
+               entry = readdir(procdir);
+// TODO: this check is too generic, it's better
+// to check for exact errno(s) which mean that we got stale entry
+               if (errno) /* Stale entry, process has died after opendir */
+                       continue;
+               if (!entry) /* EOF, no more entries */
+                       break;
+               pid = bb_strtou(entry->d_name, NULL, 10);
+               if (errno) /* NaN */
+                       continue;
+               check(pid);
+       }
+       closedir(procdir);
+       if (!pid)
+               bb_error_msg_and_die("nothing in /proc - not mounted?");
+}
+
+static int do_stop(void)
+{
+       char *what;
+       struct pid_list *p;
+       int killed = 0;
+
+       if (cmdname) {
+               if (ENABLE_FEATURE_CLEAN_UP) what = xstrdup(cmdname);
+               if (!ENABLE_FEATURE_CLEAN_UP) what = cmdname;
+       } else if (execname) {
+               if (ENABLE_FEATURE_CLEAN_UP) what = xstrdup(execname);
+               if (!ENABLE_FEATURE_CLEAN_UP) what = 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);
+               killed = -1;
+               goto ret;
+       }
+       for (p = found; p; p = p->next) {
+               if (TEST || kill(p->pid, signal_nr) == 0) {
+                       killed++;
+               } else {
+                       p->pid = 0;
+                       bb_perror_msg("warning: killing process %u", (unsigned)p->pid);
+               }
+       }
+       if (!QUIET && killed) {
+               printf("stopped %s (pid", what);
+               for (p = found; p; p = p->next)
+                       if (p->pid)
+                               printf(" %u", (unsigned)p->pid);
+               puts(")");
+       }
+ ret:
+       if (ENABLE_FEATURE_CLEAN_UP)
+               free(what);
+       return killed;
+}
+
+#if ENABLE_FEATURE_START_STOP_DAEMON_LONG_OPTIONS
+static const char start_stop_daemon_longopts[] ALIGN1 =
+       "stop\0"         No_argument       "K"
+       "start\0"        No_argument       "S"
+       "background\0"   No_argument       "b"
+       "quiet\0"        No_argument       "q"
+       "test\0"         No_argument       "t"
+       "make-pidfile\0" No_argument       "m"
+#if ENABLE_FEATURE_START_STOP_DAEMON_FANCY
+       "oknodo\0"       No_argument       "o"
+       "verbose\0"      No_argument       "v"
+       "nicelevel\0"    Required_argument "N"
+#endif
+       "startas\0"      Required_argument "a"
+       "name\0"         Required_argument "n"
+       "signal\0"       Required_argument "s"
+       "user\0"         Required_argument "u"
+       "chuid\0"        Required_argument "c"
+       "exec\0"         Required_argument "x"
+       "pidfile\0"      Required_argument "p"
+#if ENABLE_FEATURE_START_STOP_DAEMON_FANCY
+       "retry\0"        Required_argument "R"
+#endif
+       ;
+#endif
+
+int start_stop_daemon_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int start_stop_daemon_main(int argc UNUSED_PARAM, char **argv)
+{
+       unsigned opt;
+       char *signame;
+       char *startas;
+       char *chuid;
+#ifdef OLDER_VERSION_OF_X
+       struct stat execstat;
+#endif
+#if ENABLE_FEATURE_START_STOP_DAEMON_FANCY
+//     char *retry_arg = NULL;
+//     int retries = -1;
+       char *opt_N;
+#endif
+
+       INIT_G();
+
+#if ENABLE_FEATURE_START_STOP_DAEMON_LONG_OPTIONS
+       applet_long_options = start_stop_daemon_longopts;
+#endif
+
+       /* -K or -S is required; they are mutually exclusive */
+       /* -p is required if -m is given */
+       /* -xpun (at least one) is required if -K is given */
+       /* -xa (at least one) is required if -S is given */
+       /* -q turns off -v */
+       opt_complementary = "K:S:K--S:S--K:m?p:K?xpun:S?xa"
+               USE_FEATURE_START_STOP_DAEMON_FANCY("q-v");
+       opt = getopt32(argv, "KSbqtma:n:s:u:c:x:p:"
+               USE_FEATURE_START_STOP_DAEMON_FANCY("ovN:R:"),
+               &startas, &cmdname, &signame, &userspec, &chuid, &execname, &pidfile
+               USE_FEATURE_START_STOP_DAEMON_FANCY(,&opt_N)
+               /* We accept and ignore -R <param> / --retry <param> */
+               USE_FEATURE_START_STOP_DAEMON_FANCY(,NULL)
+       );
+
+       if (opt & OPT_s) {
+               signal_nr = get_signum(signame);
+               if (signal_nr < 0) bb_show_usage();
+       }
+
+       if (!(opt & OPT_a))
+               startas = execname;
+       if (!execname) /* in case -a is given and -x is not */
+               execname = startas;
+
+//     USE_FEATURE_START_STOP_DAEMON_FANCY(
+//             if (retry_arg)
+//                     retries = xatoi_u(retry_arg);
+//     )
+       //argc -= optind;
+       argv += optind;
+
+       if (userspec) {
+               user_id = bb_strtou(userspec, NULL, 10);
+               if (errno)
+                       user_id = xuname2uid(userspec);
+       }
+       /* Both start and stop need to know current processes */
+       do_procinit();
+
+       if (opt & CTX_STOP) {
+               int i = do_stop();
+               return (opt & OPT_OKNODO) ? 0 : (i <= 0);
+       }
+
+       if (found) {
+               if (!QUIET)
+                       printf("%s is already running\n%u\n", execname, (unsigned)found->pid);
+               return !(opt & OPT_OKNODO);
+       }
+
+#ifdef OLDER_VERSION_OF_X
+       if (execname)
+               xstat(execname, &execstat);
+#endif
+
+       *--argv = startas;
+       if (opt & OPT_BACKGROUND) {
+#if BB_MMU
+               bb_daemonize(DAEMON_DEVNULL_STDIO + DAEMON_CLOSE_EXTRA_FDS);
+               /* DAEMON_DEVNULL_STDIO is superfluous -
+                * it's always done by bb_daemonize() */
+#else
+               pid_t pid = vfork();
+               if (pid < 0) /* error */
+                       bb_perror_msg_and_die("vfork");
+               if (pid != 0) {
+                       /* parent */
+                       /* why _exit? the child may have changed the stack,
+                        * so "return 0" may do bad things */
+                       _exit(EXIT_SUCCESS);
+               }
+               /* Child */
+               setsid(); /* detach from controlling tty */
+               /* Redirect stdio to /dev/null, close extra FDs.
+                * We do not actually daemonize because of DAEMON_ONLY_SANITIZE */
+               bb_daemonize_or_rexec(DAEMON_DEVNULL_STDIO
+                       + DAEMON_CLOSE_EXTRA_FDS
+                       + DAEMON_ONLY_SANITIZE,
+                       NULL /* argv, unused */ );
+#endif
+       }
+       if (opt & OPT_MAKEPID) {
+               /* User wants _us_ to make the pidfile */
+               write_pidfile(pidfile);
+       }
+       if (opt & OPT_c) {
+               struct bb_uidgid_t ugid = { -1, -1 };
+               parse_chown_usergroup_or_die(&ugid, chuid);
+               if (ugid.gid != (gid_t) -1) xsetgid(ugid.gid);
+               if (ugid.uid != (uid_t) -1) xsetuid(ugid.uid);
+       }
+#if ENABLE_FEATURE_START_STOP_DAEMON_FANCY
+       if (opt & 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
+       execvp(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 (file)
index 0000000..748e6dc
--- /dev/null
@@ -0,0 +1,90 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Which implementation for busybox
+ *
+ * Copyright (C) 1999-2004 by Erik Andersen <andersen@codepoet.org>
+ * Copyright (C) 2006 Gabriel Somlo <somlo at cmu.edu>
+ *
+ * Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
+ *
+ * Based on which from debianutils
+ */
+
+#include "libbb.h"
+
+int which_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int which_main(int argc UNUSED_PARAM, char **argv)
+{
+       USE_DESKTOP(int opt;)
+       int status = EXIT_SUCCESS;
+       char *path;
+       char *p;
+
+       opt_complementary = "-1"; /* at least one argument */
+       USE_DESKTOP(opt =) getopt32(argv, "a");
+       argv += optind;
+
+       /* This matches what is seen on e.g. ubuntu.
+        * "which" there is a shell script. */
+       path = getenv("PATH");
+       if (!path) {
+               path = (char*)bb_PATH_root_path;
+               putenv(path);
+               path += 5; /* skip "PATH=" */
+       }
+
+       do {
+#if ENABLE_DESKTOP
+/* Much bloat just to support -a */
+               if (strchr(*argv, '/')) {
+                       if (execable_file(*argv)) {
+                               puts(*argv);
+                               continue;
+                       }
+                       status = EXIT_FAILURE;
+               } else {
+                       char *path2 = xstrdup(path);
+                       char *tmp = path2;
+
+                       p = find_execable(*argv, &tmp);
+                       if (!p)
+                               status = EXIT_FAILURE;
+                       else {
+ print:
+                               puts(p);
+                               free(p);
+                               if (opt) {
+                                       /* -a: show matches in all PATH components */
+                                       if (tmp) {
+                                               p = find_execable(*argv, &tmp);
+                                               if (p)
+                                                       goto print;
+                                       }
+                               }
+                       }
+                       free(path2);
+               }
+#else
+/* Just ignoring -a */
+               if (strchr(*argv, '/')) {
+                       if (execable_file(*argv)) {
+                               puts(*argv);
+                               continue;
+                       }
+               } else {
+                       char *path2 = xstrdup(path);
+                       char *tmp = path2;
+                       p = find_execable(*argv, &tmp);
+                       free(path2);
+                       if (p) {
+                               puts(p);
+                               free(p);
+                               continue;
+                       }
+               }
+               status = EXIT_FAILURE;
+#endif
+       } while (*(++argv) != NULL);
+
+       fflush_stdout_and_exit(status);
+}
diff --git a/docs/Serial-Programming-HOWTO.txt b/docs/Serial-Programming-HOWTO.txt
new file mode 100644 (file)
index 0000000..0dfc8aa
--- /dev/null
@@ -0,0 +1,424 @@
+Downloaded from http://www.lafn.org/~dave/linux/Serial-Programming-HOWTO.txt
+Seems to be somewhat old, but contains useful bits for getty.c hacking
+============================================================================
+
+  The Linux Serial Programming HOWTO, Part 1 of 2
+  By Vernon C. Hoxie
+  v2.0 10 September 1999
+
+  This document describes how to program communications with devices
+  over a serial port on a Linux box.
+  ______________________________________________________________________
+
+  Table of Contents
+
+  1. Copyright
+
+  2. Introduction
+
+  3. Opening
+
+  4. Commands
+
+  5. Changing Baud Rates
+
+  6. Additional Control Calls
+
+     6.1 Sending a "break".
+     6.2 Hardware flow control.
+     6.3 Flushing I/O buffers.
+
+  7. Modem control
+
+  8. Process Groups
+
+     8.1 Sessions
+     8.2 Process Groups
+     8.3 Controlling Terminal
+        8.3.1 Get the foreground group process id.
+        8.3.2 Set the foreground process group id of a terminal.
+        8.3.3 Get process group id.
+
+  9. Lockfiles
+
+  10. Additional Information
+
+  11. Feedback
+
+  ______________________________________________________________________
+
+  1.  Copyright
+
+  The Linux Serial-Programming-HOWTO is copyright (C) 1997 by Vernon
+  Hoxie.  Linux HOWTO documents may be reproduced and distributed in
+  whole or in part, in any medium physical or electronic, as long as
+  this copyright notice is retained on all copies. Commercial
+  redistribution is allowed and encouraged; however, the author would
+  like to be notified of any such distributions.
+
+  All translations, derivative works, or aggregate works incorporating
+  this Linux HOWTO document must be covered under this copyright notice.
+  That is, you may not produce a derivative work from this HOWTO and
+  impose additional restrictions on its distribution.
+
+  This version is a complete rewrite of the previous Serial-Programming-
+  HOWTO  by Peter H. Baumann,  <mailto:Peter.Baumann@dlr.de>
+
+  2.  Introduction
+
+  This HOWTO will attempt to give hints about how to write a program
+  which needs to access a serial port.  Its principal focus will be on
+  the Linux implementation and what the meaning of the various library
+  functions available.
+
+  Someone asked about which of several sequences of operations was
+  right.  There is no absolute right way to accomplish an outcome.  The
+  options available are too numerous.  If your sequences produces the
+  desired results, then that is the right way for you.  Another
+  programmer may select another set of options and get the same results.
+  His method is right for him.
+
+  Neither of these methods may operate properly with some other
+  implementation of UNIX.  It is strange that many of the concepts which
+  were implemented in the SYSV version have been dumped.  Because UNIX
+  was developed by AT&T and much code has been generated on those
+  concepts, the AT&T version should be the standard to which others
+  should emulate.
+
+  Now the standard is POSIX.
+
+  It was once stated that the popularity of UNIX and C was that they
+  were created by programmers for programmers.  Not by scholars who
+  insist on purity of style in deference to results and simplicity of
+  use.  Not by committees with people who have diverse personal or
+  proprietary agenda.  Now ANSI and POSIX have strayed from those
+  original clear and simply concepts.
+
+  3.  Opening
+
+  The various serial devices are opened just as any other file.
+  Although, the fopen(3) command may be used, the plain open(2) is
+  preferred.  This call returns the file descriptor which is required
+  for the various commands that configure the interface.
+
+  Open(2) has the format:
+
+       #include <fcntl.h>
+       int open(char *path, int flags, [int mode]);
+
+  In addition to the obvious O_RDWR, O_WRONLY and O_RDONLY, two
+  additional flags are available.  These are O_NONBLOCK and O_NOCTTY.
+  Other flags listed in the open(2) manual page are not applicable to
+  serial devices.
+
+  Normally, a serial device opens in "blocking" mode.  This means that
+  the open() will not return until the Carrier Detect line from the port
+  is active, e.g. modem, is active.  When opened with the O_NONBLOCK
+  flag set, the open() will return immediately regardless of the status
+  of the DCD line.  The "blocking" mode also affects the read() call.
+
+  The fcntl(2) command can be used to change the O_NONBLOCK flag anytime
+  after the device has been opened.
+
+  The device driver and the data passing through it are controlled
+  according to settings in the struct termios.  This structure is
+  defined in "/usr/include/termios.h".  In the Linux tree, further
+  reference is made to "/usr/include/asm/termbits.h".
+  In blocking mode, a read(2) will block until data is available or a
+  signal is received.  It is still subject to state of the ICANON flag.
+
+  When the termios.c_lflag ICANON bit is set, input data is collected
+  into strings until a NL, EOF or EOL character is received.  You can
+  define these in the termios.c_cc[] array.  Also, ERASE and KILL
+  characters will operate on the incoming data before it is delivered to
+  the user.
+
+  In non-canonical mode, incoming data is quanitified by use of the
+  c_cc[VMIN and c_cc[VTIME] values in termios.c_cc[].
+
+  Some programmers use the select() call to detect the completion of a
+  read().  This is not the best way of checking for incoming data.
+  Select() is part of the SOCKETS scheme and too complex for most
+  applications.
+
+  A full explanation of the fields of the termios structure is contained
+  in termios(7) of the Users Manual.  A version is included in Part 2 of
+  this HOWTO document.
+
+  4.  Commands
+
+  Changes to the struct termios are made by retrieving the current
+  settings, making the desired changes and transmitting the modified
+  structure back to the kernel.
+
+  The historic means of communicating with the kernel was by use of the
+  ioctl(fd, COMMAND, arg) system call.  Then the purists in the
+  computer industry decided that this was not genetically consistent.
+  Their argument was that the argument changed its stripes.  Sometimes
+  it was an int, sometimes it was a pointer to int and other times it
+  was a pointer to struct termios.  Then there were those times it was
+  empty or NULL.  These variations are dependent upon the COMMAND.
+
+  As a alternative, the tc* series of functions were concocted.
+
+  These are:
+
+       int tcgetattr(int filedes, struct termios *termios_p);
+       int tcsetattr(int filedes, int optional_actions,
+                     const struct termios *termios_p);
+
+  instead of:
+
+       int ioctl(int filedes, int command,
+                 struct termios *termios_p);
+
+  where command is TCGETS or one of TCSETS, TCSETSW or TCSETSF.
+
+  The TCSETS command is comparable to the TCSANOW optional_action for
+  the tc* version.  These direct the kernel to adopt the changes
+  immediately.  Other pairs are:
+
+    command   optional_action   Meaning
+    TCSETSW   TCSADRAIN         Change after all output has drained.
+    TCSETSF   TCSAFLUSH         Change after all output has drained
+                                then discard any input characters
+                                not read.
+
+  Since the return code from either the ioctl(2) or the tcsetattr(2)
+  commands only indicate that the command was processed by the kernel.
+  These do not indicate whether or not the changes were actually
+  accomplished.  Either of these commands should be followed by a call
+  to:
+
+       ioctl(fd, TCGETS, &new_termios);
+
+  or:
+
+       tcgetattr(fd, &new_termios);
+
+  A user function which makes changes to the termios structure should
+  define two struct termios variables.  One of these variables should
+  contain the desired configuration.  The other should contain a copy of
+  the kernels version.  Then after the desired configuration has been
+  sent to the kernel, another call should be made to retrieve the
+  kernels version.  Then the two compared.
+
+  Here is an example of how to add RTS/CTS flow control:
+
+       struct termios my_termios;
+       struct termios new_termios;
+
+       tcgetattr(fd, &my_termios);
+       my_termios.c_flag |= CRTSCTS;
+       tcsetattr(fd, TCSANOW, &my_termios);
+       tcgetattr(fd, &new_termios);
+       if (memcmp(my_termios, new_termios,
+            sizeof(my_termios)) != 0) {
+           /* do some error handling */
+       }
+
+  5.  Changing Baud Rates
+
+  With Linux, the baud rate can be changed using a technique similar to
+  add/delete RTS/CTS.
+
+  struct termios my_termios;
+  struct termios new_termios;
+
+  tcgetattr(fd, &my_termios);
+  my_termios.c_flag &= ~CBAUD;
+  my_termios.c_flag |= B19200;
+  tcsetattr(fd, TCSANOW, &my_termios);
+  tcgetattr(fd, &new_termios);
+  if (memcmp(my_termios, new_termios,
+       sizeof(my_termios)) != 0) {
+      /* do some error handling */
+  }
+
+  POSIX adds another method.  They define:
+
+       speed_t cfgetispeed(const struct termios *termios_p);
+       speed_t cfgetospeed(const struct termios *termios_p);
+
+  library calls to extract the current input or output speed from the
+  struct termios pointed to with *termio_p.  This is a variable defined
+  in the calling process.  In practice, the data contained in this
+  termios, should be obtained by the tcgetattr() call or an ioctl() call
+  using the TCGETS command.
+
+  The companion library calls are:
+
+       int cfsetispeed(struct termios *termios_p, speed_t speed);
+       int cfsetospeed(struct termios *termios_p, speed_t speed);
+
+  which are used to change the value of the baud rate in the locally
+  defined *termios_p.  Following either of these calls, either a call to
+  tcsetattr() or ioctl() with one of TCSETS, TCSETSW or TCSETSF as the
+  command to transmit the change to the kernel.
+
+  The cf* commands are preferred for portability.  Some weird Unices use
+  a considerably different format of termios.
+
+  Most implementations of Linux use only the input speed for both input
+  and output.  These functions are defined in the application program by
+  reference to <termios.h>.  In reality, they are in
+  /usr/include/asm/termbits.h.
+
+  6.  Additional Control Calls
+
+  6.1.  Sending a "break".
+
+       int ioctl(fd, TCSBRK, int arg);
+       int tcsendbreak(fd, int arg);
+
+  Send a break:  Here the action differs between the conventional
+  ioctl() call and the POSIX call.  For the conventional call, an arg of
+  '0' sets the break control line of the UART for 0.25 seconds.  For the
+  POSIX command, the break line is set for arg times 0.1 seconds.
+
+  6.2.  Hardware flow control.
+
+       int ioctl(fd, TCXONC, int action);
+       int tcflow(fd, int action);
+
+  The action flags are:
+
+  o  TCOOFF  0  suspend output
+
+  o  TCOON   1  restart output
+
+  o  TCIOFF  2  transmit STOP character to suspend input
+
+  o  TCION   3  transmit START character to restart input
+
+  6.3.  Flushing I/O buffers.
+
+       int ioctl(fd, TCFLSH, queue_selector);
+       int tcflush(fd, queue_selector);
+
+  The queue_selector flags are:
+
+  o  TCIFLUSH  0  flush any data not yet read from the input buffer
+
+  o  TCOFLUSH  1  flush any data written to the output buffer but not
+     yet transmitted
+
+  o  TCIOFLUSH 2  flush both buffers
+
+  7.  Modem control
+
+  The hardware modem control lines can be monitored or modified by the
+  ioctl(2) system call.  A set of comparable tc* calls apparently do not
+  exist.  The form of this call is:
+
+       int ioctl(fd, COMMAND, (int *)flags);
+
+  The COMMANDS and their action are:
+
+  o  TIOCMBIS  turn on control lines depending upon which bits are set
+     in flags.
+
+  o  TIOCMBIC  turn off control lines depending upon which bits are
+     unset in flags.
+  o  TIOCMGET  the appropriate bits are set in flags according to the
+     current status
+
+  o  TIOCMSET  the state of the UART is changed according to which bits
+     are set/unset in 'flags'
+
+     The bit pattern of flags refer to the following control lines:
+
+  o  TIOCM_LE      Line enable
+
+  o  TIOCM_DTR     Data Terminal Ready
+
+  o  TIOCM_RTS     Request to send
+
+  o  TIOCM_ST      Secondary transmit
+
+  o  TIOCM_SR      Secondary receive
+
+  o  TIOCM_CTS     Clear to send
+
+  o  TIOCM_CAR     Carrier detect
+
+  o  TIOCM_RNG     Ring
+
+  o  TIOCM_DSR     Data set ready
+
+  It should be noted that some of these bits are controlled by the modem
+  and the UART cannot change them but their status can be sensed by
+  TIOCMGET.  Also, most Personal Computers do not provide hardware for
+  secondary transmit and receive.
+
+  There are also a pair of ioctl() to monitor these lines.  They are
+  undocumented as far as I have learned.  The commands are TIOCMIWAIT
+  and TCIOGICOUNT.  They also differ between versions of the Linux
+  kernel.
+
+  See the lines.c file in my "serial_suite" for an example of how these
+  can be used see  <ftp://scicom.alphacd.com/pub/linux/serial_suite>
+
+  8.  Process Groups
+
+  8.1.  Sessions
+
+  8.2.  Process Groups
+
+  Any newly created process inherits the Process Group of its creator.
+  The Process Group leader has the same PID as PGID.
+
+  8.3.  Controlling Terminal
+
+  There are a series of ioctl(2) and tc*(2) calls which can be used to
+  monitor or to change the process group to which the device is
+  attached.
+
+  8.3.1.  Get the foreground group process id.
+
+  If there is no foreground group, a number not representing an existing
+  process group is returned.  On error, a -1 is returned and errno is
+  set.
+
+       int ioctl(fd, TIOCGPGRP, (pid_t *)pid);
+       int tcgetpgrp(fd, (pid_t *)pid);
+
+  8.3.2.  Set the foreground process group id of a terminal.
+
+  The fd must be the controlling terminal and be associated with the
+  session of the calling process.
+
+       int ioctl(fd, TIOCSPGRP, (pid_t *)pid);
+       int tcsetpgrp(fd, (pid_t *)pid);
+
+  8.3.3.  Get process group id.
+
+       int ioctl(fd, TIOCGPGRP, &(pid_t)pid);
+       int tcgetpgrp(fd, &(pid_t)pid);
+
+  9.  Lockfiles
+
+  Any process which accesses a serial device should first check for the
+  existence of lock file for the desired device.  If such a lock lock
+  file exists, this means that the device may be in use by another
+  process.
+
+  Check my "libdevlocks-x.x.tgz" at
+  <ftp://scicom.alphacdc.com/pub/linux> for an example of how these lock
+  files should be utilized.
+
+  10.  Additional Information
+
+  Check out my "serial_suite.tgz" for more information about programming
+  the serial ports at   <mailto:vern@zebra.alphacdc.com>.  There some
+  examples and some blurbs about setting up modems and comments about
+  some general considerations.
+
+  11.  Feedback
+
+  Please send me any corrections, questions, comments, suggestions, or
+  additional material. I would like to improve this HOWTO!  Tell me
+  exactly what you don't understand, or what could be clearer.  You can
+  reach me at  <mailto:vern@zebra.alphacdc.com> via email.  Please
+  include the version number of the Serial-Programming-HOWTO when
+  writing.
diff --git a/docs/autodocifier.pl b/docs/autodocifier.pl
new file mode 100755 (executable)
index 0000000..576e312
--- /dev/null
@@ -0,0 +1,307 @@
+#!/usr/bin/perl -w
+# vi: set sw=4 ts=4:
+
+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;
+                       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.
+# Perhaps we can use some of APPLET_ODDNAME from include/applets.h ?
+       $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;
+       $name =~ s/ether_wake/ether-wake/g;
+
+       # make options bold
+       my $trivial = $usage->{trivial};
+       if (!defined $usage->{trivial}) {
+               $trivial = "";
+       } else {
+               $trivial =~ s/(?<!\w)(-\w+)/B<$1>/sxg;
+       }
+       my @f0 =
+               map { $_ !~ /^\s/ && s/(?<!\w)(-\w+)/B<$1>/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<usage.h>,
+F<docs/busybox.pod>.  This was tedious and error-prone, so it was
+decided that F<usage.h> 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<autodocifier.pl> is one such script.  It is based on a script by
+Erik Andersen <andersen@codepoet.org> which was in turn based on a
+script by Mark Whitley <markw@codepoet.org>
+
+=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<trivial>
+
+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<REQUIRED>
+
+=item B<full>
+
+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<REQUIRED>
+
+=item B<notes>
+
+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<usage.h>.  I<OPTIONAL>
+
+=item B<example>
+
+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<OPTIONAL>
+
+=back
+
+=head1 FILES
+
+F<usage.h>
+
+=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 <b@ax9.org>
+
+=cut
+
diff --git a/docs/busybox.net/FAQ.html b/docs/busybox.net/FAQ.html
new file mode 100644 (file)
index 0000000..7ed1394
--- /dev/null
@@ -0,0 +1,1146 @@
+<!--#include file="header.html" -->
+
+<h3>Frequently Asked Questions</h3>
+
+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,
+
+<h2>General questions</h2>
+<ol>
+<li><a href="#getting_started">How can I get started using BusyBox?</a></li>
+<li><a href="#configure">How do I configure busybox?</a></li>
+<li><a href="#build">How do I build BusyBox with a cross-compiler?</a></li>
+<li><a href="#build_system">How do I build a BusyBox-based system?</a></li>
+<li><a href="#kernel">Which Linux kernel versions are supported?</a></li>
+<li><a href="#arch">Which architectures does BusyBox run on?</a></li>
+<li><a href="#libc">Which C libraries are supported?</a></li>
+<li><a href="#commercial">Can I include BusyBox as part of the software on my device?</a></li>
+<li><a href="#external">Where can I find other small utilities since busybox does not include the features I want?</a></li>
+<li><a href="#demanding">I demand that you to add &lt;favorite feature&gt; 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 <em>Right Now</em>!</a></li>
+<li><a href="#helpme">I need help with BusyBox!  What should I do?</a></li>
+<li><a href="#contracts">I need you to add &lt;favorite feature&gt;!  Are the BusyBox developers willing to be paid in order to fix bugs or add in &lt;favorite feature&gt;?  Are you willing to provide support contracts?</a></li>
+</ol>
+
+<h2>Troubleshooting</h2>
+<ol>
+<li><a href="#bugs">I think I found a bug in BusyBox!  What should I do?!</a></li>
+<li><a href="#backporting">I'm using an ancient version from the dawn of time and something's broken.  Can you backport fixes for free?</a></li>
+<li><a href="#init">Busybox init isn't working!</a></li>
+<li><a href="#sed">I can't configure busybox on my system.</a></li>
+<li><a href="#job_control">Why do I keep getting "sh: can't access tty; job control turned off" errors?  Why doesn't Control-C work within my shell?</a></li>
+</ol>
+
+<h2>Misc. questions</h2>
+<ol>
+  <li><a href="#tz">How do I change the time zone in busybox?</a></li>
+</ol>
+
+<h2>Programming questions</h2>
+<ol>
+  <li><a href="#goals">What are the goals of busybox?</a></li>
+  <li><a href="#design">What is the design of busybox?</a></li>
+  <li><a href="#source">How is the source code organized?</a>
+    <ul>
+    <li><a href="#source_applets">The applet directories.</a></li>
+    <li><a href="#source_libbb">The busybox shared library (libbb)</a></li>
+    </ul>
+  </li>
+  <li><a href="#optimize">I want to make busybox even smaller, how do I go about it?</a></li>
+  <li><a href="#adding">Adding an applet to busybox</a></li>
+  <li><a href="#standards">What standards does busybox adhere to?</a></li>
+  <li><a href="#portability">Portability.</a></li>
+  <li><a href="#tips">Tips and tricks.</a>
+    <ul>
+    <li><a href="#tips_encrypted_passwords">Encrypted Passwords</a></li>
+    <li><a href="#tips_vfork">Fork and vfork</a></li>
+    <li><a href="#tips_short_read">Short reads and writes</a></li>
+    <li><a href="#tips_memory">Memory used by relocatable code, PIC, and static linking.</a></li>
+    <li><a href="#tips_kernel_headers">Including Linux kernel headers.</a></li>
+    </ul>
+  </li>
+  <li><a href="#who">Who are the BusyBox developers?</a></li>
+</ol>
+
+
+<hr />
+<h1>General questions</h1>
+
+<hr />
+<h2><a name="getting_started">How can I get started using BusyBox?</a></h2>
+
+<p> If you just want to try out busybox without installing it, download the
+    tarball, extract it, run "make defconfig", and then run "make".
+</p>
+<p>
+    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.
+</p>
+<p>
+    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.
+</p>
+<p>
+    BusyBox also has a feature called the
+    <a name="standalone_shell">"standalone shell"</a>, 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.)
+</p>
+<p>
+    A typical indication that you set CONFIG_BUSYBOX_EXEC_PATH to proc but
+    forgot to mount proc is:
+<pre>
+$ /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
+</pre>
+
+<hr />
+<h2><a name="configure">How do I configure busybox?</a></h2>
+
+<p> 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".
+</p>
+
+<p> 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.
+</p>
+
+<p>The most important busybox configurators are:</p>
+
+<ul>
+<li><p>make <b>defconfig</b> - 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.</p></li>
+<li><p>make <b>allnoconfig</b> - 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.</p></li>
+<li><p>make <b>menuconfig</b> - Interactively modify a .config file through a
+multi-level menu interface.  Use this after one of the previous two.</p></li>
+</ul>
+
+<p>Some other configuration options are:</p>
+<ul>
+<li><p>make <b>oldconfig</b> - Update an old .config file for a newer version
+of busybox.</p></li>
+<li><p>make <b>allyesconfig</b> - 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.</p></li>
+<li><p>make <b>allbareconfig</b> - Select all applets but disable all sub-features
+within each applet.  More build coverage testing.</p></li>
+<li><p>make <b>randconfig</b> - Create a random configuration for test purposes.</p></li>
+</ul>
+
+<p> Menuconfig modifies your .config file through an interactive menu where you can enable or disable
+    busybox features, and get help about each feature.
+
+<p>
+    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.)
+</p>
+
+<hr />
+<h2><a name="build">How do I build BusyBox with a cross-compiler?</a></h2>
+
+<p>
+   To build busybox with a cross-compiler, specify CROSS_COMPILE=&lt;prefix&gt;.
+</p>
+<p>
+   CROSS_COMPILE specifies the prefix used for all executables used
+   during compilation. Only gcc and related binutils executables
+   are prefixed with $(CROSS_COMPILE) in the makefiles.
+   CROSS_COMPILE can be set on the command line:
+</p>
+<pre>
+   make CROSS_COMPILE=arm-linux-uclibcgnueabi-
+</pre>
+<p>
+   Alternatively CROSS_COMPILE can be set in the environment.
+   Default value for CROSS_COMPILE is not to prefix executables.
+</p>
+<p>
+   To store the cross-compiler in your .config, set the variable
+   CONFIG_CROSS_COMPILER_PREFIX accordingly in menuconfig or by
+   editing the .config file.
+</p>
+
+<hr />
+<h2><a name="build_system">How do I build a BusyBox-based system?</a></h2>
+
+<p>
+    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.
+</p>
+<p>
+    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 <a href="http://www.tldp.org/HOWTO/Bootdisk-HOWTO/">Linux
+    BootDisk Howto</a>, or for a more theoretical approach try
+    <a href="http://www.tldp.org/HOWTO/From-PowerUp-To-Bash-Prompt-HOWTO.html">From
+    PowerUp to Bash Prompt</a>.
+</p>
+<p>
+    To learn how to build a working Linux system entirely from source code,
+    the place to go is the <a href="http://www.linuxfromscratch.org/">Linux
+    From Scratch</a> project.  They have an entire book of step-by-step
+    instructions you can
+    <a href="http://www.linuxfromscratch.org/lfs/view/stable/">read online</a>
+    or
+    <a href="http://www.linuxfromscratch.org/lfs/downloads/stable/">download</a>.
+    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.)
+</p>
+<p>
+    If you want an automated yet customizable system builder which produces
+    a BusyBox and uClibc based system, try
+    <a href="http://buildroot.uclibc.org/">buildroot</a>, 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.
+</p>
+
+<hr />
+<h2><a name="kernel">Which Linux kernel versions are supported?</a></h2>
+
+<p>
+    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).
+</p>
+
+<hr />
+<h2><a name="arch">Which architectures does BusyBox run on?</a></h2>
+
+<p>
+    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.
+</p>
+<p>
+    With 2.6.x kernels, module loading support should work on all architectures.
+</p>
+
+<hr />
+<h2><a name="libc">Which C libraries are supported?</a></h2>
+
+<p>
+    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.
+</p>
+<p>
+    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.
+</p>
+<p>
+    Some BusyBox applets have been built and run under a combination
+    of newlib and libgloss (see
+    <a href="http://www.busybox.net/lists/busybox/2005-March/013759.html">this thread</a>).
+    This is still experimental, but may be supported in a future release.
+</p>
+
+<hr />
+<h2><a name="commercial">Can I include BusyBox as part of the software on my device?</a></h2>
+
+<p>
+    Yes.  As long as you <a href="http://busybox.net/license.html">fully comply
+    with the generous terms of the GPL BusyBox license</a> you can ship BusyBox
+    as part of the software on your device.
+</p>
+
+<hr />
+<h2><a name="external">Where can I find other small utilities since busybox
+       does not include the features i want?</a></h2>
+
+<p>
+       we maintain such a <a href="tinyutils.html">list</a> on this site!
+</p>
+
+<hr />
+<h2><a name="demanding">I demand that you to add &lt;favorite feature&gt; 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 <em>Right Now</em>!</a></h2>
+
+<p>
+    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.
+
+<hr />
+<h2><a name="helpme">I need help with BusyBox!  What should I do?</a></h2>
+
+<p>
+    If you find that you need help with BusyBox, you can ask for help on the
+    BusyBox mailing list at busybox@busybox.net.</p>
+
+<p> In addition to the mailing list, Erik Andersen (andersee), Manuel Nova
+    (mjn3), Rob Landley (landley), Mike Frysinger (SpanKY),
+    Bernhard Reutner-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
+    <a href="http://ibot.Rikers.org/%23uclibc/">web archive of
+    daily logs of the #uclibc IRC channel</a> going back to 2002.
+</p>
+
+<p>
+    <b>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.</b>
+</p>
+
+<p>
+    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.
+</p>
+
+<hr />
+<h2><a name="contracts">I need you to add &lt;favorite feature&gt;!  Are the BusyBox developers willing to be paid in order to fix bugs or add in &lt;favorite feature&gt;?  Are you willing to provide support contracts?</a></h2>
+
+<p>
+    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.
+</p>
+
+<hr />
+<h1>Troubleshooting</h1>
+
+<hr />
+<h2><a name="bugs">I think I found a bug in BusyBox!  What should I do?</a></h2>
+
+<p>
+    If you simply need help with using or configuring BusyBox, please submit a
+    detailed description of your problem to the BusyBox mailing list at <a
+    href="mailto:busybox@busybox.net">busybox@busybox.net</a>.
+    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...
+</p>
+
+<p>
+    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
+    <a href="https://bugs.busybox.net/">BusyBox Bug
+    and Patch Tracking System</a> to submit a detailed explanation and we'll
+    get to it as soon as we can.
+</p>
+
+<p>
+    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.)
+</p>
+
+<hr />
+<h2><a name="backporting">I'm using an ancient version from the dawn of time and something's broken.  Can you backport fixes for free?</a></h2>
+
+<p>Variants of this one get asked a lot.</p>
+
+<p>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.</p>
+
+<p>If you're building BusyBox 0.50 with uClibc 0.9.19 and gcc 1.27 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.</p>
+
+<p>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.</p>
+
+<p>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.</p>
+
+<p>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.</p>
+
+<p>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.</p>
+
+<hr />
+<h2><a name="init">Busybox init isn't working!</a></h2>
+
+<p>
+    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:
+</p>
+<pre>
+#include &lt;stdio.h&gt;
+
+int main(int argc, char *argv)
+{
+  printf("Hello world!\n");
+  sleep(999999999);
+}
+</pre>
+
+<p>
+    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.
+</p>
+
+<p>
+    Once you've got it working statically linked, try getting it to work
+    dynamically linked.  Then read the FAQ entry <a href="#build_system">How
+    do I build a BusyBox-based system?</a>, and the
+    <a href="/downloads/BusyBox.html#item_init">documentation for BusyBox
+    init</a>.
+</p>
+
+<hr />
+<h2><a name="sed">I can't configure busybox on my system.</a></h2>
+
+<p>
+    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:
+</p>
+
+<pre>
+  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"
+</pre>
+
+<p>Then you can run "make defconfig" or "make menuconfig" normally.</p>
+
+<hr />
+<h2><a name="job_control">Why do I keep getting "sh: can't access tty; job control turned off" errors?  Why doesn't Control-C work within my shell?</a></h2>
+
+<p>
+    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 <em>REALLY</em> 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...
+</p>
+
+<hr />
+<h1>Misc. questions</h1>
+
+<hr />
+<h2><a name="tz">How do I change the time zone in busybox?</a></h2>
+
+<p>Busybox has nothing to do with the timezone. Please consult your libc
+documentation. (<a href="http://google.com/search?q=uclibc+glibc+timezone">http://google.com/search?q=uclibc+glibc+timezone</a>).</p>
+
+<hr />
+<h1>Development</h1>
+
+<hr />
+<h2><a name="goals">What are the goals of busybox?</a></h2>
+
+<p>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 <a href="#standards">standards
+compliant</a>, minimize run-time memory usage (heap and stack), run fast, and
+take over the world.</p>
+
+<hr />
+<h2><a name="design">What is the design of busybox?</a></h2>
+
+<p>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 <a href="FAQ.html#getting_started">getting started</a> in the
+FAQ for more information on usage, and <a href="BusyBox.html">the
+busybox documentation</a> for a list of symlink names and what they do.)
+
+<p>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.</p>
+
+<p>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.</p>
+
+<a name="source"></a>
+
+<hr />
+<h2><a name="source_applets">The applet directories</a></h2>
+
+<p>The directory "applets" contains the busybox startup code (applets.c and
+busybox.c), and several subdirectories containing the code for the individual
+applets.</p>
+
+<p>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_and_exit() 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.</p>
+
+<p>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().</p>
+
+<p>Busybox applets may also be invoked through the multiplexor applet
+"busybox" (see busybox_main() in libbb/appletlib.c), and through the
+standalone shell (grep for STANDALONE_SHELL in applets/shell/*.c).
+See <a href="FAQ.html#getting_started">getting started</a> in the
+FAQ for more information on these alternate usage mechanisms, which are
+just different ways to reach the relevant APPLET_main() function.</p>
+
+<p>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.</p>
+
+<p>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
+<a href="#adding">adding an applet to busybox</a> for more
+information.</p>
+
+<hr />
+<h2><a name="source_libbb"><b>libbb</b></a></h2>
+
+<p>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.</p>
+
+<p>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.</p>
+
+<hr />
+<h2><a name="optimize">I want to make busybox even smaller, how do I go about it?</a></h2>
+
+<p>
+       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).
+</p>
+
+<p>     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.)
+</p>
+
+<p>     The <b>"make bloatcheck"</b> 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 <b>"make baseline"</b>.
+        (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.
+</p>
+<p>
+        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).
+</p>
+<p>
+       The <b>"make sizes"</b> 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.
+</p>
+<p>
+        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.
+</p>
+
+<hr />
+<h2><a name="adding">Adding an applet to busybox</a></h2>
+
+<p>To add a new applet to busybox, first pick a name for the applet and
+a corresponding CONFIG_NAME.  Then do this:</p>
+
+<ul>
+<li>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.</li>
+
+<li>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.</li>
+
+<li>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.)</li>
+
+<li>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.)</li>
+
+<li>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).</li>
+
+<li>Run menuconfig, switch your applet on, compile, test, and fix the
+bugs.  Be sure to try both "allyesconfig" and "allnoconfig" (and
+"allbareconfig" if relevant).</li>
+
+</ul>
+
+<hr />
+<h2><a name="standards">What standards does busybox adhere to?</a></h2>
+
+<p>The standard we're paying attention to is the "Shell and Utilities"
+portion of the <a href="http://www.opengroup.org/onlinepubs/009695399/">Open
+Group Base Standards</a> (also known as the Single Unix Specification version
+3 or SUSv3).  Note that paying attention isn't necessarily the same thing as
+following it.</p>
+
+<p>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.</p>
+
+<p>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.</p>
+
+<p>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.</p>
+
+<hr />
+<h2><a name="portability">Portability.</a></h2>
+
+<p>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.</p>
+
+<p>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.</p>
+
+<p>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.</p>
+
+<p>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
+<a href="http://www.selenic.com/linux-tiny/">trimming
+down the 2.6 kernel</a>.)  Older gcc versions than that are uninteresting since
+we now use c99 features, although
+<a href="http://fabrice.bellard.free.fr/tcc/">tcc</a> might be worth a
+look.</p>
+
+<p>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).</p>
+
+<p>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: <a href="http://www.telly.org/86open/">the 86open
+project</a>.  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 <a
+href="http://www-03.ibm.com/servers/aix/products/aixos/linux/index.html">AIX</a>, <a
+href="http://www.sun.com/software/solaris/ds/linux_interop.jsp#3">Solaris</a>, and
+<a href="http://www.onlamp.com/pub/a/bsd/2000/03/17/linuxapps.html">FreeBSD</a>)
+have all either grown Linux support or folded.</p>
+
+<p>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.</p>
+
+<p>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.</p>
+
+<hr />
+<h2><a name="tips">Programming tips and tricks.</a></h2>
+
+<p>Various things busybox uses that aren't particularly well documented
+elsewhere.</p>
+
+<hr />
+<h2><a name="tips_encrypted_passwords">Encrypted Passwords</a></h2>
+
+<p>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:</p>
+<pre>
+  <b>$type$salt$encrypted_password</b>
+</pre>
+
+<p>The "type" indicates which encryption algorithm to use: 1 for MD5 and 2 for SHA1.</p>
+
+<p>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.)</p>
+
+<p>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.</p>
+
+<p>The third field is the encrypted password (plus the salt).  For md5 this
+is 22 bytes.</p>
+
+<p>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.</p>
+
+<p>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).</p>
+
+<hr />
+<h2><a name="tips_vfork">Fork and vfork</a></h2>
+
+<p>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.)</p>
+
+<p>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.</p>
+
+<p>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.</p>
+
+<p>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.)</p>
+
+<p>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.)</p>
+
+<p>So with a primitive or missing MMU, fork() is just not a good idea.</p>
+
+<p>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.</p>
+
+<p>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.)
+
+<p>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.</p>
+
+<p>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.)</p>
+
+<p>(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.)</p>
+
+<hr />
+<h2><a name="tips_sort_read">Short reads and writes</a></h2>
+
+<p>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:</p>
+
+<pre>while true; do echo hello; sleep 1; done | tee out.txt</pre>
+
+<p>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.)</p>
+
+<p>As for short writes, play around with two processes piping data to each
+other on the command line (cat bigfile | gzip &gt; 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...)</p>
+
+<p>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 <a href="http://www.faqs.org/rfcs/rfc896.html">rfc 896
+for Nagle's algorithm</a>, 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.)</p>
+
+<hr />
+<h2><a name="tips_memory">Memory used by relocatable code, PIC, and static linking.</a></h2>
+
+<p>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.</p>
+
+<p>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.</p>
+
+<p>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.</p>
+
+<p>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.</p>
+
+<p>For more on this topic, here's Rich Felker:</p>
+<blockquote>
+<p>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.</p>
+
+<p>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.</p>
+
+<p>The ELF dynamic linker has its own data segment, adding one or more
+dirty pages. I believe it also performs relocations on itself.</p>
+
+<p>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.</p>
+
+<p>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.</p>
+
+<p>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).</p>
+</blockquote>
+
+<hr />
+<h2><a name="tips_kernel_headers"></a>Including kernel headers</h2>
+
+<p>The &quot;linux&quot; or &quot;asm&quot; 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.</p>
+
+<p>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 &lt;linux/posix_types.h&gt; 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.</p>
+
+<p>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
+&lt;linux/version.h&gt; 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.</p>
+
+<p>The BusyBox developers spent <u>two years</u> 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 &quot;my_dev_t.h&quot;, which #includes &lt;linux/posix_types.h&gt;
+and &lt;linux/version.h&gt; just like we do.  There simply is no alternative.
+</p>
+
+<p>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.</p>
+
+<hr />
+<h2><a name="who">Who are the BusyBox developers?</a></h2>
+
+<p>The following login accounts currently exist on busybox.net.  (I.E. these
+people can commit <a href="http://busybox.net/downloads/patches/">patches</a>
+into subversion for the BusyBox, uClibc, and buildroot projects.)</p>
+
+<pre>
+aldot     :Bernhard Reutner-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
+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
+vda       :Denys Vlasenko     - BusyBox maintainer
+</pre>
+
+<p>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
+&lt;robwen at gmail.com&gt; asked Google and recovered the names:</p>
+
+<pre>
+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
+</pre>
+
+
+<br>
+<br>
+<br>
+
+<!--#include file="footer.html" -->
diff --git a/docs/busybox.net/about.html b/docs/busybox.net/about.html
new file mode 100644 (file)
index 0000000..35809c3
--- /dev/null
@@ -0,0 +1,24 @@
+<!--#include file="header.html" -->
+
+<h3>BusyBox: The Swiss Army Knife of Embedded Linux</h3>
+
+<p>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.</p>
+
+<p>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.</p>
+
+<p>BusyBox is maintained by
+<a href="mailto:vda.linux@googlemail.com">Denys Vlasenko</a>,
+and licensed under the <a href="license.html">GNU GENERAL PUBLIC LICENSE</a>
+version 2.</p>
+
+<!--#include file="footer.html" -->
diff --git a/docs/busybox.net/busybox-growth.ps b/docs/busybox.net/busybox-growth.ps
new file mode 100644 (file)
index 0000000..2379def
--- /dev/null
@@ -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 (file)
index 0000000..3974756
--- /dev/null
@@ -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 <rob@landley.net> 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 (file)
index 0000000..cdb68b7
--- /dev/null
@@ -0,0 +1,69 @@
+<!--#include file="header.html" -->
+
+<h3>Morris Dancing</h3>
+
+<p>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 (<a href="mailto:rob@landley.net">Rob
+Landley</a> for BusyBox, or <a href="mailto:andersen@codepoet.org">Erik
+Andersen</a> for uClibc) for an account.</p>
+
+<p>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.</p>
+
+<p>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 <b>ssh-keygen -t dsa</b> and hitting enter at the prompts.  This
+will create the files <b>~/.ssh/id_dsa</b> and <b>~/.ssh/id_dsa.pub</b>
+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 <b>.ssh/authorized_keys</b> file.)</p>
+
+<p>Note that if you would prefer to keep your communications with us
+private, you can encrypt your email using
+<a href="http://landley.net/pubkey.gpg">Rob's public key</a> or
+<a href="http://www.codepoet.org/andersen/erik/gpg.asc">Erik's public
+key</a>.</p>
+
+<p>Once you are setup with an account, you will need to use your account to
+checkout a copy of BusyBox from Subversion:</p>
+
+<p><b>svn checkout svn+ssh://username@busybox.net/svn/trunk/busybox</b></p>
+<p>or</p>
+<p><b>svn checkout svn+ssh://username@uclibc.org/svn/trunk/uclibc</b></p>
+
+<p>You must change <em>username</em> to your own username, or omit
+it if it's the same as your local username.</p>
+
+<p>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:</p>
+
+<b><pre>
+svn diff
+svn status
+svn revert
+EDITOR=vi svn commit
+svn log -v -r PREV:HEAD
+svn help
+</pre></b>
+
+<p>For additional detail on how to use Subversion, please visit the
+<a href="http://subversion.tigris.org/">the Subversion website</a>.
+You might also want to read online or buy a copy of <a
+href="http://svnbook.red-bean.com/">the Subversion Book</a>...</p>
+
+<p>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".</p>
+
+<!--#include file="footer.html" -->
diff --git a/docs/busybox.net/download.html b/docs/busybox.net/download.html
new file mode 100644 (file)
index 0000000..34195b6
--- /dev/null
@@ -0,0 +1,60 @@
+<!--#include file="header.html" -->
+
+
+
+<h3>Download</h3>
+
+<p>
+Source for the latest release can always be
+downloaded from <a href="downloads/">http://www.busybox.net/downloads/</a>.
+
+<p>
+Each 1.x branch has bug fix releases after initial 1.x.0 release.
+Also there are patches on top of latest bug fix release.
+<p>
+Latest releases and patch directories for each branch:
+<br>
+<a href="http://busybox.net/downloads/busybox-1.10.1.tar.bz2">1.10.1</a>,
+<a href="http://busybox.net/downloads/fixes-1.10.1/">patches</a>,
+<br>
+<a href="http://busybox.net/downloads/busybox-1.9.2.tar.bz2">1.9.2</a>,
+<a href="http://busybox.net/downloads/fixes-1.9.2/">patches</a>,
+<br>
+<a href="http://busybox.net/downloads/busybox-1.8.3.tar.bz2">1.8.3</a>,
+<a href="http://busybox.net/downloads/fixes-1.8.3/">patches</a>,
+<br>
+<a href="http://busybox.net/downloads/busybox-1.7.5.tar.bz2">1.7.5</a>,
+<a href="http://busybox.net/downloads/fixes-1.7.5/">patches</a>,
+<br>
+<a href="http://busybox.net/downloads/busybox-1.6.2.tar.bz2">1.6.2</a>,
+<a href="http://busybox.net/downloads/fixes-1.6.2/">patches</a>,
+<br>
+<a href="http://busybox.net/downloads/busybox-1.5.2.tar.bz2">1.5.2</a>,
+<a href="http://busybox.net/downloads/fixes-1.5.2/">patches</a>,
+<br>
+<a href="http://busybox.net/downloads/busybox-1.4.2.tar.bz2">1.4.2</a>,
+<a href="http://busybox.net/downloads/fixes-1.4.2/">patches</a>,
+<br>
+<a href="http://busybox.net/downloads/busybox-1.3.2.tar.bz2">1.3.2</a>,
+<a href="http://busybox.net/downloads/fixes-1.3.2/">patches</a>.
+
+<p>
+You can also obtain <a href="downloads/snapshots/">Daily Snapshots</a> of
+the latest development source tree for those wishing to follow BusyBox development,
+but cannot or do not wish to use Subversion (svn).
+
+<ul>
+       <li> Click here to <a href="http://sources.busybox.net/index.py/trunk/busybox/">browse the source tree</a>.
+       </li>
+
+       <li>Anonymous <a href="subversion.html">Subversion access</a> is available.
+       </li>
+
+       <li>For those that are actively contributing obtaining
+               <a href="developer.html">Subversion read/write access</a> is also possible.
+       </li>
+
+</ul>
+
+<!--#include file="footer.html" -->
+
diff --git a/docs/busybox.net/fix.html b/docs/busybox.net/fix.html
new file mode 100644 (file)
index 0000000..7bd7fe0
--- /dev/null
@@ -0,0 +1,15 @@
+<!--#include file="header.html" -->
+
+<h3>How to get your patch added to "hot fixes"</h3>
+
+<p> If you found a regression or severe bug in busybox, and you have a patch
+    for it, and you want to see it added to "hot fixes", please rediff your
+    patch against corresponding unmodified busybox source and send it to
+    <a href="mailto:busybox@busybox.net">the mailing list</a>.
+</p>
+
+<br>
+<br>
+<br>
+
+<!--#include file="footer.html" -->
diff --git a/docs/busybox.net/footer.html b/docs/busybox.net/footer.html
new file mode 100644 (file)
index 0000000..0667092
--- /dev/null
@@ -0,0 +1,51 @@
+<!-- Footer -->
+
+
+    </td>
+    </tr>
+    </table>
+
+<hr />
+
+
+    <table width="100%">
+       <tr>
+           <td width="60%">
+               <font face="arial, helvetica, sans-serif" size="-1">
+               <!--div style="font-family: arial, helvetica, sans-serif; font-size: 80%;" -->
+                   <a href="/copyright.txt">Copyright &copy; 1999-2008 Erik Andersen</a>
+                   <br>
+                   Mail all comments, insults, suggestions and bribes to
+                   <br>
+                   Denys Vlasenko <a href="mailto:vda.linux@googlemail.com">vda.linux@googlemail.com</a><br>
+               </font>
+               <!--/div-->
+           </td>
+
+           <td>
+               <a href="http://www.vim.org/"><img border="0"
+               width="88" height="31"
+               src="images/written.in.vi.png"
+               alt="This site created with the vi editor" /></a>
+           </td>
+
+           <td>
+               <a href="http://osuosl.org/"><img border="0"
+               width="114" height="63"
+               src="images/osuosl.png"
+               alt="This site is kindly hosted by OSL" /></a>
+           </td>
+<!--
+           <td>
+               <a href="http://validator.w3.org/check?uri=referer"><img
+               border="0" height="31" width="88"
+               src="images/valid-html401.png"
+               alt="Valid HTML" /></a>
+           </td>
+-->
+       </tr>
+    </table>
+
+  </body>
+</html>
+
diff --git a/docs/busybox.net/header.html b/docs/busybox.net/header.html
new file mode 100644 (file)
index 0000000..9641d8c
--- /dev/null
@@ -0,0 +1,91 @@
+<!DOCTYPE HTML PUBLIC '-//W3C//DTD HTML 4.01 Transitional//EN'>
+
+<html>
+  <head>
+    <meta http-equiv='Content-Type' content='text/html; charset=iso-8859-1'>
+    <title>BusyBox</title>
+    <style type="text/css">
+     body {
+      background-color: #DEE2DE;
+      color: #000000;
+      font-family: lucida, helvetica, arial;
+      font-size: 100%;
+     }
+     :link { color: #660000 }
+     :visited { color: #660000 }
+     :active { color: #660000 }
+     td.c2 {font-family: arial, helvetica, sans-serif; font-size: 80%}
+     td.c1 {font-family: lucida, helvetica; font-size: 248%}
+    </style>
+  </head>
+
+  <body>
+    <!--basefont face="lucida, helvetica, arial" size="3"-->
+
+<table border="0" cellpadding="0" cellspacing="0">
+<tr>
+<td>
+    <div class="c3">
+      <table border="0" cellspacing="1" cellpadding="2">
+        <tr>
+          <td class="c1">BUSYBOX</td>
+        </tr>
+      </table>
+    </div>
+
+  <a href="/"><img src="images/busybox1.png" alt="BusyBox" border="0" /></a><br>
+</td>
+</tr>
+
+<tr>
+
+<td valign="top">
+    <b>About</b>
+    <ul>
+        <li><a href="about.html">About BusyBox</a></li>
+        <li><a href="screenshot.html">Screenshot</a></li>
+        <li><a href="news.html">Announcements</a></li>
+    </ul>
+    <b>Documentation</b>
+    <ul>
+        <li><a href="FAQ.html">FAQ</a></li>
+        <li><a href="downloads/BusyBox.html">Command Help</a></li>
+        <li><a href="downloads/README">README</a></li>
+    </ul>
+    <b>Get BusyBox</b>
+    <ul>
+        <li><a href="download.html">Download Source</a></li>
+        <li><a href="license.html">License</a></li>
+        <li><a href="products.html">Products</a></li>
+    </ul>
+    <b>Development</b>
+    <ul>
+        <li><a href="http://sources.busybox.net/index.py/trunk/busybox/">Browse Source</a></li>
+        <li><a href="subversion.html">Source Control</a></li>
+        <!--li><a href="/downloads/patches/recent.html">Recent Changes</a></li-->
+        <li><a href="lists.html">Mailing Lists</a></li>
+        <li><a href="https://bugs.busybox.net/">Bug Tracking</a></li>
+    </ul>
+    <p><b>Links</b>
+    <ul>
+        <li><a href="links.html">Related Sites</a></li>
+        <li><a href="tinyutils.html">Tiny Utilities</a></li>
+        <li><a href="sponsors.html">Sponsors</a></li>
+    </ul>
+    <p><b>Developer Pages</b>
+    <ul>
+        <li><a href="http://busybox.net/~landley/">Rob</a></li>
+        <li><a href="http://busybox.net/~aldot/">Bernhard</a></li>
+       <li><a href="http://busybox.net/~vda/">Denys</a>
+        <br>- <a href="http://busybox.net/~vda/resume/denys_vlasenko.htm">resume</a>
+        <br>- <a href="http://busybox.net/~vda/mboot/">mboot</a>
+        <br>- <a href="http://busybox.net/~vda/linld/">linld</a>
+        <br>- <a href="http://busybox.net/~vda/init_vs_runsv.html">init must die</a>
+        <br>- <a href="http://busybox.net/~vda/no_ifup.txt">no ifup</a>
+        <br>- <a href="http://busybox.net/~vda/unscd/">unscd</a>
+       </li>
+    </ul>
+</td>
+
+<td valign="top">
+
diff --git a/docs/busybox.net/images/back.png b/docs/busybox.net/images/back.png
new file mode 100644 (file)
index 0000000..7992386
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 (file)
index 0000000..37edc96
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 (file)
index 0000000..b1eb92f
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 (file)
index 0000000..4d3126a
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 (file)
index 0000000..abf8f06
Binary files /dev/null and b/docs/busybox.net/images/busybox2.jpg differ
diff --git a/docs/busybox.net/images/busybox2.png b/docs/busybox.net/images/busybox2.png
new file mode 100644 (file)
index 0000000..a7460b6
Binary files /dev/null and b/docs/busybox.net/images/busybox2.png differ
diff --git a/docs/busybox.net/images/busybox3.jpg b/docs/busybox.net/images/busybox3.jpg
new file mode 100644 (file)
index 0000000..0fab84c
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 (file)
index 0000000..1d633ce
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 (file)
index 0000000..b55621b
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 (file)
index 0000000..c0883cd
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 (file)
index 0000000..d583140
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 (file)
index 0000000..9bad949
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 (file)
index 0000000..b00b500
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 (file)
index 0000000..b102450
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 (file)
index 0000000..6034f89
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 (file)
index 0000000..ec9bc0c
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 (file)
index 0000000..c5e9402
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 (file)
index 0000000..84f59bc
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 (file)
index 0000000..1bab6b0
--- /dev/null
@@ -0,0 +1 @@
+<!--#include file="news.html" -->
diff --git a/docs/busybox.net/license.html b/docs/busybox.net/license.html
new file mode 100644 (file)
index 0000000..2a4c51d
--- /dev/null
@@ -0,0 +1,99 @@
+<!--#include file="header.html" -->
+
+<p>
+<h3><a name="license">BusyBox is licensed under the GNU General Public License, version 2</a></h3>
+
+<p>BusyBox is licensed under <a href="http://www.gnu.org/licenses/gpl.html#SEC1">the
+GNU General Public License</a> 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.)</p>
+
+<p>A complete copy of the license text is included in the file LICENSE in
+the BusyBox source code.</p>
+
+<p><a href="products.html">Anyone thinking of shipping BusyBox as part of a
+product</a> 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 <a href="http://www.gnu.org/licenses/gpl-faq.html">Frequently
+Asked Questions about the GPL</a>.</p>
+
+<p>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.</p>
+
+<h3><a name="version">A note on GPL versions</a></h3>
+
+<p>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.</p>
+
+<p>Older versions of BusyBox (versions 1.2.2 and earlier, up through about svn
+16112) included variants of the recommended
+&quot;GPL version 2 or (at your option) later versions&quot; 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 that 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.</p>
+
+<p>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
+&quot;may be redistributed as per the Linux copyright&quot; (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.)</p>
+
+<p>To summarize: every version of BusyBox may be distributed under the terms of
+GPL version 2.  New versions (after 1.2.2) may <b>only</b> 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.</p>
+
+<h3><a name="enforce">License enforcement</a></h3>
+
+<p>BusyBox's copyrights are enforced by the <a
+href="http://www.softwarefreedom.org/">Software Freedom Law Center</a>
+(you can contact them at gpl@busybox.net), which
+&quot;accepts primary responsibility for enforcement of US copyrights on the
+software... and coordinates international copyright enforcement efforts for
+such works as necessary.&quot;  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 <a href="shame.html">The Hall of Shame</a>,
+but these days we find it much more effective to hand them over to the
+lawyers.)</p>
+
+<p>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.</p>
+
+<h3><a name="good">A Good Example</a></h3>
+
+<p>These days, <a href="http://www.linksys.com/">Linksys</a> 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
+<a href="http://www.linksys.com/servlet/Satellite?c=L_Content_C1&amp;childpagename=US%2FLayout&amp;cid=1115416836002&amp;pagename=Linksys%2FCommon%2FVisitorWrapper">
+distributing the firmware for their WRT54G Router.</a>
+Following their example would be a fine way to ensure that you
+have also fulfilled your licensing obligations.</p>
+
+<!--#include file="footer.html" -->
+
diff --git a/docs/busybox.net/links.html b/docs/busybox.net/links.html
new file mode 100644 (file)
index 0000000..14ad8d1
--- /dev/null
@@ -0,0 +1,19 @@
+<!--#include file="header.html" -->
+
+<h3>Related Sites</h3>
+
+<br><a href="http://uclibc.org/">uClibc.org</a>
+<br><a href="http://cxx.uclibc.org/">uClibc++</a>
+<!--br><a href="http://udhcp.busybox.net/">udhcp</a -->
+<br><a href="http://buildroot.uclibc.org/">buildroot</a>
+<br><a href="http://www.scratchbox.org/">Scratchbox</a>
+<br><a href="http://openembedded.org/">OpenEmbedded</a>
+<br><a href="http://www.ucdot.org/">uCdot</a>
+<br><a href="http://www.linuxdevices.com/">LinuxDevices</a>
+<br><a href="http://slashdot.org/">Slashdot</a>
+<br><a href="http://freshmeat.net/">Freshmeat</a>
+<br><a href="http://linuxtoday.com/">Linux Today</a>
+<br><a href="http://lwn.net/">Linux Weekly News</a>
+<br><a href="http://www.tldp.org/HOWTO">Linux HOWTOs</a>
+
+<!--#include file="footer.html" -->
diff --git a/docs/busybox.net/lists.html b/docs/busybox.net/lists.html
new file mode 100644 (file)
index 0000000..29c2f74
--- /dev/null
@@ -0,0 +1,46 @@
+<!--#include file="header.html" -->
+
+
+<!-- Begin Introduction section -->
+
+<h3>Mailing List Information</h3>
+BusyBox has a <a href="/lists/busybox/">mailing list</a> for discussion and
+development.  You can subscribe by visiting
+<a href="http://lists.busybox.net/mailman/listinfo/busybox">this page</a>.
+Only subscribers to the BusyBox mailing list are allowed to post
+to this list.
+
+<p>
+There is also a mailing list for <a href="/lists/busybox-cvs/">active developers</a>
+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
+<a href="http://lists.busybox.net/mailman/listinfo/busybox-cvs">this page</a>.
+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...
+
+<p>
+
+
+<h3>Search the List Archives</h3>
+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...
+<p>
+
+<center>
+<form method="GET" action="http://www.google.com/custom">
+<input type="hidden" name="domains" value="busybox.net">
+<input type="hidden" name="sitesearch" value="busybox.net">
+<input type="text" name="q" size="31" maxlength="255" value="">
+<br>
+<input type="submit" name="sa" value="search the mailing list archives">
+<br>
+<a href="http://www.google.com"><img src="http://www.google.com/logos/Logo_25wht.gif" border="0" alt="Google" height="32" width="75" align="middle"></a>
+<br>
+</form>
+</center>
+
+
+
+<!--#include file="footer.html" -->
diff --git a/docs/busybox.net/news.html b/docs/busybox.net/news.html
new file mode 100644 (file)
index 0000000..e0a8138
--- /dev/null
@@ -0,0 +1,504 @@
+<!--#include file="header.html" -->
+
+<ul>
+
+  <li>
+    <p>We want to thank the following companies which are providing support for the BusyBox project:
+      <ul>
+        <li>AOE media, a <a href="http://www.aoemedia.com/typo3-development.html">
+        TYPO3 development agency</a> contributes financially.</li>
+        <li><a href="http://www.analog.com/en/">Analog Devices, Inc.</a> provided
+        a <a href="http://docs.blackfin.uclinux.org/doku.php?id=bf537_quick_start">
+        Blackfin development board</a> free of charge.
+        <a href="http://www.analog.com/blackfin">Blackfin</a>
+        is a NOMMU processor, and its availability for testing is invaluable.
+        If you are an embedded device developer,
+        please note that Analog Devices has entire Linux distribution available
+        for download for this board. Visit
+        <a href="http://blackfin.uclinux.org/">http://blackfin.uclinux.org/</a>
+        for more information.
+        </li>
+      </ul>
+    </p>
+  </li>
+
+  <li><b>15 April 2009 -- BusyBox 1.14.0 (unstable), BusyBox 1.13.4 (stable)</b>
+    <p><a href="http://busybox.net/downloads/busybox-1.14.0.tar.bz2">BusyBox 1.14.0</a>.
+    (<a href="http://sources.busybox.net/index.py/branches/busybox_1_14_stable/">svn</a>,
+    <a href="http://busybox.net/downloads/fixes-1.14.0/">patches</a>,
+    <a href="http://busybox.net/fix.html">how to add a patch</a>)</p>
+    <p><a href="http://busybox.net/downloads/busybox-1.13.4.tar.bz2">BusyBox 1.13.4</a>.
+    (<a href="http://sources.busybox.net/index.py/branches/busybox_1_13_stable/">svn</a>,
+    <a href="http://busybox.net/downloads/fixes-1.13.4/">patches</a>,
+    <a href="http://busybox.net/fix.html">how to add a patch</a>)</p>
+
+    <p>Sizes of busybox-1.13.4 and busybox-1.14.0 (with equivalent config, static uclibc build):<pre>
+   text    data     bss     dec     hex filename
+ 785501     483    7036  793020   c19bc busybox.1.13.4/busybox
+ 788380     467    6960  795807   c249f busybox.1.14.0/busybox
+  15361       0       0   15361    3c01 busybox.1.13.4/shell/hush.o
+  20724       0       0   20724    50f4 busybox.1.14.0/shell/hush.o
+</pre>
+    <p>Most of growth is in hush. The rest shrank a bit.
+
+    <p>New applets:
+      <ul>
+       <li>flash_eraseall: by Sebastian Andrzej Siewior (bigeasy AT linutronix.de)</li>
+       <li>acpid, mkdosfs, tunctl: by Vladimir</li>
+       <li>ftpd: by Adam Tkac (vonsch AT gmail.com)</li>
+       <li>timeout: by Roberto Foglietta</li>
+       <li>ionice: adapted from Linux kernel example by Walter Harms</li>
+       <li>mkpasswd: synonym to cryptpw. mkpasswd is in Debian, OTOH cryptpw was added to busybox earlier. Trying to make both camps happy by making those two applets just aliases. They are command-line compatible</li>
+      </ul>
+
+    <p>Changes since previous release:
+
+    <p>lash and msh are deprecated, please migrate to hush.
+
+    <p>hush had many, many fixes and features added: here documents, arithmetic evaluation, function support, and all this works on NOMMU too, safely, including 100kb-sized `command` and here documents. Here document support, arithmetic evaluation, improved ${var} operations, other fixes are by Mike Frysinger (vapier AT gentoo.org).
+
+    <p>Other changes:
+      <ul>
+       <li>libbb: unify concurrent-safe update of /etc/{passwd,group,[g]shadow}. By Tito (farmatito AT tiscali.it)</li>
+       <li>libbb/sha{1,256,512}: major code shrink</li>
+       <li>libbb/lineedit: make history saving/loading concurrent-safe</li>
+       <li>libbb: shrink linked list ops. By xmaks AT email.cz</li>
+       <li>libbb: str2sockaddr should accept [IPv6] addr without port - wget 'ftp://[::1]/file' needs that to work</li>
+       <li>libbb: make bb_info_msg do atomic, unbuffered writes</li>
+       <li>util-linux/volumeid: abort early on read failures. Should help with probing missing fdd's</li>
+       <li>util-linux/volumeid: fix bug 249 "findfs finds the wrong partition"</li>
+       <li>adduser: allow adding to group 0; don't _create_ /etc/shadow, only append data if it exists</li>
+       <li>ash: fix mishandled ^C and SIGINT (several cases)</li>
+       <li>ash: fix "ash -c 'exec 1&gt;&amp;0'" complaining that fd 0 is busy</li>
+       <li>ash: fix $IFS handling in read. Closes bug 235</li>
+       <li>ash: fix a case where we were closing wrong descriptor</li>
+       <li>ash: fix bad interaction between ash -c '....&amp;' and bash compat</li>
+       <li>ash: fix miscalculation of memory needed for eval tree. Found by Timo Teras (timo.teras AT iki.fi)</li>
+       <li>ash: make dot command search current directory first, as bash does</li>
+       <li>ash: printf builtin with no arguments should not exit</li>
+       <li>awk: fix long field separators case. By Ian Wienand (ianw AT vmware.com)</li>
+       <li>awk: in BEGIN section $0 should be "", not "0"</li>
+       <li>awk: make "struct global" hack more robust wrt alignment. Closes bug 131</li>
+       <li>brctl: fix compilation on 2.4.x kernels</li>
+       <li>chat: treat timeout more correctly</li>
+       <li>chat: recognize RECORD directive</li>
+       <li>cksum, head, printenv: report errors via exitcode</li>
+       <li>cpio: add -p, -0 and -L options</li>
+       <li>crond, crontab: make cron directory location configurable</li>
+       <li>crond: correct more of logfile to 0666 (as usual, umask allows user to remove unwanted bits)</li>
+       <li>crond: put tasks in separate process groups</li>
+       <li>dc: fix the "base 2" patch omission of base not being set</li>
+       <li>depmod: accept and ignore -r. Linux kernel build needs this</li>
+       <li>depmod: fix -b option. By timo.teras AT iki.fi</li>
+       <li>udhcpc: fix a problem where we don't open listening socket fast enough</li>
+       <li>udhcpc: stop filtering environment passed to the script</li>
+       <li>udhcpd: disable option to have absolute lease times in lease file (that does not work with dumpleases)</li>
+       <li>udhcpd: write 64-bit current time in lease file. Without it, determination of remaining lease time is unreliable</li>
+       <li>udhcpd: remember hostnames of clients</li>
+       <li>dumpleases: fix -a option, use recorded current time in lease file, show hostnames</li>
+       <li>dnsd: fix a number of bugs. Ideas by Ming-Ching Tiew (mctiew AT yahoo.com)</li>
+       <li>dpkg: better and shorter code to compare versions. Taken from "official" dpkg by Eugene T. Bordenkircher (eugebo AT gmail.com)</li>
+       <li>du: fix "du /dir /dir" case</li>
+       <li>env: support -uVAR=VAL</li>
+       <li>expand, unexpand: fix incorrect expansion in some cases</li>
+       <li>expr: a bit more robust handling of regexps with groups. Closes bug 87</li>
+       <li>find: support --mindepth</li>
+       <li>getty: make speed 0 mean "don't change speed", stop using non-portable way of setting speeds</li>
+       <li>grep: support -z</li>
+       <li>gzip: fix gzip -dc bug caused by using stale getopt state</li>
+       <li>httpd: set $HOST to Host: header value. By Tobias Poschwatta (tp AT fonz.de)</li>
+       <li>ifupdown: allow options to udhcpc to be configurable from .config</li>
+       <li>init: do not eat last char in messages; do not print duplicate "init:" prefix to syslog</li>
+       <li>init: fix a bug where on reload order of entries might be wrong</li>
+       <li>init: major improvement in documentation and signal handling. Lots of nasty, but hard to trip, races are fixed</li>
+       <li>init: reinstate proper handling of !ENABLE_FEATURE_USE_INITTAB</li>
+       <li>init: remove wait loop on restart, it may be dangerous</li>
+       <li>init: test for vt terminal with VT_OPENQRY, assume that anything else is TERM=vt102, not TERM=linux. Closes bug 195</li>
+       <li>inotifyd: add x, o, and u events</li>
+       <li>inotifyd: fix buffer overflow and "unreaped zombies" problem</li>
+       <li>inotifyd: conserve resourses by closing unused inotify descriptors</li>
+       <li>insmod/modprobe: do not pass NULL to kernel as module parameter</li>
+       <li>ip: in "ip rule add from all table 1", "all" is taken as 0.0.0.0/32, whereas "any" and "default" would be 0.0.0.0/0. They must be all 0.0.0.0/0. Closes bug 57</li>
+       <li>iproute: fix ipXXX utilities trying to parse their applet name as their 1st parameter</li>
+       <li>klogctl: fix a problem where we don't terminate read data with '\0' and then misinterpret it</li>
+       <li>ls: do not follow links with -s. Closes bug 33</li>
+       <li>ls: implement -Q and -g (-g was accepted but ignored)</li>
+       <li>ls: make readlink error to not disrupt output (try ls -l /proc/self/fd)</li>
+       <li>man: better check for duplicated MANPATH</li>
+       <li>mdev: add support for - ("dont stop here") char</li>
+       <li>mdev: if /sys/class/block exists, don't scan /sys/block</li>
+       <li>mdev: ignore events with "$SUBSYSTEM" == "firmware" &amp;&amp; "$ACTION" == "remove"</li>
+       <li>mdev: provide $SUBSYSTEM. By Vladimir</li>
+       <li>modprobe/insmod for 2.4: support compressed modules. By Guenter (lists AT gknw.net)</li>
+       <li>modprobe: rework/speedup by Timo Teras (timo.teras AT iki.fi)</li>
+       <li>modutils-24: fix bad interaction of xzalloc with xrealloc_vector</li>
+       <li>mount: support "-O option", stop trying to mount swap partitions, fix CIFS support</li>
+       <li>mountpoint: add -n option. By Vladimir</li>
+       <li>nslookup: allow usage of IPv6 addresses or hostnames for DNS server name; allow for port specification. Tested to work on uclibc svn: "nslookup google.com [::1]:5353". glibc + IPv6 address of DNS server still does not work</li>
+       <li>popmaildir: fix several grave bugs with using memory past end of malloc block</li>
+       <li>printf: fix 1.12.0 breakage (from %*d fix), it was misinterpreting "*"</li>
+       <li>printf: make integer format strings print long long-sized values</li>
+       <li>rmmod: fix bug 263 "modutils/rmmod can't remove modules with dash in name on 2.4 kernels"</li>
+       <li>sendmail: document and fix usage of fd #4, fix check for helper failure</li>
+       <li>sendmail: update by Vladimir</li>
+       <li>seq: add -w support. By Natanael Copa</li>
+       <li>seq: add support for "-s separator"</li>
+       <li>stat: make stat -f show filesystem "ID:" as coreutils does</li>
+       <li>sysctl: fix another corner case with "dots and slashes"</li>
+       <li>sysctl: fix broken -p [file]. Closes bug 231</li>
+       <li>sysctl: support recursing if name is a directory: "sysctl net.ipv4.conf". Patch by xmaks AT email.cz</li>
+       <li>syslogd: make signal handling syncronous</li>
+       <li>syslogd: create logfile with 0666 (affected by umask as usual), not 0600</li>
+       <li>tail: fix tail +N syntax not working. Closes bug 221</li>
+       <li>tar: do not change new tarfile's mode, GNU tar doesn't do it</li>
+       <li>tar: support GNU tar's "base256" encoding</li>
+       <li>telnetd: correctly output 0xff char</li>
+       <li>telnetd: do not advertise TELNET_LFLOW, we do not support it properly</li>
+       <li>tftp: when we infer local name from remote (-r [/]path/path/file), strip path. This mimics wget and is generally more intuitive</li>
+       <li>timeout: fix parsing of -t NUM on MMU</li>
+       <li>top: make it work again on 2.4 kernels. Closes bug 125</li>
+       <li>tr: fix overflow in expand and complement, fix stop after [:class:], fix handling of ranges and [x]'s</li>
+       <li>tr: support -C as synonym to -c, support [:xdigit:]</li>
+       <li>traceroute: rewrite. Do not emit raw IP packets, instead send UDP or ICMP packets and rely on the kernel to form IP headers, select source IP and interface</li>
+       <li>uname: add support for -i and -o, fix printing of unknown -p value with -a option, support long options</li>
+       <li>unzip: fix thinko with le/be conv and size. Closes bug 129</li>
+       <li>vi: fix several instances of major goof: when text grows, text[] might get reallocated! We were keeping around pointers to old place</li>
+       <li>vi: speedup and code shrink. By Walter Harms</li>
+       <li>wget: --post-data support. By Harald Kuthe (harald-tuxbox AT arcor.de)</li>
+       <li>wget: fix --header handling, more robust EINTR detection</li>
+      </ul>
+    </p>
+
+  <li><b>8 March 2009 -- BusyBox 1.13.3 (stable)</b>
+    <p><a href="http://busybox.net/downloads/busybox-1.13.3.tar.bz2">BusyBox 1.13.3</a>.
+    (<a href="http://sources.busybox.net/index.py/branches/busybox_1_13_stable/">svn</a>,
+    <a href="http://busybox.net/downloads/fixes-1.13.3/">patches</a>,
+    <a href="http://busybox.net/fix.html">how to add a patch</a>)</p>
+
+    <p>1.13.3 is a bug fix release. It has fixes for awk, depmod, init, killall, mdev,
+    modprobe, printf, syslogd, tar, top, unzip, wget.
+    </p>
+  </li>
+
+  <li><b>31 December 2008 -- BusyBox 1.13.2 (stable), BusyBox 1.12.4 (stable)</b>
+    <p><a href="http://busybox.net/downloads/busybox-1.13.2.tar.bz2">BusyBox 1.13.2</a>.
+    (<a href="http://sources.busybox.net/index.py/branches/busybox_1_13_stable/">svn</a>,
+    <a href="http://busybox.net/downloads/fixes-1.13.2/">patches</a>,
+    <a href="http://busybox.net/fix.html">how to add a patch</a>)</p>
+    <p><a href="http://busybox.net/downloads/busybox-1.12.4.tar.bz2">BusyBox 1.12.4</a>.
+    (<a href="http://sources.busybox.net/index.py/branches/busybox_1_12_stable/">svn</a>,
+    <a href="http://busybox.net/downloads/fixes-1.12.4/">patches</a>,
+    <a href="http://busybox.net/fix.html">how to add a patch</a>)</p>
+
+    <p>Bug fix releases. 1.13.2 has fixes for crond, dc, init, ip, printf.
+    1.12.4 has fixes for ip and printf.
+    </p>
+  </li>
+
+  <li><b>29 November 2008 -- BusyBox 1.13.1 (stable), BusyBox 1.12.3 (stable)</b>
+    <p><a href="http://busybox.net/downloads/busybox-1.13.1.tar.bz2">BusyBox 1.13.1</a>.
+    (<a href="http://sources.busybox.net/index.py/branches/busybox_1_13_stable/">svn</a>,
+    <a href="http://busybox.net/downloads/fixes-1.13.1/">patches</a>,
+    <a href="http://busybox.net/fix.html">how to add a patch</a>)</p>
+    <p><a href="http://busybox.net/downloads/busybox-1.12.3.tar.bz2">BusyBox 1.12.3</a>.
+    (<a href="http://sources.busybox.net/index.py/branches/busybox_1_12_stable/">svn</a>,
+    <a href="http://busybox.net/downloads/fixes-1.12.3/">patches</a>,
+    <a href="http://busybox.net/fix.html">how to add a patch</a>)</p>
+
+    <p>Bug fix releases. 1.13.1 has fixes for ash, option parsing, id, init,
+    inotifyd, klogd, line editing and modprobe. 1.12.3 has fixes
+    for option parsing and line editing.
+    </p>
+  </li>
+
+  <li><b>10 November 2008 -- BusyBox 1.13.0 (unstable), BusyBox 1.12.2 (stable)</b>
+    <p><a href="http://busybox.net/downloads/busybox-1.13.0.tar.bz2">BusyBox 1.13.0</a>.
+    (<a href="http://sources.busybox.net/index.py/branches/busybox_1_13_stable/">svn</a>,
+    <a href="http://busybox.net/downloads/fixes-1.13.0/">patches</a>,
+    <a href="http://busybox.net/fix.html">how to add a patch</a>)</p>
+    <p><a href="http://busybox.net/downloads/busybox-1.12.2.tar.bz2">BusyBox 1.12.2</a>.
+    (<a href="http://sources.busybox.net/index.py/branches/busybox_1_12_stable/">svn</a>,
+    <a href="http://busybox.net/downloads/fixes-1.12.2/">patches</a>,
+    <a href="http://busybox.net/fix.html">how to add a patch</a>)</p>
+
+    <p>Sizes of busybox-1.12.2 and busybox-1.13.0 (with equivalent config, static uclibc build):<pre>
+   text    data     bss     dec     hex filename
+ 778291     551    7856  786698   c010a busybox-1.12.2/busybox
+ 778981     551    7852  787384   c03b8 busybox-1.13.0/busybox
+</pre>
+
+    <p>New applets: blkid, devmem
+
+    <p>Changes since previous release:
+      <ul>
+       <li>mail applets: total overhaul. Vladimir as usual</li>
+       <li>ash: fix "while kill -0 $child; do true; done" looping forever</li>
+       <li>ash: fix NOEXEC mode - we were forgetting to pass environment</li>
+       <li>ash: fix a bug in standalone mode (corrupted getopt state)</li>
+       <li>ash: optionally support "&gt;&amp;file" and "&amp;&gt;file" redirections</li>
+       <li>awk: bitwise ops cast oprands and results to unsigned long, not signed. closes bug 4774</li>
+       <li>awk: fix typo in atan2 code. closes bug 5594</li>
+       <li>awk: improve handling of negative numbers in bitwise ops; fix handling of octal costants</li>
+       <li>awk: support hex constants</li>
+       <li>basename: fix error code (again)</li>
+       <li>cpio: emit TRAILER even when hard links were found. By Pascal Bellard (pascal.bellard AT ads-lu.com)</li>
+       <li>crontab: do not destroy STDIN_FILENO, editor may need it (crontab -e)</li>
+       <li>dc: support for bases 2 and 8, by Nate Case (ncase AT xes-inc.com)</li>
+       <li>dhcpc: treat "discover...select...discover..." loop the same way as "discover...discover...discover..."</li>
+       <li>dpkg: add dpkg -l PACKAGE_PATTERN. By Peter Korsgaard</li>
+       <li>fbset: fix mode matching code: original code may trigger false positive.</li>
+       <li>findfs: fix LUKS and FAT detection routines; do not exit if corrupted FAT fs makes us try to seek past the end</li>
+       <li>grep: fix 'echo aaa | grep -o a' + ENABLE_EXTRA_COMPAT case. By Natanael Copa</li>
+       <li>grep: fix EXTRA_COMPAT grep to honor -E and -i</li>
+       <li>gunzip: restore mtime</li>
+       <li>halt: reinstate -w even if !FEATURE_WTMP</li>
+       <li>hexdump: fix SEGV in hexdump -e ""</li>
+       <li>httpd: pass "Accept:" and "Accept-Language:" header to CGI scripts (Alina Friedrichsen)</li>
+       <li>hush: fix environment and memory leaks</li>
+       <li>hush: fix trashing of environment by local env vars: a=a; a=b cmd; - a was unset</li>
+       <li>id: improve compatibility with coreutils. By Tito Ragusa</li>
+       <li>inetd: fix a case when we have zero services</li>
+       <li>inetd: use config parser. by Vladimir</li>
+       <li>init: set stderr to NONBLOCK</li>
+       <li>insmod: fix detection of open failure</li>
+       <li>install: support -D</li>
+       <li>ip: fix ip route rejecting dotted quads as prefix</li>
+       <li>ip: route metric support (Natanael Copa)</li>
+       <li>iplink: accept shorthands for "address" keyword: "ip link set address 00:11:22:33:44:55"</li>
+       <li>kbd_mode: support -C TTY</li>
+       <li>kill[all[5]]: accept -s SIG too. By Steve Bennett (steveb AT workware.net.au)</li>
+       <li>klogd: handle many lines at once. By Steve Bennett (steveb AT workware.net.au)</li>
+       <li>less: support -I to be able to search case-insensitively</li>
+       <li>less: add optional line number toggle and resizing on window resize</li>
+       <li>libbb: do not reject floating point strings like ".15"</li>
+       <li>lineedit: fix bug 5824 "since rev 23530 fdisk and ed don't work any more"</li>
+       <li>lineedit: fix problems with empty commands in history</li>
+       <li>login: fix /etc/nologin handling</li>
+       <li>man: fix inconsistencies in handling $MANPATH</li>
+       <li>mdev: support match by major,minor. See bug 4714</li>
+       <li>modprobe-small: make insmod command line compatible</li>
+       <li>modprobe-small: support "blacklist" keyword in /etc/modules/MODULE_NAME</li>
+       <li>modprobe: fix a segfault when modprobe is called with no arguments at all</li>
+       <li>modutils/*: rewrite by Timo Teras (timo.teras AT iki.fi)</li>
+       <li>mount: fix "-o parm1 -o parm2" not accumulating</li>
+       <li>nmeter: 4k buffers are too small for /proc files, make them dynamically sized with 16k upper limit</li>
+       <li>ping: SO_RCVBUF must be bigger than packet size, otherwise large ping packets might fail to be received</li>
+       <li>route: fix for 64-bit BE machines by Seonghun Lim (wariua AT gmail.com)</li>
+       <li>rpm: fix incompatibilities which prevented rpm -i foo.src.rpm</li>
+       <li>runsvdir: support runsvdir-as-init</li>
+       <li>setarch: do not try to use non-existent data in argv[]</li>
+       <li>setfont: support -m and -C, support -m TEXTUAL_MAP (by Vladimir)</li>
+       <li>setup_environment: cd $HOME regardless of clear_env value</li>
+       <li>slattach: preserve speed in non-raw mode. By Matthieu Castet (matthieu.castet AT parrot.com)</li>
+       <li>start_stop_daemon: accept (and ignore) -R PARAM</li>
+       <li>sv: make default service dir configurable (Vladimir wants it)</li>
+       <li>sysctl: fix bug 3894 (by Kryzhanovskyy Maksym)</li>
+       <li>tar: fix bug 3844: non-root tar does not preserve perms</li>
+       <li>telnetd: handle emacs M-DEL and IAC-NOP. by Jim Cathey (jcathey AT ciena.com)</li>
+       <li>top: fix "top -d 1" (bug 5144)</li>
+       <li>top: optional SMP support by Vineet Gupta (vineetg76 AT gmail.com)</li>
+       <li>trylink: make messages less confusing</li>
+       <li>unzip: handle "central directory". needed for OpenOffice, gmail attachment .zips etc</li>
+       <li>vi: Rob's algorithm of reading and matching ESC sequences (nice work btw!)</li>
+       <li>vi: deal with EOF/error on stdin and with input NULs</li>
+       <li>vi: fix uninitialized last_search_pattern (bug 5794)</li>
+       <li>vi: handle chars 0x80, 0x81 etc correctly</li>
+       <li>volume identification: abolish /proc/partitions and /proc/cdroms scanning. It does not catch volume managers and such. Simply scan /dev/* for any block devices</li>
+       <li>watchdog: WDIOC_SETTIMEOUT accepts seconds, not milliseconds</li>
+       <li>watchdog: add -T option</li>
+      </ul>
+    <p>
+    The email address gpl@busybox.net is the recommended way to contact
+    the Software Freedom Law Center to report BusyBox license violations.
+    </p>
+  </li>
+
+  <li><b>28 September 2008 -- BusyBox 1.12.1 (stable), BusyBox 1.11.3 (stable)</b>
+    <p><a href="http://busybox.net/downloads/busybox-1.12.1.tar.bz2">BusyBox 1.12.1</a>.
+    (<a href="http://sources.busybox.net/index.py/branches/busybox_1_12_stable/">svn</a>,
+    <a href="http://busybox.net/downloads/fixes-1.12.1/">patches</a>,
+    <a href="http://busybox.net/fix.html">how to add a patch</a>)</p>
+    <p><a href="http://busybox.net/downloads/busybox-1.11.3.tar.bz2">BusyBox 1.11.3</a>.
+    (<a href="http://sources.busybox.net/index.py/branches/busybox_1_11_stable/">svn</a>,
+    <a href="http://busybox.net/downloads/fixes-1.11.3/">patches</a>,
+    <a href="http://busybox.net/fix.html">how to add a patch</a>)</p>
+    <p>
+    Bugfix-only releases for 1.11.x and 1.12.x branches.
+    </p>
+  </li>
+
+  <li><b>21 August 2008 -- BusyBox 1.12.0 (unstable), BusyBox 1.11.2 (stable)</b>
+    <p><a href="http://busybox.net/downloads/busybox-1.12.0.tar.bz2">BusyBox 1.12.0</a>.
+    (<a href="http://sources.busybox.net/index.py/branches/busybox_1_12_stable/">svn</a>,
+    <a href="http://busybox.net/downloads/fixes-1.12.0/">patches</a>,
+    <a href="http://busybox.net/fix.html">how to add a patch</a>)</p>
+    <p><a href="http://busybox.net/downloads/busybox-1.11.2.tar.bz2">BusyBox 1.11.2</a>.
+    (<a href="http://sources.busybox.net/index.py/branches/busybox_1_11_stable/">svn</a>,
+    <a href="http://busybox.net/downloads/fixes-1.11.2/">patches</a>,
+    <a href="http://busybox.net/fix.html">how to add a patch</a>)</p>
+
+    <p>Sizes of busybox-1.11.2 and busybox-1.12.0 (with equivalent config, static uclibc build):<pre>
+   text    data     bss     dec     hex filename
+ 829687     617    7052  837356   cc6ec busybox-1.11.2/busybox
+ 822961     594    6832  830387   cabb3 busybox-1.12.0/busybox
+</pre>
+
+    <p>New applets: rdev (Grant Erickson), setfont, showkey (both by Vladimir)
+
+    <p>Most significant changes since previous release (please report any regression):
+      <ul>
+       <li>ash: bash compat: "shift $BIGNUM" is equivalent to "shift 1"</li>
+       <li>ash: dont allow e.g. exec &lt;&amp;10 to attach to script's fd! </li>
+       <li>ash: fix a bug where redirection fds were not closed afterwards. optimize close+fcntl(DUPFD) into dup2</li>
+       <li>ash: fix segfault in "command -v"</li>
+       <li>ash: fix very weak $RANDOM generator</li>
+       <li>ash: prevent exec NN&gt;&amp;- from closing fd used for script reading</li>
+       <li>ash: teach ash about 123&gt;file. It could take only 0..9 before</li>
+       <li>hush: fix a case where "$@" must expand to no word at all</li>
+       <li>hush: fix mishandling of a'b'c=fff as assignments. They are not</li>
+       <li>hush: fix non-detection of builtins and applets in "v=break; ...; $v; ..." case</li>
+       <li>hush: fix "while false; ..." exitcode; add testsuites</li>
+       <li>hush: support "case...esac" statements (~350 bytes of code)</li>
+       <li>hush: support "break [N]" and "continue [N]" statements</li>
+       <li>hush: support "for if in do done then; do echo $if; done" case</li>
+       <li>hush: support "for v; do ... done" syntax (implied 'in "$@"')</li>
+       <li>hush: support $_NUMBERS variable names</li>
+       <li>libbb: unified config parser (by Vladimir). This change affected many applets</li>
+      </ul>
+
+    <p>Other changes:
+      <ul>
+       <li>libbb: dump: do not use uninitialized memory (closes bug 4364)</li>
+       <li>libbb: fix bb_strtol[l]'s check for "-" (closes bug 4174)</li>
+       <li>libbb: fix --help to not affect "test --help"</li>
+       <li>libbb: fix mishandling of "all argv are opts" in getopt32()</li>
+       <li>libbb: getopt32() should not ever touch argv[0] (even read)</li>
+       <li>libbb: introduce and use xrealloc_vector</li>
+       <li>libbb: [x]fopen_for_{read,write} introduced and used (by Vladimir)</li>
+       <li>lineedit: fix use-after-free</li>
+       <li>libunarchive: refactor handling of archived files. "tar f file.tar.lzma" now works too</li>
+       <li>bb_strtoXXX: close bug 4174 (potential use of buf[-1])</li>
+       <li>open_transformer: don't leak file descriptor</li>
+       <li>open_transformer: fix bug of calling exit instead of _exit</li>
+       <li>arp: without -H type, assume "ether" (closes bug 4564)</li>
+       <li>ar: reuse existing ar unpacking code</li>
+       <li>awk: fix a case with multiple -f options. Simplify -f file reading. </li>
+       <li>build system: introduce and use FAST_FUNC: regparm on i386, otherwise no-op</li>
+       <li>bunzip2: fix an uncompression error (by Rob Landley rob AT landley.net)</li>
+       <li>b[un]zip2, g[un]zip: unlink destination if -f is given (closes bug 3854)</li>
+       <li>comm: almost total rewrite</li>
+       <li>cpio: fix -m to actually work as expected (by Pascal Bellard)</li>
+       <li>cpio: internalize archive_xread_all_eof, add a few paranoia checks for corrupted cpio files</li>
+       <li>cpio: make long opts depend only on ENABLE_GETOPT_LONG</li>
+       <li>cpio: on unpack, limit filename length to 8k</li>
+       <li>cpio: support some long options</li>
+       <li>crond: use execlp instead of execl</li>
+       <li>cut: fix buffer overflow (closes bug 4544)</li>
+       <li>envdir: fix "envdir" (no params at all) and "envdir dir" cases</li>
+       <li>findfs: make it use setuid-ness of busybox binary</li>
+       <li>fsck: use getmntent_r instead of open-coded parsing (by Vladimir)</li>
+       <li>fuser: a bit of safety in scanf</li>
+       <li>grep: option to use GNU regex matching instead of POSIX one. This fixes problems with NULs in files being scanned, but costs +800 bytes</li>
+       <li>halt: signal init regardless of ENABLE_INIT</li>
+       <li>httpd: add homedir directive specially for (and by) Walter Harms wharms AT bfs.de</li>
+       <li>ifupdown: /etc/network/interfaces can have comments with leading blanks</li>
+       <li>ifupdown: fixes for custom MAC address (by Wade Berrier wberrier AT gmail.com)</li>
+       <li>ifupdown: fixes for shutdown of DHCP-managed interfaces (by Wade Berrier wberrier AT gmail.com)</li>
+       <li>inetd: do not trash errno in signal handlers; in CHLD handler, stop looping through services when pid is found</li>
+       <li>insmod: users report that "|| defined(__powerpc__)" is missing</li>
+       <li>install: do not chown intermediate directories with install -d (by Natanael Copa)</li>
+       <li>install: fix long option not taking params (closes bug 4584)</li>
+       <li>lpd,lpr: send/receive ACKs after filenames, not only after file bodies</li>
+       <li>ls: fix a bug where we may use uninintialized variable</li>
+       <li>man: add handling of "man links", by Ivana Varekova varekova AT redhat.com</li>
+       <li>man: fix a case when a full pathname to manpage is given</li>
+       <li>man: fix inverted cat/man bool variable</li>
+       <li>man: fix missed NULL termination of an array</li>
+       <li>man: mimic "no manual entry for 'bogus'" message and exitcode</li>
+       <li>man: support cat pages too (by Jason Curl jcurlnews AT arcor.de)</li>
+       <li>man: teach it to use .lzma if requested by .config</li>
+       <li>mdev: check for "/block/" substring for block dev detection</li>
+       <li>mdev: do not complain if mdev.conf does not exist</li>
+       <li>mdev: if device was moved at creation, at removal correctly remove it from moved location and also remove symlinks to it</li>
+       <li>mdev: support for serializing hotplug</li>
+       <li>mdev, init: use shared code for fd sanitization</li>
+       <li>mkdir: fix "uname 0222; mkdir -p foo/bar" case (by Doug Graham dgraham AT nortel.com)</li>
+       <li>modprobe: support for /etc/modprobe.d (by Timo Teras)</li>
+       <li>modprobe: use buffering line reads (fgets()) instead of reads()</li>
+       <li>modutils: optional modprobe-small (by Vladimir), 15kb smaller than standard one</li>
+       <li>mount: support for "-o mand" and "[no]relatime"</li>
+       <li>mount: support nfs mount option "nordiplus" (by Octavian Purdila opurdila AT ixiacom.com)</li>
+       <li>mount: support "relatime" / "norelatime"</li>
+       <li>mount: testsuite for "-o mand"</li>
+       <li>msh: fix "while... continue; ..." (closes bug 3884)</li>
+       <li>mv: fix a case when we move dangling symlink across mountpoints</li>
+       <li>netstat: optional -p support (by L. Gabriel Somlo somlo AT cmu.edu)</li>
+       <li>nmeter: fix read past the end of a buffer (closes bug 4594)</li>
+       <li>od, hexdump: fix bug where xrealloc may move pointer, leaving other pointers dangling (closes bug 4104)</li>
+       <li>pidof/killall: allow find_pid_by_name to find running processes started as scripts_with_name_longer_than_15_bytes.sh (closes bug 4054)</li>
+       <li>printf: do not print garbage on "%Ld" (closes bug 4214)</li>
+       <li>printf: fix %b, fix several bugs in %*.*, fix compat issues with aborting too early, support %zd; expand testsuite</li>
+       <li>printf: protect against bogus format specifiers (closes bug 4184)</li>
+       <li>sendmail: updates from Vladimir:</li>
+       <li>sendmail: do not discard all headers</li>
+       <li>sendmail: do not ignore CC; accept to: and cc: case-insensitively. +20 bytes</li>
+       <li>sendmail: fixed mail recipient address</li>
+       <li>sendmail: fixed SEGV if sender address is missed</li>
+       <li>sendmail: use HOSTNAME instead of HOST when no server is explicitly specified</li>
+       <li>sleep: if FANCY &amp;&amp; DESKTOP, support fractional seconds, minutes, hours and so on (coreutils compat)</li>
+       <li>ssd: CLOSE_EXTRA_FDS in MMU case too</li>
+       <li>ssd: do not stat -x EXECUTABLE, it is not needed anymore</li>
+       <li>ssd: fix -a without -x case</li>
+       <li>ssd: use $PATH</li>
+       <li>tar: fix handling of tarballs with symlinks with size field != 0</li>
+       <li>tar: handle autodetection for tiny .tar.gz files too, simplify autodetection</li>
+       <li>taskset: fix some careless code in both fancy and non-fancy cases. -5 bytes for fancy, +5 for non-fancy</li>
+       <li>tee: fix infinite looping on open error (echo asd | tee "")</li>
+       <li>tee: "-" is a name for stdout, handle it that way</li>
+       <li>telnetd: fix issue file printing</li>
+       <li>test: fix parser to prefer binop over unop, as coreutils does</li>
+       <li>testsuite: uniformly use $ECHO with -n -e</li>
+       <li>time: don't segfault with no arguments</li>
+       <li>touch: support -r REF_FILE if ENABLE_DESKTOP (needed for blackfin compile)</li>
+       <li>tr: fix "access past the end of a string" bug 4354</li>
+       <li>tr: fix "tr [=" case (closes bug 4374)</li>
+       <li>tr: fix yet another access past the end of a string (closes bug 4374)</li>
+       <li>unlzma: fix memory leak (by Pascal Bellard)</li>
+       <li>vi: fix reversed checks for underflow</li>
+       <li>vi: using array data after it fell out of scope is stupid</li>
+       <li>xargs: fix -e default to match newer GNU xargs, add SUS mandated -E (closes bug 4414)</li>
+       <li>other fixes and code size reductions in many applets</li>
+      </ul>
+    </p>
+
+  <li><b>12 July 2008 -- BusyBox 1.11.1 (stable)</b>
+    <p><a href="http://busybox.net/downloads/busybox-1.11.1.tar.bz2">BusyBox 1.11.1</a>.
+    (<a href="http://sources.busybox.net/index.py/branches/busybox_1_11_stable/">svn</a>,
+    <a href="http://busybox.net/downloads/fixes-1.11.1/">patches</a>,
+    <a href="http://busybox.net/fix.html">how to add a patch</a>)</p>
+    <p>
+    Bugfix-only release for 1.11.x branch. It contains fixes for awk,
+    bunzip2, cpio, ifupdown, ip, man, start-stop-daemon, uname and vi.
+    </p>
+  </li>
+
+  <li><b>11 July 2008 -- HOWTO is updated</b>
+    <p>
+    <a href="http://busybox.net/~vda/HOWTO/i486-linux-uclibc/HOWTO.txt">
+    "How to build static busybox for i486-linux-uclibc"</a> is updated
+    and tested on a fresh Fedora 9 install. Please report if it doesn't
+    work for you.
+    </p>
+  </li>
+
+
+
+  <li><b>Old News</b><p>
+    Click here to read <a href="oldnews.html">older news</a>
+    </p>
+  </li>
+
+</ul>
+
+<!--#include file="footer.html" -->
+
diff --git a/docs/busybox.net/oldnews.html b/docs/busybox.net/oldnews.html
new file mode 100644 (file)
index 0000000..444af74
--- /dev/null
@@ -0,0 +1,2214 @@
+<!--#include file="header.html" -->
+
+<h3>News archive</h3>
+
+<ul>
+
+  <li><b>25 June 2008 -- BusyBox 1.11.0 (unstable), BusyBox 1.10.4 (stable)</b>
+    <p><a href="http://busybox.net/downloads/busybox-1.11.0.tar.bz2">BusyBox 1.11.0</a>.
+    (<a href="http://sources.busybox.net/index.py/branches/busybox_1_11_stable/">svn</a>,
+    <a href="http://busybox.net/downloads/fixes-1.11.0/">patches</a>,
+    <a href="http://busybox.net/fix.html">how to add a patch</a>)</p>
+    <p><a href="http://busybox.net/downloads/busybox-1.10.4.tar.bz2">BusyBox 1.10.4</a>.
+    (<a href="http://sources.busybox.net/index.py/branches/busybox_1_10_stable/">svn</a>,
+    <a href="http://busybox.net/downloads/fixes-1.10.4/">patches</a>,
+    <a href="http://busybox.net/fix.html">how to add a patch</a>)</p>
+    <p>Sizes of busybox-1.10.4 and busybox-1.11.0 (with equivalent config, static uclibc build):<pre>
+   text    data     bss     dec     hex filename
+ 800675     636    7080  808391   c55c7 busybox-1.10.4
+ 798392     611    6900  805903   c4c0f busybox-1.11.0
+</pre>
+
+    <p>New applets: inotify (Vladimir Dronnikov), man (Ivana Varekova),
+    fbsplash (Michele Sanges), depmod (Bernhard Reutner-Fischer)
+
+    <p>Changes since previous release:
+      <ul>
+       <li>build system: reinstate CONFIG_CROSS_COMPILE_PREFIX</li>
+       <li>ash: optional bash compatibility features added; other fixes</li>
+       <li>hush: lots and lots of fixes</li>
+       <li>msh: fix the case where the file has exec bit but can't be run directly (runs "$SHELL file" instead)</li>
+       <li>msh: fix exit codes when command is not found or can't be execed</li>
+       <li>udhcpc: added workaround for buggy kernels</li>
+       <li>mount: fix mishandling of proto=tcp/udp</li>
+       <li>diff: make it work on non-seekable streams</li>
+       <li>openvt: made more compatible with "standard" one</li>
+       <li>mdev: fix block/char device detection</li>
+       <li>ping: add -w, -W support (James Simmons)</li>
+       <li>crond: add handling of "MAILTO=user" lines</li>
+       <li>start-stop-daemon: make --exec follow symlinks (Joakim Tjernlund)</li>
+       <li>date: make it accept ISO date format</li>
+       <li>echo: fix echo -e -n "msg\n\0" (David Pinedo)</li>
+       <li>httpd: fix several bugs triggered by relative path in -h DIR</li>
+       <li>printf: fix printf -%s- foo, printf -- -%s- foo</li>
+       <li>syslogd: do not error out on missing files to rotate</li>
+       <li>ls: support Unicode in names</li>
+       <li>ip: support for the LOWER_UP flag (Natanael Copa)</li>
+       <li>mktemp: make argument optional (coreutil 6.12 compat)</li>
+       <li>libiproute: fix option parsing, so that "ip -o link" works again</li>
+       <li>other fixes and code size reductions in many applets</li>
+      </ul>
+    <p>
+    The email address gpl@busybox.net is the recommended way to contact
+    the Software Freedom Law Center to report BusyBox license violations.
+    </p>
+  </li>
+
+  <li><b>12 June 2008 -- Sponsors!</b>
+    <p>We want to thank the following companies which are providing support
+    for the BusyBox project:
+    </p>
+      <ul>
+        <li>AOE media, a <a href="http://www.aoemedia.com/typo3-development.html">
+       TYPO3 development agency</a> contributes financially.</li>
+       <li><a href="http://www.analog.com/en/">Analog Devices, Inc.</a> provided
+        a <a href="http://docs.blackfin.uclinux.org/doku.php?id=bf537_quick_start">
+       Blackfin development board</a> free of charge.
+       <a href="http://www.analog.com/blackfin">Blackfin</a>
+       is a NOMMU processor, and its availability for testing is invaluable.
+       If you are an embedded device developer,
+       please note that Analog Devices has entire Linux distribution available
+       for download for this board. Visit
+       <a href="http://blackfin.uclinux.org/">http://blackfin.uclinux.org/</a>
+       for more information.
+       </li>
+      </ul>
+  </li>
+
+  <li><b>5 June 2008 -- BusyBox 1.10.3 (stable)</b>
+    <p><a href="http://busybox.net/downloads/busybox-1.10.3.tar.bz2">BusyBox 1.10.3</a>.
+    (<a href="http://sources.busybox.net/index.py/branches/busybox_1_10_stable/">svn</a>,
+    <a href="http://busybox.net/downloads/fixes-1.10.3/">patches</a>,
+    <a href="http://busybox.net/fix.html">how to add a patch</a>)</p>
+    <p>
+    Bugfix-only release for 1.10.x branch. It contains fixes for dnsd, fuser, hush,
+    ip, mdev and syslogd.
+    </p>
+  </li>
+
+  <li><b>8 May 2008 -- BusyBox 1.10.2 (stable)</b>
+    <p><a href="http://busybox.net/downloads/busybox-1.10.2.tar.bz2">BusyBox 1.10.2</a>.
+    (<a href="http://sources.busybox.net/index.py/branches/busybox_1_10_stable/">svn</a>,
+    <a href="http://busybox.net/downloads/fixes-1.10.2/">patches</a>,
+    <a href="http://busybox.net/fix.html">how to add a patch</a>)</p>
+    <p>
+    Bugfix-only release for 1.10.x branch. It contains fixes for echo, httpd, pidof,
+    start-stop-daemon, tar, taskset, tab completion in shells, build system.
+    <p>Please note that mdev was backported from current svn trunk. Please
+    report if you encounter any problems with it.
+    </p>
+  </li>
+
+  <li><b>19 April 2008 -- BusyBox 1.10.1 (stable)</b>
+    <p><a href="http://busybox.net/downloads/busybox-1.10.1.tar.bz2">BusyBox 1.10.1</a>.
+    (<a href="http://sources.busybox.net/index.py/branches/busybox_1_10_stable/">svn</a>,
+    <a href="http://busybox.net/downloads/fixes-1.10.1/">patches</a>,
+    <a href="http://busybox.net/fix.html">how to add a patch</a>)</p>
+    <p>
+    Bugfix-only release for 1.10.x branch. It contains fixes for
+    fuser, init, less, nameif, tail, taskset, tcpudp, top, udhcp.
+  </li>
+
+  <li><b>21 March 2008 -- BusyBox 1.10.0 (unstable)</b>
+    <p><a href="http://busybox.net/downloads/busybox-1.10.0.tar.bz2">BusyBox 1.10.0</a>.
+    (<a href="http://sources.busybox.net/index.py/branches/busybox_1_10_stable/">svn</a>,
+    <a href="http://busybox.net/downloads/fixes-1.10.0/">patches</a>,
+    <a href="http://busybox.net/fix.html">how to add a patch</a>)</p>
+
+    <p>Sizes of busybox-1.9.2 and busybox-1.10.0 (with almost full config, static uclibc build):<pre>
+   text    data     bss     dec     hex filename
+ 781405     679    7500  789584   c0c50 busybox-1.9.2
+ 773551     640    7372  781563   becfb busybox-1.10.0
+</pre>
+    <p>Top 10 stack users:<pre>
+busybox-1.9.2:               busybox-1.10.0:
+echo_dg                 4116 bb_full_fd_action       4112
+bb_full_fd_action       4112 find_list_entry2        4096
+discard_dg              4108 readlink_main           4096
+discard_dg              4096 ipaddr_list_or_flush    3900
+echo_stream             4096 iproute_list_or_flush   3680
+discard_stream          4096 insmod_main             3152
+find_list_entry2        4096 fallbackSort            2952
+readlink_main           4096 do_iproute              2492
+ipaddr_list_or_flush    3900 cal_main                2464
+iproute_list_or_flush   3680 readhere                2308
+</pre>
+
+    <p>New applets: brctl, chat (by Vladimir Dronnikov &lt;dronnikov AT gmail.com&gt;),
+       findfs, ifenslave (closes bug 115), lpd (by Vladimir Dronnikov &lt;dronnikov AT gmail.com&gt;),
+       lpr+lpq (by Walter Harms), script (by Pascal Bellard &lt;pascal.bellard AT ads-lu.com&gt;),
+       sendmail (Vladimir Dronnikov &lt;dronnikov AT gmail.com&gt;), tac, tftpd.
+    </p>
+    <p>Made NOMMU-compatible: crond, crontab, ifupdown, inetd, init, runsv, svlogd, tcpsvd, udpsvd.
+    </p>
+    <p>Changes since previous release:
+    </p>
+      <ul>
+       <li>globally: add -Wunused-parameter</li>
+       <li>globally: add optimization barrier to all "G trick" locations</li>
+       <li>adduser/addgroup: check username for invalid chars (by Tito &lt;farmatito AT tiscali.it&gt;)</li>
+       <li>adduser: optional support for long options. Closes bug 2134</li>
+       <li>ash: handle "A=1 A=2 B=$A; echo $B". Closes bug 947</li>
+       <li>ash: make ash -c "if set -o barfoo 2&gt;/dev/null; then echo foo; else echo bar; fi" work. Closes bug 1142</li>
+       <li>build system: don't use "gcc -o /dev/null", old gcc can delete /dev/null in this case</li>
+       <li>build system: fixes for cross-compiling on an OS X host</li>
+       <li>build system: make it do without "od -t"</li>
+       <li>build system: pass CFLAGS to link stage too. Closes bug 1376</li>
+       <li>build system: add CONFIG_NOMMU</li>
+       <li>cp: add ENABLE_FEATURE_VERBOSE_CP_MESSAGE. Closes bug 1470</li>
+       <li>crontab: almost complete rewrite</li>
+       <li>dnsd: properly set _src_ IP:port on outgoing UDP packets</li>
+       <li>dpkg: fix bug where existence check was reversed</li>
+       <li>eject: add -s for SCSI- and USB-devices (Nico Erfurth)</li>
+       <li>fdisk: fix a case where break was reached only for DOS labels</li>
+       <li>fsck: don't kill pid -1! (Roy Marples &lt;roy at marples.name&gt;)</li>
+       <li>fsck_minix: fix bug in map_block2: s/(blknr &gt;= 256 * 256)/(blknr &lt; 256 * 256)/</li>
+       <li>fuser: substantial rewrite</li>
+       <li>getopt: add support for "a+" specifier for nonnegative int parameters. By Vladimir Dronnikov &lt;dronnikov at gmail.com&gt;</li>
+       <li>getty: don't try to detect parity on local lines (Joakim Tjernlund &lt;Joakim.Tjernlund at transmode.se&gt;)</li>
+       <li>halt: write wtmp entry if wtmp support is enabled</li>
+       <li>httpd: "HEAD" support. Closes bug 1530</li>
+       <li>httpd: fix bug 2004: wrong argv when interpreter is invoked</li>
+       <li>httpd: fix bug where we did chdir("") if CGI path had only one "/"</li>
+       <li>httpd: fix for POST upload</li>
+       <li>httpd: support for "I:index.xml" syntax (Peter Korsgaard &lt;jacmet AT uclibc.org&gt;)</li>
+       <li>hush: fix a case where none of pipe members could be started because of fork failure</li>
+       <li>hush: more correct handling of piping</li>
+       <li>hush: reinstate `cmd` handling for NOMMU</li>
+       <li>hush: report [v]fork failures</li>
+       <li>hush: set CLOEXEC on script file being executed</li>
+       <li>hush: try to add a bit more of vfork-friendliness</li>
+       <li>inetd: make "udp nowait" work</li>
+       <li>inetd: make inetd IPv6-capable</li>
+       <li>init: add FEATURE_KILL_REMOVED (Eugene Bordenkircher &lt;eugebo AT gmail.com&gt;)</li>
+       <li>init: allow last line of config file to be not terminated by "\n"</li>
+       <li>init: do not die if "/dev/null" is missing</li>
+       <li>init: fix bug 1111: restart actions were not splitting words</li>
+       <li>init: wait for orphaned children too while waiting for sysinit-like processes (harald-tuxbox AT arcor.de)</li>
+       <li>ip route: "ip route" was misbehaving (extra argv+1 ate 1st env var)</li>
+       <li>last: do not go into endless loop on read error</li>
+       <li>less,klogd,syslogd,nc,tcpudp: exit on signal by killing itself, not exit(1)</li>
+       <li>less: "examine" command will not bomb out on bad file name now</li>
+       <li>less: fix bug where backspace wasn't actually deleting chars</li>
+       <li>less: make it a bit more resistant against status line corruption</li>
+       <li>less: improve search when data is not supplied fast enough by stdin - now will try reading for 1-2 seconds before declaring that there is no match. This fixes a very common annoyance with long manpages</li>
+       <li>less: update line input so that it doesn't interfere with screen update. Makes "man bash", [enter], [/], &lt;enter search pattern&gt;, [enter] more usable - manpage now draws even as you enter the pattern!</li>
+       <li>libbb: filename completion matches dangling symlinks too</li>
+       <li>libbb: fix getopt state corruption for NOFORK applets</li>
+       <li>libbb: full_read/write now will report partial data counts prior to error</li>
+       <li>libbb: intrduce and use safe_gethostname. By Tito &lt;farmatito AT tiscali.it&gt;</li>
+       <li>libbb: introduce and use nonblock_safe_read(). Yay! Our shells are immune from this nasty O_NONBLOCK now!</li>
+       <li>login,su: avoid clearing environment with some options, as was intended</li>
+       <li>microcom: read more than 1 byte from device, if possible</li>
+       <li>microcom: split -d (delay) option away from -t</li>
+       <li>mktemp: support -p DIR (Timo Teras &lt;timo.teras at iki.fi&gt;)</li>
+       <li>mount: #ifdef out MOUNT_LABEL code parts if it is not selected</li>
+       <li>mount: add another mount helper call method</li>
+       <li>mount: allow and ignore _netdev option</li>
+       <li>mount: make -f work even without mtab support (Cristian Ionescu-Idbohrn &lt;cristian.ionescu-idbohrn at axis.com&gt;)</li>
+       <li>mount: optional support for -vv verbosity</li>
+       <li>mount: plug a hole where FEATURE_MOUNT_HELPERS could allow execution of arbitrary command</li>
+       <li>mount: recognize "dirsync" (closes bug 835)</li>
+       <li>mount: sanitize environment if called by non-root</li>
+       <li>mount: support for mount by label. Closes bug 1143</li>
+       <li>mount: with -vv -f, say what mount() calls we were going to make</li>
+       <li>msh: create testsuite (based on hush one)</li>
+       <li>msh: don't use floating point in "times" builtin</li>
+       <li>msh: fix Ctrl-C handling with line editing</li>
+       <li>msh: fix for bug 846 ("break" didn't work second time)</li>
+       <li>msh: glob0/glob1/glob2/glob3 were just a sorting routine, removed</li>
+       <li>msh: instead of fixing "ls | cd", "cd | ls" etc disallow builtins in pipes. They make no sense there anyway</li>
+       <li>msh: stop trying to parse variables in "msh SCRIPT VAR=val param". They are passed as ordinary parameters</li>
+       <li>netstat: print control chars as "^C" etc</li>
+       <li>nmeter: fix bug where %[mf] behaves as %[mt]</li>
+       <li>nohup: compat patch by Christoph Gysin &lt;mailinglist.cache at gmail.com&gt;</li>
+       <li>od: handle /proc files (which have filesize 0) correctly</li>
+       <li>patch: don't trash permissions of patched file</li>
+       <li>ps: add conditional support for -o [e]time</li>
+       <li>ps: fix COMMAND column adjustment; overflow in USER and VSZ columns</li>
+       <li>reset: call "stty sane". Closes bug 1414</li>
+       <li>rmdir: optional long options support for Debian users. By Roberto Gordo Saez &lt;roberto.gordo AT gmail.com&gt;</li>
+       <li>run-parts: add --reverse</li>
+       <li>script: correctly handle buffered "tail" of output</li>
+       <li>sed: "n" command must reset "we had successful subst" flag. Closes bug 1214</li>
+       <li>sort: -z outputs NUL terminated lines. Closes bug 1591</li>
+       <li>stty: fix mishandling of control keywords (Ralf Friedl &lt;Ralf.Friedl AT online.de&gt;)</li>
+       <li>switch_root: stop at first non-option. Closes bug 1425</li>
+       <li>syslogd: avoid excessive time() system calls</li>
+       <li>syslogd: don't die if remote host's IP cannot be resolved. Retry resolutions every two minutes instead</li>
+       <li>syslogd: fix shmat error check</li>
+       <li>syslogd: optional support for dropping dups. Closes bug 436</li>
+       <li>syslogd: send "\n"-terminated messages over the network. Fully closes bug 1574</li>
+       <li>syslogd: tighten up hostname handling</li>
+       <li>tail: fix "tail -c 20 /dev/huge_disk" (was taking ages)</li>
+       <li>tar: compat: handle tarballs with only one zero block at the end</li>
+       <li>tar: autodetection of gz/bz2 compressed tarballs. Closes bug 992</li>
+       <li>tar: real support for -p. By Natanael Copa &lt;natanael.copa at gmail.com&gt;</li>
+       <li>tcpudp: narrow down time window where we have no wildcard socket</li>
+       <li>telnetd: use login always, not "sometimes login, sometimes shell"</li>
+       <li>test: fix mishandling of "test ! arg1 op arg2 more args"</li>
+       <li>trylink: instead of build error, disable --gc-sections if GLIBC and STATIC are selected</li>
+       <li>udhcp: make file paths configurable</li>
+       <li>udhcp: optional support for non-standard DHCP ports</li>
+       <li>udhcp: set correct op byte in the packet for DHCPDECLINE</li>
+       <li>udhcpc: filter unwanted packets in kernel (Cristian Ionescu-Idbohrn &lt;cristian.ionescu-idbohrn AT axis.com&gt;)</li>
+       <li>udhcpc: fix wrong options in decline and release packets (Jonas Danielsson &lt;jonas.danielsson AT axis.com&gt;)</li>
+       <li>umount: do not complain several times about the same mountpoint</li>
+       <li>umount: do not try to free loop device or erase mtab if remounted ro</li>
+       <li>umount: instead of non-standard -D, use -d with opposite meaning. Closes bug 1604</li>
+       <li>unlzma: shrink by Pascal Bellard &lt;pascal.bellard AT ads-lu.com&gt;</li>
+       <li>unzip: do not try to read entire compressed stream at once (it can be huge)</li>
+       <li>unzip: handle short reads correctly</li>
+       <li>vi: many fixes</li>
+       <li>zcip: don't chdir to root</li>
+       <li>zcip: open ARP socket before openlog (else we can trash syslog socket)</li>
+      </ul>
+  </li>
+
+  <li><b>21 March 2008 -- BusyBox old stable releases</b>
+    <p>
+    Bugfix-only releases for four past branches. Links to locations
+    for future hot patches are in parentheses.
+    <p>
+    <a href="http://busybox.net/downloads/busybox-1.9.2.tar.bz2">1.9.2</a>
+    (<a href="http://busybox.net/downloads/fixes-1.9.2/">patches</a>),
+    <a href="http://busybox.net/downloads/busybox-1.8.3.tar.bz2">1.8.3</a>
+    (<a href="http://busybox.net/downloads/fixes-1.8.3/">patches</a>),
+    <a href="http://busybox.net/downloads/busybox-1.7.5.tar.bz2">1.7.5</a>
+    (<a href="http://busybox.net/downloads/fixes-1.7.5/">patches</a>),
+    <a href="http://busybox.net/downloads/busybox-1.5.2.tar.bz2">1.5.2</a>
+    (<a href="http://busybox.net/downloads/fixes-1.5.2/">patches</a>).
+    <p>
+    <a href="http://busybox.net/fix.html">How to add a patch.</a>
+    </p>
+
+
+  <li><b>12 February 2008 -- BusyBox 1.9.1 (stable)</b>
+    <p><a href="http://busybox.net/downloads/busybox-1.9.1.tar.bz2">BusyBox 1.9.1</a>.
+    (<a href="http://sources.busybox.net/index.py/branches/busybox_1_9_stable/">svn</a>,
+    <a href="http://busybox.net/downloads/fixes-1.9.1/">patches</a>,
+    <a href="http://busybox.net/fix.html">how to add a patch</a>)</p>
+
+    <p>This is a bugfix-only release, with fixes to fsck,
+    iproute, mdev, mkswap, msh, nameif, stty, test, zcip.</p>
+    <p>hush has `command` expansion re-enabled for NOMMU, although it is
+    inherently unsafe (by virtue of NOMMU's use of vfork instead of fork).
+    The plan is to make this less likely to bite people in future versions.</p>
+  </li>
+
+  <li><b>24 December 2007 -- BusyBox 1.9.0 (unstable)</b>
+    <p><a href="http://busybox.net/downloads/busybox-1.9.0.tar.bz2">BusyBox 1.9.0</a>.
+    (<a href="http://sources.busybox.net/index.py/branches/busybox_1_9_stable/">svn</a>,
+    <a href="http://busybox.net/downloads/fixes-1.9.0/">patches</a>,
+    <a href="http://busybox.net/fix.html">how to add a patch</a>)</p>
+
+    <p>Sizes of busybox-1.8.2 and busybox-1.9.0 (with almost full config, static uclibc build):<pre>
+   text    data     bss     dec     hex filename
+ 792796     978    9724  803498   c42aa busybox-1.8.2
+ 783803     683    7508  791994   c15ba busybox-1.9.0
+</pre>
+    <p>Top 10 stack users:<pre>
+busybox-1.8.2:               busybox-1.9.0:
+input_tab             10428  echo_dg                4116
+umount_main            8252  bb_full_fd_action      4112
+rtnl_talk              8240  discard_dg             4096
+xrtnl_dump_filter      8240  echo_stream            4096
+sendMTFValues          5316  discard_stream         4096
+mainSort               4700  find_list_entry2       4096
+mkfs_minix_main        4288  readlink_main          4096
+grave                  4260  ipaddr_list_or_flush   3900
+unix_do_one            4156  iproute_list_or_flush  3680
+parse_prompt           4132  insmod_main            3152
+</pre>
+
+    <p>lash is deleted from this release. hush can be configured down to almost
+       the same size, but it is significantly less buggy. It even works
+       on NOMMU machines (interactive mode and backticks are not working on NOMMU,
+       though). "lash" applet is still available, but it runs hush.
+
+    <p>init has some changes in this release, please report if it causes
+       problems for you.
+
+    <p>Changes since previous release:
+      <ul>
+       <li>Build system improvements
+       <li>Testsuite additions
+       <li>Stack size reductions, code size reductions, data/bss reductions
+       <li>An option to prefer IPv4 address if host has both
+       <li>New applets: hd, sestatus
+       <li>Removed applets: lash
+       <li>hush: fixed a few bugs, wired up echo and test to be builtins
+       <li>init: simplify forking of children
+       <li>getty: special handling of '#' and '@' is removed
+       <li>[su]login: sanitize environment if called by non-root
+       <li>udhcpc: support "bad" servers which send oversized packets
+         (Cristian Ionescu-Idbohrn &lt;cristian.ionescu-idbohrn at axis.com&gt;)
+       <li>udhcpc: -O option allows to specify which options to ask for
+         (Stefan Hellermann &lt;stefan at the2masters.de&gt;)
+       <li>udhcpc: optionally check whether given IP is really free (by ARP ping)
+         (Jonas Danielsson &lt;jonas.danielsson at axis.com&gt;)
+       <li>vi: now handles files with unlimited line length
+       <li>vi: speedup for huge line lengths
+       <li>vi: Del key works
+       <li>sed: support GNUism '\t'
+       <li>cp/mv/install: optionally use bigger buffer for bulk copying
+       <li>line editing: don't eat stack like crazy
+       <li>passwd: follows symlinked /etc/passwd
+       <li>renice: accepts priority with +N too
+       <li>netstat: wide output mode
+       <li>nameif: extended matching (Nico Erfurth &lt;masta at perlgolf.de&gt;)
+       <li>test: become NOFORK applet
+       <li>find: -iname (Alexander Griesser &lt;alexander.griesser at lkh-vil.or.at&gt;)
+       <li>df: -i option (show inode info) (Pascal Bellard &lt;pascal.bellard at ads-lu.com&gt;)
+       <li>hexdump: -R option (Pascal Bellard &lt;pascal.bellard at ads-lu.com&gt;)
+      </ul>
+  </li>
+
+  <li><b>23 November 2007 -- BusyBox 1.8.2 (stable), BusyBox 1.7.4 (stable)</b>
+    <p><a href="http://busybox.net/downloads/busybox-1.8.2.tar.bz2">BusyBox 1.8.2</a>.
+    (<a href="http://sources.busybox.net/index.py/branches/busybox_1_8_stable/">svn</a>,
+    <a href="http://busybox.net/downloads/fixes-1.8.2/">patches</a>,
+    <a href="http://busybox.net/fix.html">how to add a patch</a>)</p>
+    <p><a href="http://busybox.net/downloads/busybox-1.7.4.tar.bz2">BusyBox 1.7.4</a>.
+    (<a href="http://sources.busybox.net/index.py/branches/busybox_1_7_stable/">svn</a>,
+    <a href="http://busybox.net/downloads/fixes-1.7.4/">patches</a>,
+    <a href="http://busybox.net/fix.html">how to add a patch</a>)</p>
+
+    <p>These are bugfix-only releases.
+    1.8.2 contains fixes for inetd, lash, tar, tr, and build system.
+    1.7.4 contains a fix for inetd.</p>
+  </li>
+
+  <li><b>9 November 2007 -- BusyBox 1.8.1 (stable)</b>
+    <p><a href="http://busybox.net/downloads/busybox-1.8.1.tar.bz2">BusyBox 1.8.1</a>.
+    (<a href="http://sources.busybox.net/index.py/branches/busybox_1_8_stable/">svn</a>,
+    <a href="http://busybox.net/downloads/fixes-1.8.1/">patches</a>,
+    <a href="http://busybox.net/fix.html">how to add a patch</a>)</p>
+
+    <p>This is a bugfix-only release, with fixes to login (PAM), modprobe, syslogd, telnetd, unzip.</p>
+  </li>
+
+  <li><b>4 November 2007 -- BusyBox 1.8.0 (unstable)</b>
+    <p><a href="http://busybox.net/downloads/busybox-1.8.0.tar.bz2">BusyBox 1.8.0</a>.
+    (<a href="http://sources.busybox.net/index.py/branches/busybox_1_8_stable/">svn</a>,
+    <a href="http://busybox.net/downloads/fixes-1.8.0/">patches</a>,
+    <a href="http://busybox.net/fix.html">how to add a patch</a>)</p>
+
+    <p>Note: this is probably the very last release with lash. It will be dropped. Please migrate to hush.
+
+    <p>Applets which had many changes since 1.7.x:
+    <p>httpd:
+      <ul>
+       <li>does not clear environment, CGIs will see all environment variables which were set for httpd
+       <li>fix bug where we were trying to read more POSTDATA than content-length
+       <li>fix trivial bug (spotted by Alex Landau)
+       <li>optional support for partial downloads
+       <li>simplified CGI i/o loop (now it looks good to me)
+       <li>small auth and IPv6 fixes (Kim B. Heino &lt;Kim.Heino at bluegiga.com>)
+       <li>support for proxying connection to other http server (by Alex Landau &lt;landau_alex at yahoo.com>)
+      </ul>
+
+    <p>top:
+      <ul>
+       <li>TOPMEM feature - 's(how sizes)' command
+       <li>don't wait before final bailout (try top -b -n1)
+       <li>fix for command line wrapping
+      </ul>
+
+    <p>Build system improvements: libbusybox mode restored (it was lost in transition to new makefiles).
+
+    <p>Code and data size in comparison with 1.7.3:<pre>
+Equivalent .config, i386 uclibc static builds:
+   text    data     bss     dec     hex filename
+ 768123           1055   10768  779946   be6aa busybox-1.7.3/busybox
+ 759693            974    9420  770087   bc027 busybox-1.8.0/busybox</pre>
+
+    <p>New applets:
+      <ul>
+       <li>microcom: new applet by Vladimir Dronnikov &lt;dronnikov at gmail.ru&gt;
+       <li>kbd_mode: new applet by Loic Grenie &lt;loic.grenie at gmail.com&gt;
+       <li>bzip2: port bzip2 1.0.4 to busybox, 9 kb of code
+       <li>pgrep, pkill: new applets by Loic Grenie &lt;loic.grenie at gmail.com&gt;
+       <li>setsebool: new applet (Yuichi Nakamura &lt;ynakam at hitachisoft.jp&gt;)
+      </ul>
+
+    <p>Other changes since previous release (abridged):
+      <ul>
+       <li>cp: -r and -R imply -d (coreutils compat)
+       <li>cp: detect and prevent infinite recursion
+       <li>cp: make it a bit closer to POSIX, but still refuse to open and overwrite symbolic link
+       <li>hdparm: reduce possibility of numeric overflow in -T
+       <li>hdparm: simplify timing measurement
+       <li>wget: -O FILE is allowed to overwrite existing file (compat)
+       <li>wget: allow dots in header field names
+       <li>telnetd: add -K option to close sessions as soon as child exits
+       <li>telnetd: don't SIGKILL child when closing the session, kernel will send SIGHUP for us
+       <li>ed: large cleanup, add line editing
+       <li>hush: feeble attempt at making it more NOMMU-friendly
+       <li>hush: fix glob()
+       <li>hush: stop doing manual accounting of open fd's, kernel can do it for us
+       <li>adduser: implement -S and fix uid selection
+       <li>ash: fix prompt expansion (Natanael Copa &lt;natanael.copa at gmail.com&gt;)
+       <li>ash: revert "cat | jobs" fix, it causes more problems than good
+       <li>find: fix -xdev behavior in the presence of two or more nested mount points
+       <li>grep: fix grep -F -e str1 -e str2 (was matching str2 only)
+       <li>grep: optimization: stop on first -e match
+       <li>gunzip: support concatenated gz files
+       <li>inetd: fix bug 1562 "inetd does not set argv[0] properly" (fix by Ilya Panfilov)
+       <li>install: 'support' (by ignoring) -v and -b
+       <li>install: fix bug in "install -c file dir" (tried to copy dir into dir too)
+       <li>ip: tunnel parameter parsing fix by Jean Wolter &lt;jw5 at os.inf.tu-dresden.de&gt;
+       <li>isrv: use monotonic_sec
+       <li>less: make 'f' key page forward
+       <li>libiproute: add missing break statements
+       <li>load_policy: update (Yuichi Nakamura &lt;ynakam at hitachisoft.jp&gt;)
+       <li>logger: fix a problem of losing all argv except first
+       <li>login: do reject wrong passwords with PAM auth
+       <li>losetup: support -f (Loic Grenie &lt;loic.grenie at gmail.com&gt;)
+       <li>fdisk: make fdisk compile on libc without llseek64
+       <li>libbb: by popular request allow PATH to be customized at build time
+       <li>mkswap: selinux support by KaiGai Kohei &lt;kaigai at ak.jp.nec.com&gt;
+       <li>mount: allow (and ignore) -i
+       <li>mount: ignore NFS bg option on NOMMU machines
+       <li>mount: mount helpers support (by Vladimir Dronnikov &lt;dronnikov at gmail.ru&gt;)
+       <li>passwd: handle Ctrl-C, restore termios on Ctrl-C
+       <li>passwd: SELinux support by KaiGai Kohei &lt;kaigai at ak.jp.nec.com&gt;
+       <li>ping: make -I ethN work too (-I addr already worked)
+       <li>ps: fix RSS parsing (rss field in /proc/PID/stat is in pages, not bytes)
+       <li>read_line_input: fix it to not do any fancy editing if echoing is disabled
+       <li>run_parts: make it sort executables by name (required by API)
+       <li>runsv: do not use clock_gettime if !MONOTONIC_CLOCK
+       <li>runsvdir: fix "linear wait time" bug
+       <li>sulogin: remove alarm handling, it is redundant there
+       <li>svlogd: compat: svlogd -tt should timestamp stderr too
+       <li>syslogd: bail out if you see null read from Unix socket
+       <li>syslogd: do not need to poll(), we can just block in read()
+       <li>tail: work correctly on /proc files (Kazuo TAKADA &lt;kztakada at sm.sony.co.jp&gt;)
+       <li>tar + gzip/bzip2/etc: support NOMMU machines (by Alex Landau &lt;landau_alex at yahoo.com&gt;)
+       <li>tar: strip leading '/' BEFORE memorizing hardlink's name
+       <li>tftp: fix infinite retry bug
+       <li>umount: support (by ignoring) -i; style fixes
+       <li>unzip: fix endianness bugs
+       <li>vi: don't wait 50 ms before reading ESC sequences
+       <li>watchdog: allow millisecond spec (-t 250ms)
+       <li>zcip: fix unaligned trap on ARM
+      </ul>
+  </li>
+
+  <li><b>4 November 2007 -- BusyBox 1.7.3 (stable)</b>
+    <p><a href="http://busybox.net/downloads/busybox-1.7.3.tar.bz2">BusyBox 1.7.3</a>.
+    (<a href="http://sources.busybox.net/index.py/branches/busybox_1_7_stable/">svn</a>,
+    <a href="http://busybox.net/downloads/fixes-1.7.3/">patches</a>,
+    <a href="http://busybox.net/fix.html">how to add a patch</a>)</p>
+
+    <p>This is a bugfix-only release, with fixes to ash, httpd, inetd, iptun, logger, login, tail.</p>
+  </li>
+
+  <li><b>30 September 2007 -- BusyBox 1.7.2 (stable)</b>
+    <p><a href="http://busybox.net/downloads/busybox-1.7.2.tar.bz2">BusyBox 1.7.2</a>.
+    (<a href="http://sources.busybox.net/index.py/branches/busybox_1_7_stable/">svn</a>,
+    <a href="http://busybox.net/downloads/fixes-1.7.2/">patches</a>,
+    <a href="http://busybox.net/fix.html">how to add a patch</a>)</p>
+
+    <p>This is a bugfix-only release, with fixes to install, find, login, httpd, runsvdir, chcon, setfiles, fdisk and line editing.</p>
+  </li>
+
+  <li><b>16 September 2007 -- BusyBox 1.7.1 (stable)</b>
+    <p><a href="http://busybox.net/downloads/busybox-1.7.1.tar.bz2">BusyBox 1.7.1</a>.
+    (<a href="http://sources.busybox.net/index.py/branches/busybox_1_7_stable/">svn</a>,
+    <a href="http://busybox.net/downloads/fixes-1.7.1/">patches</a>,
+    <a href="http://busybox.net/fix.html">how to add a patch</a>)</p>
+
+    <p>This is a bugfix-only release, with fixes to cp, runsv, tar, busybox --install and build system.</p>
+  </li>
+
+  <li><b>24 August 2007 -- BusyBox 1.7.0 (unstable)</b>
+    <p><a href="http://busybox.net/downloads/busybox-1.7.0.tar.bz2">BusyBox 1.7.0</a>.
+    (<a href="http://sources.busybox.net/index.py/branches/busybox_1_7_stable/">svn</a>,
+    <a href="http://busybox.net/downloads/fixes-1.7.0/">patches</a>,
+    <a href="http://busybox.net/fix.html">how to add a patch</a>)</p>
+
+    <p>Applets which had many changes since 1.6.x:
+    <p>httpd:
+      <ul>
+       <li>works in standalone mode on NOMMU machines now (partly by Alex Landau &lt;landau_alex at yahoo.com&gt;)
+       <li>indexer example is rewritten in C
+       <li>optional support for error pages (by Pierre Metras &lt;genepi at sympatico.ca&gt;)
+       <li>stop reading headers using 1-byte reads
+       <li>new option -v[v]: prints client addresses, HTTP codes returned, URLs
+       <li>extended -p PORT to -p [IP[v6]:]PORT
+       <li>sendfile support (by Pierre Metras &lt;genepi at sympatico.ca&gt;)
+       <li>add support for Status: CGI header
+       <li>fix CGI handling bug (we were closing wrong fd)
+       <li>CGI I/O loop still doesn't look 100% ok to me...
+      </ul>
+
+    <p>udhcp[cd]:
+      <ul>
+       <li>add -f "foreground" and -S "syslog" options
+       <li>fixed "ifupdown + udhcpc_without_pidfile_creation" bug
+       <li>new config option "Rewrite the lease file at every new acknowledge" (Mats Erik Andersson &lt;mats at blue2net.com&gt; (Blue2Net AB))
+       <li>consistently treat server_config.start/end IPs as host-order
+       <li>fix IP parsing for 64bit machines
+       <li>fix unsafe hton macro usage in read_opt()
+       <li>do not chdir to / when daemonizing
+      </ul>
+
+    <p>top, ps, killall, pidof:
+      <ul>
+       <li>simpler loadavg processing
+       <li>truncate usernames to 8 chars
+       <li>fix non-CONFIG_DESKTOP ps -ww (by rockeychu)
+       <li>improve /proc/PID/cmdinfo reading code
+       <li>use cmdline, not comm field (fixes problems with re-execed applets showing as processes with name "exe", and not being found by pidof/killall by applet name)
+       <li>reduce CPU usage in decimal conversion (optional) (corresponding speedup on kernel side is accepted in mainline Linux kernel, yay!)
+       <li>make percentile (0.1%) calculations configurable
+       <li>add config option and code for global CPU% display
+       <li>reorder columns, so that [P]PIDs are together and VSZ/%MEM are together - makes more sense
+      </ul>
+
+    <p>Build system improvements: doesn't link against libraries we don't need,
+       generates verbose link output and map file, allows for custom link
+       scripts (useful for removing extra padding, among other things).
+
+    <p>Code and data size in comparison with 1.6.1:<pre>
+Equivalent .config, i386 glibc dynamic builds:
+   text    data     bss     dec     hex filename
+ 672671    2768   16808  692247   a9017 busybox-1.6.1/busybox
+ 662948    2660   13528  679136   a5ce0 busybox-1.7.0/busybox
+ 662783    2631   13416  678830   a5bae busybox-1.7.0/busybox.customld
+
+Same .config built against static uclibc:
+ 765021    1059   11020  777100   bdb8c busybox-1.7.0/busybox_uc</pre>
+
+    <p>Code/data shrink done in applets: crond, hdparm, dd, cal, od, nc, expr, uuencode,
+       test, slattach, diff, ping, tr, syslogd, hwclock, zcip, find, pidof, ash, uudecode,
+       runit/*, in libbb.
+
+    <p>New applets:
+      <ul>
+       <li>pscan, expand, unexpand (from Tito &lt;farmatito at tiscali.it&gt;)
+       <li>setfiles, restorecon (by Yuichi Nakamura &lt;ynakam at hitachisoft.jp&gt;)
+       <li>chpasswd (by Alexander Shishkin &lt;virtuoso at slind.org&gt;)
+       <li>slattach, ttysize
+      </ul>
+
+    <p>Unfortunately, not much work is done on shells. This was mostly stalled
+       by lack of time (read: laziness) on my part to learn how to adapt existing
+       qemu-runnable image for a NOMMU architechture (available on qemu website)
+       for local testing of cross-compiled busybox on my machine.
+
+    <p>Other changes since previous release (abridged):
+      <ul>
+       <li>addgroup: disallow addgroup -g num user group; make -g 0 work (Tito &lt;farmatito at tiscali.it&gt;)
+       <li>adduser: close /etc/{passwd,shadow} before calling passwd etc. Spotted by Natanael Copa &lt;natanael.copa at gmail.com&gt;
+       <li>arping: -i should be -I, fixed
+       <li>ash: make "jobs | cat" work like in bash (was giving empty output)
+       <li>ash: recognize -l as --login equivalent; do not recognize +-login
+       <li>ash: fix buglet in DEBUG code (Nguyen Thai Ngoc Duy &lt;pclouds at gmail.com&gt;)
+       <li>ash: fix SEGV if type has zero parameters
+       <li>awk: fix -F 'regex' bug (miscounted fields if last field is empty)
+       <li>catv: catv without arguments was trying to use environ as argv (Alex Landau &lt;landau_alex at yahoo.com&gt;)
+       <li>catv: don't die on open error (emit warning)
+       <li>chown/chgrp: completely match coreutils 6.8 wrt symlink handling
+       <li>correct_password: do not print "no shadow passwd..." message
+       <li>crond: don't start sendmail with absolute path, don't report obsolete version (report true bbox version)
+       <li>dd: fix bug where we assume count=INT_MAX when count is unspecified
+       <li>devfsd: sanitization by Tito &lt;farmatito at tiscali.it&gt;
+       <li>echo: fix non-fancy echo
+       <li>fdisk: make it work with big disks (read: typical today's disks) even if CONFIG_LFS is unset
+       <li>find: -context support for SELinux (KaiGai Kohei &lt;kaigai at kaigai.gr.jp&gt;)
+       <li>find: add conditional support for -maxdepth and -regex, make -size match GNU find
+       <li>find: fix build failure on certain configs (found by Cristian Ionescu-Idbohrn &lt;cristian.ionescu-idbohrn at axis.com&gt;)
+       <li>fsck_minix: make it print bb version, not it's own (outdated/irrelevant) one
+       <li>grep: implement -m MAX_MATCHES, fix buglets with context printing
+       <li>grep: fix selection done by FEATURE_GREP_EGREP_ALIAS (Maxime Bizon &lt;mbizon at freebox.fr&gt; (Freebox))
+       <li>hush: add missing dependencies (Maxime Bizon &lt;mbizon at freebox.fr&gt; (Freebox))
+       <li>hush: fix read builtin to not read ahead past EOL and to not use insane amounts of stack
+       <li>ifconfig: make it work with ifaces with interface no. &gt; 255
+       <li>ifup/ifdown: make location of ifstate configurable
+       <li>ifupdown: make netmask parsing smaller and more strict (was accepting 255.0.255.0, 255.1234.0.0 etc...)
+       <li>install: fix -s (strip) option, fix install a b /a/link/to/dir
+       <li>libbb: consolidate ARRAY_SIZE macro (Walter Harms &lt;wharms at bfs.de&gt;)
+       <li>libbb: make /etc/network parsing configurable. -200 bytes when off
+       <li>libbb: nuke BB_GETOPT_ERROR, always die if there are mutually exclusive options
+       <li>libbb: xioctl and friends by Tito &lt;farmatito at tiscali.it&gt;
+       <li>login: optional support for PAM
+       <li>login: make /etc/nologin support configurable (-240 bytes)
+       <li>login: ask passwords even for wrong usernames
+       <li>md5_sha1_sum: fix mishandling when run as /bin/md5sum
+       <li>mdev: add support for firmware loading
+       <li>mdev: work even when CONFIG_SYSFS_DEPRECATED in kernel is off
+       <li>modprobe: add scanning of /lib/modules/`uname -r`/modules.symbols (by Yann E. MORIN &lt;yann.morin.1998 at anciens.enib.fr&gt;)
+       <li>more: fixes by Tristan Schmelcher &lt;tpkschme at engmail.uwaterloo.ca&gt;
+       <li>nc: make connecting to IPv4 from IPv6-enabled hosts easier (was requiring -s local_addr)
+       <li>passwd: fix bug "updating shadow even if user's record is in passwd"
+       <li>patch: fix -p -1 handling
+       <li>patch: fix bad line ending handling (Nguyen Thai Ngoc Duy &lt;pclouds at gmail.com&gt;)
+       <li>ping: display roundtrip times with 1/1000th of ms, not 1/10 ms precision.
+       <li>ping: fix incorrect handling of -I (Iouri Kharon &lt;bc-info at styx.cabel.net&gt;)
+       <li>ping: fix non-fancy ping6
+       <li>printenv: fix "printenv VAR1 VAR2" bug (spotted by Kalyanatejaswi Balabhadrapatruni &lt;kalyanatejaswi at yahoo.co.in&gt;)
+       <li>ps: fix -Z (by Yuichi Nakamura &lt;ynakam at hitachisoft.jp&gt;)
+       <li>rpm: add optional support for bz2 data. +50 bytes of code
+       <li>rpm: fix bogus "package is not installed" case
+       <li>sed: fix 'q' command handling (by Nguyen Thai Ngoc Duy &lt;pclouds at gmail.com&gt;)
+       <li>start_stop_daemon: NOMMU fixes by Alex Landau &lt;landau_alex at yahoo.com&gt;
+       <li>stat: fix option -Z SEGV
+       <li>strings: strings a b was processing a twice, fix that
+       <li>svlogd: fix timestamping, do not warn if config is missing
+       <li>syslogd, logread: get rid of head pointer, fix logread bug in the process
+       <li>syslogd: do not convert tabs to ^I, set syslog IPC buffer to mode 0644
+       <li>tar: improve OLDGNU compat, make old SUN compat configurable
+       <li>test: fix testing primary expressions like '"-u" = "-u"'
+       <li>uudecode: fix to base64 decode by Jorgen Cederlof &lt;jcz at google.com&gt;
+       <li>vi: multiple fixes by Natanael Copa &lt;natanael.copa at gmail.com&gt;
+       <li>wget: fix bug in base64 encoding (bug 1404). +10 bytes
+       <li>wget: lift 256 chars limitation on terminal width
+       <li>wget, zcip: use monotonic_sec instead of gettimeofday
+      </ul>
+  </li>
+
+  <li><b>30 June 2007 -- BusyBox 1.6.1 (stable)</b>
+    <p><a href="http://busybox.net/downloads/busybox-1.6.1.tar.bz2">BusyBox 1.6.1</a>.
+    (<a href="http://sources.busybox.net/index.py/branches/busybox_1_6_stable/">svn</a>,
+    <a href="http://busybox.net/downloads/fixes-1.6.1/">patches</a>,
+    <a href="http://busybox.net/fix.html">how to add a patch</a>)</p>
+
+    <p>This is a bugfix-only release, with fixes to echo, hush, and wget.</p>
+  </li>
+
+  <li><b>1 June 2007 -- BusyBox 1.6.0 (unstable)</b>
+    <p><a href="http://busybox.net/downloads/busybox-1.6.0.tar.bz2">BusyBox 1.6.0</a>.
+    (<a href="http://sources.busybox.net/index.py/branches/busybox_1_6_stable/">svn</a>,
+    <a href="http://busybox.net/downloads/fixes-1.6.0/">patches</a>,
+    <a href="http://busybox.net/fix.html">how to add a patch</a>)</p>
+
+    <p>Since this is a x.x.0 release, it probably does not deserve "stable"
+    label. Please help making 1.6.1 stable by testing 1.6.0.</p>
+    <p>Note that hush shell had many changes and (hopefully) is much improved now,
+    but there is a possibility that it regressed in some obscure cases. Please
+    report any such cases.</p>
+    <p>lash users please note: lash is going to be deprecated in busybox 1.7.0
+    and removed in the more distant future. Please migrate to hush.</p>
+    <p><a href="http://busybox.net/~vda/mem_usage-1.6.0.txt">Memory usage has decreased, but we can do better still</a></p>
+    <p>Other changes since previous release:
+    <ul>
+<li>NOFORK: audit small applets and mark some of them as NOFORK. Put big scary warnings in relevant places
+<li>NOFORK: factor out NOFORK/NOEXEC code from find. Use NOFORK/NOEXEC in find and xargs
+<li>NOFORK: remove potential xmalloc from NOFORK path in bb_full_fd_action
+<li>NOMMU: random fixes; compressed --help now works for NOMMU
+<li>SELinux: load_policy applet
+<li>[u]mount: extend -t option (Roy Marples &lt;uberlord at gentoo.org&gt;)
+<li>addgroup: clean up, fix adding users to existing groups and make it optional (Tito)
+<li>adduser: don't bomb out if shadow password file doesn't exist (from Tito &lt;farmatito at tiscali.it&gt;)
+<li>applet.c: do not even try to read config if run by real root; fix suid config handling
+<li>ash: fix infinite loop on exit if tty is not there anymore
+<li>ash: fix kill -l (by Mats Erik Andersson &lt;mats.andersson64 at comhem.se&gt;)
+<li>ash: implement type -p, costs less than 10 bytes (patch by Mats Erik Andersson &lt;mats.andersson64 at comhem.se&gt;)
+<li>awk: don't segfault on printf(%*s). Closes bug 1337
+<li>awk: guard against empty environment
+<li>awk: some 'lineno' vars were shorts, made them ints (code got smaller)
+<li>cat: stop using stdio.h opens
+<li>config system: clarify PREFER_APPLETS/SH_STANDALONE effects in help text
+<li>cryptpw: new applet (by Thomas Lundquist &lt;lists at zelow.no&gt;)
+<li>cttyhack: new applet
+<li>dd: NOEXEC fix; fix skip= parse error (spotted by Dirk Clemens &lt;develop at cle-mens.de&gt;)
+<li>deluser: add optional support for removing users from groups (by Tito &lt;farmatito at tiscali.it&gt;)
+<li>diff: fix SEGV (NULL deref) in diff -N
+<li>diff: fix segfault on empty dirs (Peter Korsgaard &lt;peter.korsgaard at barco.com&gt;)
+<li>dnsd: fix several buglets, make smaller; openlog(), so that applet's name is logged
+<li>dpkg: run_package_script() returns 0 if all ok and non-zero if failure. The result code was checked incorrectly in two places. (from Kim B. Heino &lt;Kim.Heino at bluegiga.com&gt;)
+<li>dpkg: use bitfields which are a bit closer to typical short/char. Code size -800 bytes
+<li>dumpleases: getopt32()-ization (from Mats Erik Andersson &lt;mats.andersson64 at comhem.se&gt;)
+<li>e2fsprogs: stop using statics in chattr. Minor code shrinkage (-130 bytes)
+<li>ether-wake: close bug 1317. Reorder fuctions to avoid forward refs while at it
+<li>ether-wake: save a few more bytes of code
+<li>find: -group, -depth (Natanael Copa &lt;natanael.copa at gmail.com&gt;)
+<li>find: add support for -delete, -path (by Natanael Copa)
+<li>find: fix -prune. Add big comment about it
+<li>find: improve usage text (Natanael Copa &lt;natanael.copa at gmail.com&gt;)
+<li>find: missed 'static' on const data; size and prune were mixed up; use index_in_str_array
+<li>find: un-DESKTOPize (Kai Schwenzfeier &lt;niteblade at gmx.net&gt;)
+<li>find_root_device: teach to deal with /dev/ subdirs (by Kirill K. Smirnov &lt;lich at math.spbu.ru&gt;)
+<li>find_root_device: use lstat - don't follow links
+<li>getopt32: fix llist_t options ordering. llist_rev is now unused
+<li>getopt: use getopt32 for option parsing - inspired by patch by Mats Erik Andersson &lt;mats.andersson64 at comhem.se&gt;
+<li>hdparm: fix multisector mode setting (from Toni Mirabete &lt;amirabete at catix.cat&gt;)
+<li>hdparm: make -T -t code smaller (-194 bytes), and output prettier
+<li>ifupdown: make it possible to use DHCP clients different from udhcp
+<li>ifupdown: reread state file before rewriting it. Fixes "ifup started another ifup" state corruption bug. Patch by Natanael Copa &lt;natanael.copa at gmail.com&gt;
+<li>ifupdown: small optimization (avoid doing useless work if we are not going to update state file)
+<li>ip: fix compilation if FEATURE_TR_CLASSES is off
+<li>ip: mv ip*_main into ip.c; use a dispatcher to save on needless duplication. Saves a minor 12b
+<li>ip: rewrite the ip applet to be less bloaty. Convert to index_in_(sub)str_array()
+<li>ip: set the scope properly. Thanks to Jean Wolter
+<li>iplink: shrink iplink; sanitize libiproute a bit (-916 bytes)
+<li>iproute: shrink a bit (-200 bytes)
+<li>kill: know much more signals; make code smaller; use common code for kill applet and ash kill builtin
+<li>klogd: remove dependency on syslogd
+<li>lash: "forking" applets are actually can be treated the same way as "non-forked". Also save a bit of space on trailing NULL array elements.
+<li>lash: fix kill buglet (didn't properly recognize ESRCH)
+<li>lash: make -c work; crush buffer overrun and free of non-malloced ptr (from Mats Erik Andersson &lt;mats.andersson64 at comhem.se&gt;)
+<li>lash: recognize and use NOFORK applets
+<li>less: fix case when regex search finds nothing; fix very obscure memory corruption bug; fix less &lt;HUGEFILE + [End] busy loop
+<li>libbb: add xsendto, xunlink, xpipe
+<li>libbb: fix segfault in reset_ino_dev_hashtable() when *hashtable was NULL
+<li>libbb: make pidfile writing configurable
+<li>libbb: make xsocket die with address family printed (if VERBOSE_RESOLUTION_ERRORS=y)
+<li>libbb: rework NOMMU helper API so that it makes more sense and easier to use
+<li>libiproute: audit callgraph, shortcut error paths into die() functions
+<li>lineedit: do not try to open NULL history file
+<li>lineedit: nuke two unused variables and code which sets them
+<li>login: remove setpgrp call (makes it work from shell prompt again); sanitize stdio descriptors (we are suid, need to be careful!)
+<li>login: shrink login and set_environment by ~100 bytes
+<li>mount: fix incorrect usage of strtok (inadvertently used NULL sometimes)
+<li>mount: fix mounting of symlinks (mount from util-linux allows that)
+<li>msh: data/bss reduction (more than 9k of it); fix "underscore bug" (a_b=1111 didn't work); fix obscure case with backticks and closed fd 1
+<li>nc: port nc 1.10 to busybox
+<li>netstat: fix for bogus state value for raw sockets
+<li>netstat: introduce -W: wide, ipv6-friendly output; shrink by ~500 bytes
+<li>nmeter: should die if stdout doesn't like him anymore
+<li>patch: do not try to delete same file twice
+<li>ping: fix wrong sign extension of packet id (bug 1373)
+<li>ps: add -o tty and -o rss support; make a bit smaller; work around libc bug: printf("%.*s\n", MAX_INT, buffer)
+<li>run_parts: rewrite
+<li>run_parts: do not check path portion of a name for "bad chars". Needed for ifupdown. Patch by Gabriel L. Somlo &lt;somlo at cmu.edu&gt;
+<li>sed: fix escaped newlines in -f
+<li>split: new applet
+<li>stat: remove superfluous bss user (flags) and manually unswitch some areas
+<li>stty: fix option parsing bug (spotted by Sascha Hauer &lt;s.hauer at pengutronix.de&gt;)
+<li>svlogd: fix 'SEGV on uninitialized data' and make it honor TERM
+<li>tail: fix SEGV on "tail -N"
+<li>ipsvd: tcpsvd,udpsvd are new applets, GPL-ed 'clones' of Dan Bernstein's tcpserver. Author: Gerrit Pape &lt;pape at smarden.org&gt;, http://smarden.sunsite.dk/ipsvd/
+<li>test: close bug 1371; plug a memory leak; code size reduction
+<li>tftp: code diet, and I think retransmits were broken
+<li>tr: fix bug where we did not reject invalid classes like '[[:alpha'. debloat while at it
+<li>udhcp: MAC_BCAST_ADDR and blank_chaddr are in fact constant, move to rodata; use pipe instead of socketpair
+<li>udhcp[cd]: stop using atexit magic fir pidfile removal; stop deleting our own pidfile if we daemonize
+<li>xargs: shrink code, ~80 bytes; simplify word list management
+<li>zcip: make it work on NOMMU (+ improve NOMMU support machinery)
+    </ul>
+  </li>
+
+  <li><b>20 May 2007 -- BusyBox 1.5.1 (stable)</b>
+    <p><a href="http://busybox.net/downloads/busybox-1.5.1.tar.bz2">BusyBox 1.5.1</a>.
+    (<a href="http://busybox.net/downloads/fixes-1.5.1/">patches</a>,
+    <a href="http://busybox.net/fix.html">how to add a patch</a>)</p>
+
+    <p>This is a bugfix-only release, with fixes to hdparm, hush, ifupdown, ps
+    and sed.</p>
+  </li>
+
+  <li><b>23 March 2007 -- BusyBox 1.5.0 (unstable)</b>
+    <p><a href="http://busybox.net/downloads/busybox-1.5.0.tar.bz2">BusyBox 1.5.0</a>.
+    (<a href="http://busybox.net/downloads/fixes-1.5.0/">patches</a>,
+    <a href="http://busybox.net/fix.html">how to add a patch</a>)</p>
+
+    <p>Since this is a x.x.0 release, it probably does not deserve "stable"
+    label. Please help making 1.5.1 stable by testing 1.5.0.</p>
+    <p>Notable changes since previous release:
+    <ul>
+    <li>find: added support for -user, -not, fixed -mtime, -mmin, -perm
+    <li>[de]archivers: merge common logic into one module
+    <li>ping[6]: unified code for both
+    <li>less: regex search improved
+    <li>ash: more readable code, testsuite added
+    <li>sed: several very obscure bugs fixed
+    <li>chown: -H, -L, -P support (required by POSIX)
+    <li>tar: handle (broken) checksums a-la Sun; tar restores mode again
+    <li>grep: implement -w, "implement" -a and -I by ignoring them
+    <li>cp: more sane behavior when overwriting existing files
+    <li>init: stop doing silly things with the console (-400 bytes)
+    <li>httpd: make httpd usable for NOMMU CPUs; fix POSTDATA handling bugs
+    <li>httpd: run interpreter for configured file extensions in any dir,
+        not only in /cgi-bin/
+    <li>chrt: new applet
+    <li>SELinux: SELinux-related code and -Z option added to several applets,
+        new SELinux-specific applets: chcon, runcon.
+    <li>Build system: produces link map, uses -Wwrite-strings to catch
+        improper usage of string constants.
+    <li>Data and bss section usage audited and reduced - should help NOMMU
+        targets.
+    <li>Applets with bug fixes: gunzip, vi, syslogd, dpkg, ls, adjtimex, resize,
+        sv, printf, diff, awk, sort, dpkg, diff, tftp
+    <li>Applets with usability improvements: swapon, more, ifup/ifdown, hwclock,
+        udhcpd, start_stop_daemon, cmp
+    <li>Applets with code cleaned up: telnet, fdisk, fsck_minix, mkfs_minix,
+        syslogd, swapon, runsv, svlogd, klogd
+    </ul>
+  </li>
+
+  <li><b>18 March 2007 -- BusyBox 1.4.2 (stable)</b>
+    <p><a href="http://busybox.net/downloads/busybox-1.4.2.tar.bz2">BusyBox 1.4.2</a>.
+    </p>
+
+    <p>This release includes only trivial fixes accumulated since 1.4.1.
+    </p>
+  </li>
+
+  <li><b>25 January 2007 -- BusyBox 1.4.1 (stable)</b>
+    <p><a href="http://busybox.net/downloads/busybox-1.4.1.tar.bz2">BusyBox 1.4.1</a>.
+    (<a href="http://busybox.net/downloads/fixes-1.4.1/">patches</a>)</p>
+
+    <p>This release includes only trivial fixes accumulated since 1.4.0.
+    </p>
+  </li>
+
+  <li><b>20 January 2007 -- BusyBox 1.4.0 (stable)</b>
+    <p><a href="http://busybox.net/downloads/busybox-1.4.0.tar.bz2">BusyBox 1.4.0</a>.
+    (<a href="http://busybox.net/downloads/fixes-1.4.0/">patches</a>)</p>
+
+    <p>Since this is a x.x.0 release, it probably is a bit less "stable"
+    than usual.</p>
+    <p>Changes since previous release:
+    <ul>
+    <li>e2fsprogs are mostly removed from busybox. Some smaller parts remain,
+    the rest of it sits disabled in e2fsprogs/old_e2fsprogs/*, because
+    it's too bloated. Really. I'm afraid it's about the only way we can
+    ever get e2fsprogs cleaned up.
+    <li>less: many improvements. Now can display binary files
+    (although I expect it to have trouble with displays where 8bit chars
+    don't have 1-to-1 char/glyph relationship). Regexp search is not buggy
+    anymore. Less does not read entire input up-front. Reads input
+    as it appears (yay!). Works rather nice as man pager. I recommend it
+    for general use now.
+    <li>IPv6: generic support is in place, many networking applets are
+    upgraded to be IPv6 capable. Probably some work remains, but it is
+    already much better than what we had previously.
+    <li>arp: new applet (thanks to Eric Spakman).
+    <li>fakeidentd: non-forking standalone server part was taking ~90%
+    of the applet. Factored it out (in fact, rewrote it).
+    <li>syslogd: mostly rewritten.
+    <li>decompress_unzip, gzip: sanitized a bit.
+    <li>sed: better hadling of NULs
+    <li>httpd: stop adding our own "Content-type:" to CGI output
+    <li>chown: user.grp works again.
+    <li>minor bugfixes to: passwd, date, tftp, start_stop_daemon, tar,
+    ps, ifupdown, time, su, stty, awk, ping[6], sort,...
+    </ul>
+  </li>
+
+  <li><b>20 January 2007 -- BusyBox 1.3.2 (stable)</b>
+    <p><a href="http://busybox.net/downloads/busybox-1.3.2.tar.bz2">BusyBox 1.3.2</a>.</p>
+
+    <p>This release includes only one trivial fix accumulated since 1.3.1
+    </p>
+  </li>
+
+  <li><b>27 December 2006 -- BusyBox 1.3.1 (stable)</b>
+    <p><a href="http://busybox.net/downloads/busybox-1.3.1.tar.bz2">BusyBox 1.3.1</a>.
+    (<a href="http://busybox.net/downloads/fixes-1.3.1/">patches</a>)</p>
+
+    <p>Closing 2006 with new release. It includes only trivial fixes accumulated since 1.3.0
+    </p>
+  </li>
+
+  <li><b>14 December 2006 -- BusyBox 1.3.0 (stable)</b>
+    <p><a href="http://busybox.net/downloads/busybox-1.3.0.tar.bz2">BusyBox 1.3.0</a>.
+    (<a href="http://busybox.net/downloads/fixes-1.3.0/">patches</a>)</p>
+
+    <p>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.
+    <p>Changes since previous release:
+    <ul>
+    <li>find: taking many more of standard options
+    <li>ps: POSIX-compliant -o implemented
+    <li>cp: added -s, -l
+    <li>grep: added -r, fixed -h
+    <li>watch: make it exec child like standard one does (was totally
+        incompatible)
+    <li>tar: fix limitations which were preventing bbox tar usage
+        on 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).
+    <li>httpd: added -i (inetd), -f (foreground), support for
+        directory indexer CGI (example is included), bugfixes.
+    <li>telnetd: fixed/improved IPv6 support, inetd+standalone support,
+        other fixes. Useful IPv6 stuff factored out into libbb.
+    <li>runit/*: new applets adapted from http://smarden.sunsite.dk/runit/
+        (these are my personal favorite small-and-beautiful toys)
+    <li>minor bugfixes to: login, dd, mount, umount, chmod, chown, ln, udhcp,
+        fdisk, ifconfig, sort, tee, mkswap, wget, insmod.
+    </ul>
+    <p>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.
+    </p>
+  </li>
+
+  <li><b>29 October 2006 -- BusyBox 1.2.2.1 (fix)</b>
+    <p><a href="http://busybox.net/downloads/busybox-1.2.2.1.tar.bz2">BusyBox 1.2.2.1</a>.</p>
+
+    <p>Added compile-time warning that static linking against glibc
+    produces buggy executables.
+  </li>
+
+  <li><b>24 October 2006 -- BusyBox 1.2.2 (stable)</b>
+    <p>It's a bit overdue, but
+    <a href="http://busybox.net/downloads/busybox-1.2.2.tar.bz2">here is
+    BusyBox 1.2.2</a>.</p>
+
+    <p>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.</p>
+
+    <p>As <a href="http://lwn.net/Articles/202106/">Linux Weekly News noted</a>,
+    this is my (Rob's) last release of BusyBox.  The new maintainer is Denis
+    Vlasenko, I'm off to do <a href="http://landley.net/code">other things</a>.
+    </p>
+  </li>
+
+  <li><b>29 September 2006 -- New license email address.</b>
+    <p>The email address gpl@busybox.net is now the recommended way to contact
+    the Software Freedom Law Center to report BusyBox license violations.</p>
+
+  <li><b>31 July 2006 -- BusyBox 1.2.1 (stable)</b>
+    <p>Since nobody seems to have objected too loudly over the weekend, I
+    might as well point you all at
+    <a href="http://busybox.net/downloads/busybox-1.2.1.tar.bz2">Busybox
+    1.2.1</a>, a bugfix-only release with no new features.</p>
+
+    <p>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.</p>
+
+    <p>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.</p>
+
+  <li><b>30 June 2006 -- BusyBox 1.2.0</b>
+    <p>The -devel branch has been stabilized and the result is
+    <a href="http://busybox.net/downloads/busybox-1.2.0.tar.bz2">Busybox
+    1.2.0</a>.  Lots of stuff changed, I need to work up a decent changelog
+    over the weekend.</p>
+
+    <p>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. :)</p>
+
+    <p>Update: Here are <a href="http://busybox.net/downloads/busybox-1.2.0.fixes.patch">the first few bug fixes</a> that will go into 1.2.1.</p>
+
+  <li><b>17 May 2006 -- BusyBox 1.1.3 (stable)</b>
+    <p><a href="http://busybox.net/downloads/busybox-1.1.3.tar.bz2">BusyBox
+    1.1.3</a> 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.</p>
+
+    <p>Expect 1.2.0 sometime next month, which won't be a bugfix release.</p>
+
+  <li><b>10 April 2006 -- BusyBox 1.1.2 (stable)</b>
+    <p>You can now download <a href="http://busybox.net/downloads/busybox-1.1.2.tar.bz2">BusyBox 1.1.2</a>, 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.</p>
+
+    <p>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.)</p>
+
+  <li><b>27 March 2006 -- Software Freedom Law Center representing BusyBox and uClibc</b>
+    <p>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
+    <a href="http://www.busybox.net/shame.html">Hall of Shame</a>), 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
+    <a href="http://www.groklaw.net">Groklaw</a> for suggestions.  She
+    referred us to the fine folks at softwarefreedom.org.</p>
+
+    <p>As a result, we're pleased to announce that the
+    <a href="http://www.softwarefreedom.org">Software Freedom Law Center</a>
+    has agreed to represent BusyBox and uClibc.  We join a number of other
+    free and open source software projects (such as
+    <a href="http://lwn.net/Articles/141806/">X.org</a>,
+    <a href="http://lwn.net/Articles/135413/">Wine</a>, and
+    <a href="http://plone.org/foundation/newsitems/software-freedom-law-center-support/">Plone</a>
+    in being represented by a fairly cool bunch of lawyers, which is not a
+    phrase you get to use every day.</p>
+
+  <li><b>22 March 2006 -- BusyBox 1.1.1</b>
+    <p>The new maintainer is Rob Landley, and the new release is <a href="http://busybox.net/downloads/busybox-1.1.1.tar.bz2">BusyBox 1.1.1</a>.  Expect a "what's new" document in a few days.  (Also, Erik and I have have another announcement pending...)</p>
+    <p>Update: Rather than put out an endless stream of 1.1.1.x releases,
+    the various small fixes have been collected together into a
+    <a href="http://busybox.net/downloads/busybox-1.1.1.fixes.patch">patch</a>,
+    and new fixes will be appended to that as needed.  Expect 1.1.2 around
+    June.</p>
+  </li>
+  <li><b>11 January 2006 -- 1.1.0 is out</b>
+    <p>The new stable release is
+    <a href="http://www.busybox.net/downloads/busybox-1.1.0.tar.bz2">BusyBox
+    1.1.0</a>.  It has a number of improvements, including several new applets.
+    (It also has <a href="http://www.busybox.net/lists/busybox/2006-January/017733.html">a few rough spots</a>,
+    but we're trying out a "release early, release often" strategy to see how
+    that works.  Expect 1.1.1 sometime in March.)</p>
+
+  <li><b>31 October 2005 -- 1.1.0-pre1</b>
+    <p>The development branch of busybox is stable enough for wider testing, so
+    you can now
+    <a href="http://www.busybox.net/downloads/busybox-1.1.0-pre1.tar.bz2">download</a>,
+    the first prerelease of 1.1.0.  This prerelease includes a lot of
+    <a href="http://www.busybox.net/downloads/BusyBox.html">new
+    functionality</a>: new applets, new features, and extensive rewrites of
+    several existing applets.  This prerelease should be noticeably more
+    <a href="http://www.opengroup.org/onlinepubs/009695399/">standards
+    compliant</a> than earlier versions of busybox, although we're
+    still working out the <a href="https://bugs.busybox.net">bugs</a>.</p>
+
+  <li><b>16 August 2005 -- 1.01 is out</b>
+
+    <p>A new stable release (<a href="http://www.busybox.net/downloads/busybox-1.01.tar.bz2">BusyBox
+    1.01</a>) is now available for download, containing over a hundred
+    <a href="http://www.busybox.net/lists/busybox/2005-August/015424.html">small
+    fixes</a> that have cropped up since the 1.00 release.</p>
+
+  <li><b>13 January 2005 -- Bug and Patch Tracking</b><p>
+
+    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
+    <a href="https://bugs.busybox.net/">shiny new Bug and Patch Tracking System</a>
+    to post all the gory details.
+
+    <p>
+
+    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.
+
+
+  <p>
+  <li><b>13 October 2004 -- BusyBox 1.00 released</b><p>
+
+    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.
+
+    <p>
+
+    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!
+
+    <p>
+
+    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.
+
+    <p>
+
+    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 <a
+    href="FAQ.html">BusyBox FAQ</a>.
+
+    <p>
+
+    As usual you can <a href="downloads">download busybox here</a>.
+
+    <p>Have Fun!
+
+    <p>
+    <li><b>Old News</b><p>
+    <a href="/oldnews.html">Click here to read older news</a>
+
+
+  <li><b>16 August 2004 -- BusyBox 1.0.0-rc3 released</b><p>
+
+    Here goes release candidate 3...
+    <p>
+    The <a href="downloads/Changelog">changelog</a> has all the details.
+    And as usual you can <a href="downloads">download busybox here</a>.
+
+    <p>Have Fun!
+
+  <p>
+  <li><b>26 July 2004 -- BusyBox 1.0.0-rc2 released</b><p>
+
+    Here goes release candidate 2...
+    <p>
+    The <a href="downloads/Changelog">changelog</a> has all the details.
+    And as usual you can <a href="downloads">download busybox here</a>.
+
+    <p>Have Fun!
+
+  <p>
+  <li><b>20 July 2004 -- BusyBox 1.0.0-rc1 released</b><p>
+
+    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.
+    <p>
+
+    I <b>really</b> 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....
+    <p>
+
+    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!
+
+    <p>
+    The <a href="downloads/Changelog">changelog</a> has all the details.
+    And as usual you can <a href="downloads">download busybox here</a>.
+
+    <p>Have Fun!
+
+    <p>
+    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 <a
+    href="http://www.linuxsymposium.org/2004/">OLS</a> and give my presentation
+    as scheduled.
+    <p>
+    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.
+    <p>
+
+
+  <p>
+  <li><b>13 April 2004 -- BusyBox 1.0.0-pre10 released</b><p>
+
+    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 <b>really</b>
+    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 <b>very</b> helpful if
+    people could continue to review the BusyBox documentation and submit
+    improvements.
+
+    <p>
+    The <a href="downloads/Changelog">changelog</a> has all the details.
+    And as usual you can <a href="downloads">download busybox here</a>.
+
+    <p>Have Fun!
+    <p>
+
+
+  <p>
+  <li><b>6 April 2004 -- BusyBox 1.0.0-pre9 released</b><p>
+
+    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
+    <b>very</b> 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.
+
+    <p>
+    I had hoped to get this released a month ago, but
+    <a href="http://codepoet.org/gallery/baby_peter/img_1796">
+    another release on 1 March 2004</a> has kept me busy...
+
+    <p>
+    The <a href="downloads/Changelog">changelog</a> has all the details.
+    And as usual you can <a href="downloads">download busybox here</a>.
+
+    <p>Have Fun!
+    <p>
+
+
+  <p>
+  <li><b>23 February 2004 -- BusyBox 1.0.0-pre8 released</b><p>
+
+    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 <b>very</b> 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.
+
+    <p>
+
+    The <a href="downloads/Changelog">changelog</a> has all the details.
+    And as usual you can <a href="downloads">download busybox here</a>.
+
+    <p>Have Fun!
+    <p>
+
+
+  <li><b>4 February 2004 -- BusyBox 1.0.0-pre7 released</b><p>
+
+    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.
+
+    <p>
+
+    The <a href="downloads/Changelog">changelog</a> has all
+    the details.  And as usual you can
+    <a href="downloads">download busybox here</a>.
+
+    <p>Have Fun!
+    <p>
+
+
+  <p>
+  <li><b>30 January 2004 -- BusyBox 1.0.0-pre6 released</b><p>
+
+    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.
+
+    <p>
+
+    People who rely on the <a href="downloads/snapshots/">daily BusyBox snapshots</a>
+    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-&lt;date&gt;.tar.bz2".  Please
+    adjust any build scripts using the old naming scheme accordingly.
+
+    <p>
+
+    The <a href="downloads/Changelog">changelog</a> has all
+    the details.  And as usual you can
+    <a href="downloads">download busybox here</a>.
+
+    <p>Have Fun!
+    <p>
+
+
+  <p>
+  <li><b>23 December 2003 -- BusyBox 1.0.0-pre5 released</b><p>
+
+    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.
+
+    <p>
+
+    If you see any problems, of have suggestions to make, as
+    always, please feel free to send an email to the busybox
+    mailing list.
+
+    <p>
+
+    The <a href="downloads/Changelog">changelog</a> has all
+    the details.  And as usual you can
+    <a href="downloads">download busybox here</a>.
+
+    <p>Have Fun!
+    <p>
+
+
+
+  <li><b>10 December 2003 -- BusyBox 1.0.0-pre4 released</b><p>
+
+    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!
+
+    <p>
+
+    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.
+
+    <p>
+
+    The <a href="downloads/Changelog">changelog</a> has all
+    the details.  And as usual you can
+    <a href="downloads">download busybox here</a>.
+
+    <p>Have Fun!
+
+
+
+  <p>
+  <li><b>12 Sept 2003 -- BusyBox 1.0.0-pre3 released</b><p>
+
+    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!
+
+    <p>
+
+    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!
+
+    <p>
+
+    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.
+
+    <p>
+
+    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
+    <a href="http://www.uclibc.org/">uClibc</a>
+    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.
+
+    <p>
+
+    The <a href="downloads/Changelog">changelog</a> has all
+    the details.  And as usual you can
+    <a href="downloads">download busybox here</a>.
+
+    <p>Have Fun!
+
+
+    <p>
+    <li><b>30 July 2003 -- BusyBox 1.0.0-pre2 released</b><p>
+
+    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.
+
+    <p>
+
+    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.
+    <p>
+
+    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.
+    <p>
+
+    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...
+    <p>
+
+    The <a href="downloads/Changelog">changelog</a> has all
+    the details.  As usual you can <a href="downloads">download busybox here</a>.
+
+    <p>Have Fun!
+    <p>
+
+    <p>
+  <li><b>15 July 2003 -- BusyBox 1.0.0-pre1 released</b><p>
+
+    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...
+    <p>
+
+    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.
+    <p>
+
+    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 <b>NOT</b> 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.
+    <p>
+
+    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.
+    <p>
+
+    As usual you can <a href="downloads">download busybox here</a>.
+    You don't really need to bother with the
+    <a href="downloads/Changelog">changelog</a>, 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.
+
+    <p>Have Fun!
+    <p>
+
+
+
+  <p>
+  <li><b>26 October 2002 -- BusyBox 0.60.5 released</b><p>
+
+    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...
+
+    <p>
+    The <a href="downloads/Changelog.full">changelog</a> has all
+    the details.  As usual you can <a href="downloads">download busybox here</a>.
+    <p>Have Fun!
+    <p>
+
+  <p>
+  <li><b>18 September 2002 -- BusyBox 0.60.4 released</b><p>
+
+    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.
+
+    <p>
+    The <a href="downloads/Changelog.full">changelog</a> has all
+    the details.  As usual you can <a href="downloads">download busybox here</a>.
+    <p>Have Fun!
+    <p>
+
+
+  <p>
+  <li><b>27 April 2002 -- BusyBox 0.60.3 released</b><p>
+
+    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.
+
+    <p>
+    The <a href="downloads/Changelog">changelog</a> has all
+    the details.  As usual you can <a href="downloads">download busybox here</a>.
+    <p>Have Fun!
+    <p>
+
+
+  <p>
+  <li><b>6 March 2002 -- busybox.net now has mirrors!</b><p>
+
+    Busybox.net is now much more available, thanks to
+    the fine folks at <a href="http://i-netinnovations.com/">http://i-netinnovations.com/</a>
+    who are providing hosting for busybox.net and
+    uclibc.org.  In addition, we now have two mirrors:
+    <a href="http://busybox.linuxmagic.com/">http://busybox.linuxmagic.com/</a>
+    in Canada and
+    <a href="http://busybox.csservers.de/">http://busybox.csservers.de/</a>
+    in Germany.  I hope this makes things much more
+    accessible for everyone!
+
+
+<li>
+<b>3 January 2002 -- Welcome to busybox.net!</b>
+
+<p>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).</p>
+
+<p>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, <a href=
+"http://www.codepoet.org/~markw">Mark Whitley</a>
+(author of busybox sed, cut, and grep) has donated
+his <a href=
+"http://www.netwinder.org/">NetWinder</a> 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.</p>
+
+<p><!--
+    <center>
+    Click here to help support busybox.net!
+    <form action="https://www.paypal.com/cgi-bin/webscr" method="post">
+    <input type="hidden" name="cmd" value="_xclick">
+    <input type="hidden" name="business" value="andersen@codepoet.org">
+    <input type="hidden" name="item_name" value="Support Busybox">
+    <input type="hidden" name="image_url" value="https://codepoet-consulting.com/images/busybox2.jpg">
+    <input type="hidden" name="no_shipping" value="1">
+    <input type="image" src="images/donate.png" border="0" name="submit" alt="Make donation using PayPal">
+    </form>
+    </center>
+    -->
+ 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 <b>HUGE thank-you</b> goes out to
+everyone that has contributed!<br>
+ -Erik</p>
+</li>
+
+<li>
+<b>20 November 2001 -- BusyBox 0.60.2 released</b>
+
+<p>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.</p>
+
+<p>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 <a href=
+"downloads/Changelog">changelog</a> for <small>most
+of</small> the details. The last release was
+<em>very</em> solid for people, and this one should
+be even better.</p>
+
+<p>As usual BusyBox 0.60.2 can be downloaded from
+<a href=
+"downloads">http://www.busybox.net/downloads</a>.</p>
+
+<p>Have Fun.<br>
+ -Erik</p>
+</li>
+
+<li> <b>18 November 2001 -- Help us buy busybox.net!</b>
+
+<!-- Begin PayPal Logo -->
+<center>
+Click here to help buy busybox.net!
+<form action="https://www.paypal.com/cgi-bin/webscr" method="post">
+<input type="hidden" name="cmd" value="_xclick">
+<input type="hidden" name="business" value="andersen@codepoet.org">
+<input type="hidden" name="item_name" value="Support Busybox">
+<input type="hidden" name="image_url" value="https://busybox.net/images/busybox2.jpg">
+<input type="hidden" name="no_shipping" value="1">
+<input type="image" src="images/donate.png" name="submit" alt="Make donation using PayPal">
+</form>
+</center>
+<!-- End PayPal Logo -->
+
+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 <em>know</em> 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.
+<p>
+
+I was going to pay it all myself, but my wife didn't like that
+idea at all (big surprise).   It turns out &lt;insert argument
+where she wins and I don't&gt; 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...
+<p>
+
+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!
+<p>
+
+
+<li> <b>23 August 2001 -- BusyBox 0.60.1 released</b>
+<br>
+
+     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 <em>too</em>
+     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.
+     <p>
+     The
+     <a href="downloads/Changelog">changelog</a> has all
+     the details.  As usual BusyBox 0.60.1 can be downloaded from
+     <a href="downloads">http://busybox.net/downloads</a>.
+     <p>Have Fun!
+     <p>
+
+
+<li> <b>2 August 2001 -- BusyBox 0.60.0 released</b>
+<br>
+     I am very pleased to announce the immediate availability of
+     BusyBox 0.60.0.  I have personally tested this release with libc5, glibc,
+     and <a href="http://uclibc.org/">uClibc</a> 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.
+
+     <p>
+
+     Those wanting an easy way to test the 0.60.0 release with uClibc can
+     use <a href="http://user-mode-linux.sourceforge.net/">User-Mode Linux</a>
+     to give it a try by downloading and compiling
+     <a href="ftp://busybox.net/buildroot.tar.gz">buildroot.tar.gz</a>.
+     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.
+     <p>
+     Another cool thing is the nifty <a href="downloads/tutorial/index.html">
+     BusyBox Tutorial</a> contributed by K Computing.  This requires
+     a ShockWave plugin (or standalone viewer), so you may want to grab the
+     the GPLed shockwave viewer from <a href="http://www.swift-tools.com/Flash/flash-0.4.10.tgz">here</a>
+     to view the tutorial.
+     <p>
+
+     Finally, In case you didn't notice anything odd about the
+     version number of this release, let me point out that this release
+     is <em>not</em> 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.
+
+     <p>
+     The
+     <a href="downloads/Changelog">changelog</a> has all
+     the details.  As usual BusyBox 0.60.0 can be downloaded from
+     <a href="downloads">http://busybox.net/downloads</a>.
+     <p>Have Fun!
+     <p>
+
+
+<li> <b>7 July 2001 -- BusyBox 0.52 released</b>
+<br>
+
+     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 <em>many</em> 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).
+
+     <p>
+     The
+     <a href="downloads/Changelog">changelog</a> 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
+     <a href="downloads">http://busybox.net/downloads</a>.
+     <p>Have Fun!
+     <p>
+
+
+<li> <b>10 April 2001 - Graph of Busybox Growth </b>
+<br>
+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 <a href="busybox-growth.ps"> right here</a>.
+
+<p> (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.)
+<p>
+
+
+<li> <b>10 April 2001 -- BusyBox 0.51 released</b>
+<br>
+
+     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.
+     <p>
+
+     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!).
+     <p>
+     You can read the
+     <a href="downloads/Changelog">changelog</a> for
+     complete details.  BusyBox 0.51 can be downloaded from
+     <a href="downloads">http://busybox.net/downloads</a>.
+     <p>Have Fun!
+     <p>
+
+<li> <b>Busybox Boot-Floppy Image</b>
+
+<p>Because you asked for it, we have made available a <a href=
+"downloads/busybox.floppy.img"> Busybox boot floppy
+image</a>. Here's how you use it:
+
+<ol>
+
+    <li> <a href="downloads/busybox.floppy.img">
+    Download the image</a>
+
+    <li> dd it onto a floppy like so: <tt> dd if=busybox.floppy.img
+    of=/dev/fd0 ; sync </tt>
+
+    <li> Pop it in a machine and boot up.
+
+</ol>
+
+<p> If you want to look at the contents of the initrd image, do this:
+
+<pre>
+    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
+</pre>
+
+
+<li> <b>15 March 2001 -- BusyBox 0.50 released</b>
+<br>
+
+     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
+     <a href="downloads/Changelog">changelog</a>.
+     <p>
+     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
+     <a href="http://doolittle.faludi.com/~larry/parser.html">Larry Doolittle's website</a>.
+     <p>
+
+
+<li> <b>27 January 2001 -- BusyBox 0.49 released</b>
+<br>
+
+     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 <a href="downloads/Changelog">changelog</a>.
+     <p>
+     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.
+     <p>
+     <em>Special Note</em><br>
+
+     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 <a href="http://doolittle.faludi.com/~larry/parser.html">Larry's website</a>
+     and help out if you can.  This shell will be included in the next
+     release of BusyBox.
+     <p>
+
+<li> <b>13 December 2000 -- BusyBox 0.48 released</b>
+<br>
+
+     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.
+     <p>
+     The curious can get a list of some of the more interesting changes by reading
+     the <a href="downloads/Changelog">changelog</a>.
+     <p>
+     Many thanks go out to the many many people that have contributed to
+     this release, especially Matt Kraai, Larry Doolittle, and Kent Robotti.
+     <p>
+<p> <li> <b>26 September 2000 -- BusyBox 0.47 released</b>
+<br>
+
+     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 <a href="downloads/Changelog">changelog</a>
+     for complete details.
+
+
+<p> <li> <b>11 July 2000 -- BusyBox 0.46 released</b>
+<br>
+
+     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 <a href="downloads/Changelog">changelog</a>
+     for complete details.
+
+
+<p> <li> <b>21 June 2000 -- BusyBox 0.45 released</b>
+<br>
+
+     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 <a href="downloads/Changelog">changelog</a> for
+     details).
+     <p>
+     Also, some exciting infrastructure news!  Busybox now has its own
+     <a href="lists/busybox/">mailing list</a>,
+     publically browsable
+     <a href="http://sources.busybox.net/index.py/trunk/busybox/">CVS tree</a>,
+     anonymous
+     <a href="cvs_anon.html">CVS access</a>, and
+     for those that are actively contributing there is even
+     <a href="cvs_write.html">CVS write access</a>.
+     I think this will be a huge help to the ongoing development of BusyBox.
+     <p>
+     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.
+     <p>
+     Many thanks go out to the many people that have contributed to this release
+     of BusyBox (esp. Pavel Roskin)!
+
+
+<p> <li> <b>19 April 2000 -- syslogd bugfix</b>
+<br>
+Turns out that there was still a bug in busybox syslogd.
+For example, with the following test app:
+<pre>
+#include &lt;syslog.h&gt;
+
+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);
+}
+</pre>
+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).
+<p>
+Karl M. Hegbloom has created a fix for the problem.
+Thanks Karl!
+
+
+<p> <li> <b>18 April 2000 -- BusyBox 0.43 released (finally!)</b>
+<br>
+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
+<a href="downloads/Changelog">changelog</a>.
+Oh, and as a special bonus, I wrote some fairly comprehensive
+<em>documentation</em>, complete with examples and full usage information.
+
+<p>
+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.
+<p>
+
+You can grab BusyBox 0.43 tarballs <a href="downloads">here</a>.
+
+<p> <li> <b>9 April 2000 -- BusyBox 0.43 pre release</b>
+<br>
+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.
+<p>
+The pre-release can be found <a href="downloads">here</a>.
+Please let me know ASAP if you find <em>any</em> bugs.
+
+<p> <li> <b>28 March 2000 -- Andersen Baby Boy release</b>
+<br>
+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).
+<p>
+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.
+<p>
+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.
+
+
+<p> <li> <b>11 February 2000 -- BusyBox 0.42 released</b>
+<br>
+
+     This is the most solid BusyBox release so far.  Many, many
+       bugs have been fixed.   See the
+       <a href="downloads/Changelog">changelog</a> 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.
+
+<p> <li> <b>19 January 2000 -- BusyBox 0.41 released</b>
+<br>
+
+     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 <a href="downloads/Changelog">here</a>.
+
+<p> <li> <b>7 January 2000 -- BusyBox 0.40 released</b>
+<br>
+
+     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 <a href="downloads/Changelog">here</a>.
+
+<p> <li> <b>11 December 1999 -- BusyBox Website</b>
+<br>
+     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.
+
+<p> <li> <b>10 December 1999 -- BusyBox 0.39 released</b>
+<br>
+     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 <a href="downloads/Changelog">here</a>.
+<p> <li> <b>5 December 1999 -- BusyBox 0.38 released</b>
+<br>
+     This release includes fixes to tar, cat, ls, dd, rm, umount, find, df,
+       and make install, and includes new apps syslogd/klogd and logger.
+
+
+</ul>
+
+
+<!--#include file="footer.html" -->
+
diff --git a/docs/busybox.net/products.html b/docs/busybox.net/products.html
new file mode 100644 (file)
index 0000000..7bb07f7
--- /dev/null
@@ -0,0 +1,165 @@
+<!--#include file="header.html" -->
+
+
+<h3>Products/Projects Using BusyBox</h3>
+
+Do you use BusyBox? I'd love to know about it and
+I'd be happy to link to you.
+
+<p>
+I know of the following projects that use BusyBox --
+listed in the order I happen to add them to the web page:
+
+<ul>
+
+<li><a href="http://buildroot.uclibc.org/">buildroot</a><br>A configurable
+means for building your own busybox/uClibc based system systems, maintained
+by the uClibc developers.
+
+<li><a href="http://openwrt.org">OpenWrt</a> a Linux distribution for embedded
+devices, based on buildroot.
+
+<li><a href="http://www.pengutronix.de/software/ptxdist_en.html">PTXdist</a>
+ <br>another configurable means for building your own busybox based systems.
+
+<li><a href=
+"http://cvs.debian.org/boot-floppies/">
+Debian installer (boot floppies) project</a>
+
+<li><a href="http://redhat.com/">Red Hat installer</a>
+
+<li><a href=
+"http://distro.ibiblio.org/pub/Linux/distributions/slackware/slackware-current/source/rootdisks/">
+Slackware Installer</a>
+
+<li><a href="http://www.gentoo.org/">Gentoo Linux install/boot CDs</a>
+<li><a href="http://www.mandriva.com/">The Mandriva installer</a>
+
+<li><a href="http://Leaf.SourceForge.net">Linux Embedded Appliance Firewall</a>
+ <br>The sucessor of the Linux Router Project, supporting all sorts
+ of embedded Linux gateways, routers, wireless routers, and firewalls.
+
+<li><a href=
+"http://www.toms.net/rb/">tomsrtbt</a>
+
+<li><a href="http://www.stormix.com/">Stormix Installer</a>
+
+<li><a href="http://www.emacinc.com/linux2_sbc.htm">EMAC Linux 2.0 SBC</a>
+
+<li><a href="http://www.trinux.org/">Trinux</a>
+
+<li><a href="http://oddas.sourceforge.net/">ODDAS project</a>
+
+<li><a href="http://byld.sourceforge.net/">Build Your Linux Disk</a>
+
+<li><a href=
+"http://ibiblio.org/pub/Linux/system/recovery">Zdisk</a>
+
+<li><a href="http://www.adtran.com">AdTran -
+VPN/firewall VPN Linux Distribution</a>
+
+<li><a href="http://mkcdrec.ota.be/">mkCDrec - make CD-ROM recovery</a>
+
+<li><a href="http://recycle.lbl.gov/~ldoolitt/bse/">Linux on nanoEngine</a>
+
+<li><a href="http://www.zelow.no/floppyfw/">Floppyfw</a>
+
+<li><a href="http://www.ltsp.org/">Linux Terminal Server Project</a>
+
+<li><a href="http://www.devil-linux.org/">Devil-Linux</a>
+
+<li><a href="http://dutnux.sourceforge.net/">DutNux</a>
+
+<li><a href="http://www.microwerks.net/~hugo/mindi/">Mindi</a>
+
+<li><a href="http://www.minimalinux.org/ttylinux/">ttylinux</a>
+
+<li><a href="http://www.coyotelinux.com/">Coyote Linux</a>
+
+<li><a href="http://www.partimage.org/">Partition Image</a>
+
+<li><a href="http://www.fli4l.de/">fli4l the on(e)-disk-router</a>
+
+<li><a href="http://tinfoilhat.cultists.net/">Tinfoil Hat Linux</a>
+
+<li><a href="http://sourceforge.net/projects/gp32linux/">gp32linux</a>
+<li><a href="http://familiar.handhelds.org/">Familiar Linux</a><br>A linux distribution for handheld computers
+<li><a href="http://rescuecd.sourceforge.net/">Timo's Rescue CD Set</a>
+<li><a href="http://sf.net/projects/netstation/">Netstation</a>
+<li><a href="http://www.fiwix.org/">GNU/Fiwix Operating System</a>
+<li><a href="http://www.softcraft.com/">Generations Linux</a>
+<li><a href="http://systemimager.org/relatedprojects/">SystemImager / System Installation Suite</a>
+<li><a href="http://www.bablokb.de/gendist/">GENDIST distribution generator</a>
+<li><a href="http://diet-pc.sourceforge.net/">DIET-PC embedded Linux thin client distribution</a>
+<li><a href="http://byzgl.sourceforge.net/">BYZantine Gnu/Linux</a>
+<li><a href="http://dban.sourceforge.net/">Darik's Boot and Nuke</a>
+<li><a href="http://www.timesys.com/">TimeSys real-time Linux</a>
+<li><a href="http://movix.sf.net/">MoviX</a><br>Boots from CD and automatically plays every video file on the CD
+<li><a href="http://katamaran.sourceforge.net">katamaran</a><br>Linux, X11, xfce windowmanager, based on BusyBox
+<li><a href="http://www.sourceforge.net/projects/simplygnustep">Prometheus SimplyGNUstep</a>
+<li><a href="http://www.renyi.hu/~ekho/lowlife/">lowlife</a><br>A documentation project on how to make your own uClibc-based systems and floppy.
+<li><a href="http://metadistros.hispalinux.es/">Metadistros</a><br>a project to allow you easily make Live-CD distributions.
+<li><a href="http://salvare.sourceforge.net/">Salvare</a><br>More Linux than tomsrtbt but less than Knoppix, aims to provide a useful workstation as well as a rescue disk.
+<li><a href="http://www.stresslinux.org/">stresslinux</a><br>minimal linux distribution running from a bootable cdrom or via PXE.
+<li><a href="http://thinstation.sourceforge.net/">thinstation</a><br>convert standard PCs into full-featured diskless thinclients.
+<li><a href="http://www.uhulinux.hu/">UHU-Linux Hungary</a>
+<li><a href="http://deep-water.berlios.de/">Deep-Water Linux</a>
+<li><a href="http://www.freesco.org/">Freesco router</a>
+<li><a href="http://Sentry.SourceForge.net/">Sentry Firewall CD</a>
+
+</ul>
+
+<p>
+And here are products that use BusyBox --
+
+<ul>
+
+<li><a href="http://www.dream-multimedia-tv.de/">Dreambox (Linux based PVR)</a>
+<li><a href="http://www.elpa.it/eng/rd129gb.html">RD129 embedded board from ELPA</a>
+<li>EMTEC MovieCube R700 uses Busybox 1.1.3.
+<li><a href="http://tuxscreen.net">Tuxscreen Linux Phone</a>
+<li><a href="http://www.kerbango.com/">The Kerbango Internet Radio</a>
+<li><a href="http://www.linuxmagic.com/vpn/">LinuxMagic VPN Firewall</a>
+<li><a href="http://www.isilver-inc.com/">I-Silver Linux appliance servers</a>
+<li><a href="http://zaurus.sourceforge.net/">Sharp Zaurus PDA</a>
+<li><a href="http://www.cyclades.com/">Cyclades-TS and other Cyclades products</a>
+<li><a href="http://www.linksys.com/products/product.asp?prid=508">Linksys WRT54G - Wireless-G Broadband Router</a>
+<li><a href="http://www.dell.com/us/en/biz/topics/sbtopic_005_truemobile.htm">Dell TrueMobile 1184</a>
+<li><a href="http://actiontec.com/products/modems/dual_pcmodem/dpm_overview.html">Actiontec Dual PC Modem</a>
+<li><a href="http://www.kiss-technology.com/">Kiss DP Series DVD players</a>
+<li><a href="http://www.netgear.com/products/prod_details.asp?prodID=170">NetGear WG602 wireless router</a>
+    <br>with sources <a href="http://www.netgear.com/support/support_details.asp?dnldID=453">here</a>
+<li><a href="http://www.trendware.com/products/TEW-411BRP.htm">TRENDnet TEW-411BRP 802.11g Wireless AP/Router/Switch</a>
+    <br>Source for busybox and udhcp <a href="http://www.trendware.com/asp/download/fileinfo.asp?file_id=277&amp;B1=Search">here</a> though no kernel source is provided.
+<li><a href="http://www.buffalo-technology.com/webcontent/products/wireless/wbr-g54.htm">Buffalo WBR-G54 wireless router</a>
+  <li><a href="http://www.asus.com/products/communication/wireless/wl-300g/overview.htm">ASUS WL-300g Wireless LAN Access Point</a>
+    <br>with source<a href="http://www.asus.com.tw/support/download/item.aspx?ModelName=WL-300G">here</a>
+  <li><a href="http://catalog.belkin.com/IWCatProductPage.process?Merchant_Id=&amp;Section_Id=201522&amp;pcount=&amp;Product_Id=136493">Belkin 54g Wireless DSL/Cable Gateway Router</a>
+    <br>with source<a href="http://web.belkin.com/support/gpl.asp">here</a>
+  <li><a href="http://www.acronis.com/products/partitionexpert/">Acronis PartitionExpert 2003</a>
+       <br>includes a heavily modified BusyBox v0.60.5 with built in
+       cardmgr, device detection, gpm, lspci, etc.  Also includes udhcp,
+       uClibc 0.9.26, a heavily patched up linux kernel, etc.  Source
+       can only be obtained <a href="http://www.acronis.com/files/gpl/linux.tar.bz2">here</a>
+
+<li><a href="http://www.usr.com/">U.S. Robotics Sureconnect 4-port ADSL router</a><br>
+    with source <a href="http://www.usr.com/support/s-gpl-code.asp">here</a>
+<li><a href="http://www.actiontec.com/products/broadband/54mbps_wireless_gateway_1p/index.html">
+    ActionTec GT701-WG Wireless Gateway/DSL Modem</a>
+    with source <a href="http://opensource.actiontec.com/">here</a>
+<li><a href="http://smartlinux.sourceforge.net/">S.M.A.R.T. Linux</a>
+<li><a href="http://www.dlink.com/">DLink - Model GSL-G604T, DSL-300T, and possibly other models</a>
+    with source <a href="ftp://ftp.dlink.co.uk/dsl_routers_modems/">here,</a>
+    with source <a href="ftp://ftp.dlink.de/dsl-products/">and here,</a>
+    and quite possibly other places as well.  You may need to dig down a bit
+    to find the source, but it does seem to be there.
+<li><a href="http://www.siemens-mobile.de/cds/frontdoor/0,2241,de_de_0_42931_rArNrNrNrN,00.html">Siemens SE515 DSL router</a>
+    with source <a href="http://now-portal.c-lab.de/projects/gigaset/">here, I think...</a>
+    with some details <a href="http://heinz.hippenstiel.org/familie/hp/hobby/gigaset_se515dsl.html">here.</a>
+<li><a href="http://freeterm.spb.ru/frwt/">Free Remote Windows Terminal</a>
+
+<li><a href="http://www.zyxel.com/">ZyXEL Routers</a>
+
+</ul>
+
+<!--#include file="footer.html" -->
diff --git a/docs/busybox.net/screenshot.html b/docs/busybox.net/screenshot.html
new file mode 100644 (file)
index 0000000..c5ef18b
--- /dev/null
@@ -0,0 +1,75 @@
+<!--#include file="header.html" -->
+
+
+<!-- Begin Screenshot -->
+
+<h3> Busybox Screenshot! </h3>
+
+
+Everybody loves to look at screenshots, so here is a live action screenshot of BusyBox.
+
+<pre style="background-color: black; color: lightgreen; padding: 5px;
+font-family: monospace; font-size: smaller;" width="100">
+
+$ busybox
+BusyBox v1.10.1 (2008-04-24 11:30:07 CEST) multi-call binary
+Copyright (C) 1998-2007 Erik Andersen, Rob Landley, Denys Vlasenko
+and others. Licensed under GPLv2.
+See source distribution for full notice.
+
+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, arp, arping, ash,
+       awk, basename, bbconfig, brctl, bunzip2, bzcat, bzip2,
+       cal, cat, catv, chat, chattr, chcon, chgrp, chmod, chown,
+       chpasswd, chpst, chroot, chrt, chvt, cksum, clear, cmp,
+       comm, cp, cpio, crond, crontab, cryptpw, cttyhack, cut,
+       date, dc, dd, deallocvt, delgroup, deluser, devfsd, df,
+       dhcprelay, diff, dirname, dmesg, dnsd, dos2unix, dpkg,
+       dpkg-deb, du, dumpkmap, dumpleases, echo, ed, egrep, eject,
+       env, envdir, envuidgid, ether-wake, expand, expr, fakeidentd,
+       false, fbset, fdflush, fdformat, fdisk, fetchmail, fgrep,
+       find, findfs, fold, free, freeramdisk, fsck, fsck.minix,
+       ftpget, ftpput, fuser, getenforce, getopt, getsebool,
+       getty, grep, gunzip, gzip, halt, hd, hdparm, head, hexdump,
+       hostid, hostname, httpd, hush, hwclock, id, ifconfig,
+       ifdown, ifenslave, ifup, inetd, init, insmod, install,
+       ip, ipaddr, ipcalc, ipcrm, ipcs, iplink, iproute, iprule,
+       iptunnel, kbd_mode, kill, killall, killall5, klogd, lash,
+       last, length, less, linux32, linux64, linuxrc, ln, load_policy,
+       loadfont, loadkmap, logger, login, logname, logread, losetup,
+       lpd, lpq, lpr, ls, lsattr, lsmod, lzmacat, makedevs, matchpathcon,
+       md5sum, mdev, mesg, microcom, mkdir, mkfifo, mkfs.minix,
+       mknod, mkswap, mktemp, modprobe, more, mount, mountpoint,
+       msh, mt, mv, nameif, nc, netstat, nice, nmeter, nohup,
+       nslookup, od, openvt, passwd, patch, pgrep, pidof, ping,
+       ping6, pipe_progress, pivot_root, pkill, poweroff, printenv,
+       printf, ps, pscan, pwd, raidautorun, rdate, readahead,
+       readlink, readprofile, realpath, reboot, renice, reset,
+       resize, restorecon, rm, rmdir, rmmod, route, rpm, rpm2cpio,
+       rtcwake, run-parts, runcon, runlevel, runsv, runsvdir,
+       rx, script, sed, selinuxenabled, sendmail, seq, sestatus,
+       setarch, setconsole, setenforce, setfiles, setkeycodes,
+       setlogcons, setsebool, setsid, setuidgid, sha1sum, slattach,
+       sleep, softlimit, sort, split, start-stop-daemon, stat,
+       strings, stty, su, sulogin, sum, sv, svlogd, swapoff,
+       swapon, switch_root, sync, sysctl, syslogd, tac, tail,
+       tar, taskset, tcpsvd, tee, telnet, telnetd, test, tftp,
+       tftpd, time, top, touch, tr, traceroute, true, tty, ttysize,
+       udhcpc, udhcpd, udpsvd, umount, uname, uncompress, unexpand,
+       uniq, unix2dos, unlzma, unzip, uptime, usleep, uudecode,
+       uuencode, vconfig, vi, vlock, watch, watchdog, wc, wget,
+       which, who, whoami, xargs, yes, zcat, zcip
+
+$ <span style="text-decoration:blink;">_</span>
+
+</pre>
+
+<!--#include file="footer.html" -->
diff --git a/docs/busybox.net/shame.html b/docs/busybox.net/shame.html
new file mode 100644 (file)
index 0000000..d9da44b
--- /dev/null
@@ -0,0 +1,82 @@
+<!--#include file="header.html" -->
+
+
+<h3>Hall of Shame!!!</h3>
+
+<p>This page is no longer updated, these days we forward this sort of
+thing to the <a href="http://www.softwarefreedom.org">Software Freedom Law
+Center</a> instead.</p>
+
+<p>The following products and/or projects appear to use BusyBox, but do not
+appear to release source code as required by the <a
+href="/license.html">BusyBox license</a>.  This is a violation of the law!
+The distributors of these products are invited to contact <a href=
+"mailto:andersen@codepoet.org">Erik Andersen</a> 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.
+
+<p>
+
+Here are the details of <a href="/license.html">exactly how to comply
+with the BusyBox license</a>, 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 <a
+href="mailto:andersen@codepoet.org">Erik</a> 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.
+
+<p>
+
+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.
+
+<p>
+
+<ul>
+
+  <li><a href="http://www.trittontechnologies.com/products.html">Tritton Technologies NAS120</a>
+       <br>see <a href="http://www.ussg.iu.edu/hypermail/linux/kernel/0404.0/1611.html">here for details</a>
+  <li><a href="http://www.macsense.com/product/homepod/">Macsense HomePod</a>
+       <br>with details
+       <a href="http://developer.gloolabs.com/modules.php?op=modload&amp;name=Forums&amp;file=viewtopic&amp;topic=123&amp;forum=7">here</a>
+  <li><a href="http://www.cpx.com/products.asp?c=Wireless+Products">Compex Wireless Products</a>
+    <br>appears to be running v0.60.5 with Linux version 2.4.20-uc0 on ColdFire,
+    but no source code is mentioned or offered.
+  <li><a href="http://www.inventel.com/en/product/datasheet/10/">Inventel DW 200 wireless/ADSL router</a>
+  <li><a href="http://www.sweex.com/product.asp">Sweex DSL router</a>
+    <br>appears to be running BusyBox v1.00-pre2 and udhcpd, but no source
+       code is mentioned or offered.
+  <li><a href="http://www.trendware.com/products/TEW-410APB.htm">TRENDnet TEW-410APB</a>
+  </li><li><a href="http://www.hauppauge.com/Pages/products/data_mediamvp.html">Hauppauge Media MVP</a>
+  <br>Hauppauge contacted me on 16 Dec 2003, and claims to be working on resolving this problem.
+  </li><li><a href="http://www.hitex.com/download/adescom/data/">TriCore</a>
+  </li><li><a href="http://www.allnet.de/">ALLNET 0186 wireless router</a>
+  </li><li><a href="http://www.dmmtv.com/">Dreambox DM7000S DVB Satellite Receiver</a>
+  <br> Dream Multimedia contacted me on 22 Dec 2003 and is working on resolving this problem.
+  <br> Source _may_ be here: http://cvs.tuxbox.org/cgi-bin/viewcvs.cgi/tuxbox/cdk/
+  </li><li><a href="http://testing.lkml.org/slashdot.php?mid=331690">Sigma Designs EM8500 based DVD players</a>
+  <br>Source for the Sigma Designs reference platform is found here<br>
+    <a href="http://www.uclinux.org/pub/uClinux/ports/arm/EM8500/uClinux-2.4-sigma.tar.gz">uClinux-2.4-sigma.tar.gz</a>, so while Sigma Designs itself appears to be in compliance, as far as I can tell,
+    no vendors of Sigma Designs EM8500 based devices actually comply with the GPL....
+  </li><li><a href="http://testing.lkml.org/slashdot.php?mid=433790">Liteon LVD2001 DVD player using the Sigma Designs EM8500</a>
+  </li><li><a href="http://www.rimax.net/">Rimax DVD players using the Sigma Designs EM8500</a>
+  </li><li><a href="http://www.vinc.us/">Bravo DVD players using the Sigma Designs EM8500</a>
+  </li><li><a href="http://www.hb-direct.com/">H&amp;B DX3110 Divx player based on Sigma Designs EM8500</a>
+  </li><li><a href="http://www.recospa.it/mdpro1/index.php">United *DVX4066 mpeg4 capable DVD players</a>
+  </li><li><a href="http://www.a-link.com/RR64AP.html">Avaks alink Roadrunner 64</a>
+  <br> Partial source available, based on source distributed under NDA from <a href="http://www.lsilogic.com/products/dsl_platform_solutions/hb_linuxr2_2.html"> LSILogic</a>. Why the NDA LSILogic, what are you hiding ?
+  <br>To verify the Avaks infrigment see my slashdot <a href="http://slashdot.org/~bug1/journal/">journal</a>.
+  <br>The ZipIt wireless IM device appears to be using Busybox-1.00-pre1 in the ramdisk, however no source has been made available.
+  </li><li>Undoubtedly there are others...  Please report them so we can shame them (or if necessary sue them) into compliance.
+
+</ul>
+
+
+<!--#include file="footer.html" -->
+
diff --git a/docs/busybox.net/sponsors.html b/docs/busybox.net/sponsors.html
new file mode 100644 (file)
index 0000000..e52adfc
--- /dev/null
@@ -0,0 +1,56 @@
+<!--#include file="header.html" -->
+
+<h3>Sponsors</h3>
+
+<p>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!</p>
+
+
+<ul>
+  <li><a href="http://osuosl.org/">OSU OSL</a><br>
+  OSU OSL kindly provides hosting for BusyBox and uClibc.
+  </li>
+
+  <li><a href="http://www.codepoet-consulting.com/">Codepoet Consulting</a><br>
+  Custom Linux, embedded Linux, BusyBox, and uClibc development.
+  </li>
+
+  <li><a href="http://www.laptopcomputers.org/">Laptop Computers</a> contributes
+  financially.
+  </li>
+
+  <li>AOE media, a <a href="http://www.aoemedia.com/typo3-development.html">
+  TYPO3 development agency</a> contributes financially.
+  </li>
+
+  <li><a href="http://www.analog.com/en/">Analog Devices, Inc.</a> provided
+  a <a href="http://docs.blackfin.uclinux.org/doku.php?id=bf537_quick_start">
+  Blackfin development board</a> free of charge.
+  <a href="http://www.analog.com/blackfin">Blackfin</a>
+  is a NOMMU processor, and its availability for testing is invaluable.
+  If you are an embedded device developer,
+  please note that Analog Devices has entire Linux distribution available
+  for download for this board. Visit
+  <a href="http://blackfin.uclinux.org/">http://blackfin.uclinux.org/</a>
+  for more information.
+  </li>
+
+  <li><a href="http://www.timesys.com/">TimeSys</a><br>
+  Embedded Linux development, cross-compilers, real-time, KGDB, tsrpm and cygwin.
+  </li>
+
+  <li><a href="http://www.penguru.net/">Penguru Consulting</a><br>
+  Custom development for embedded Linux systems and multimedia platforms.
+  </li>
+
+  <li><a href="http://opensource.se/">opensource.se</a><br>
+  Embedded open source consulting in Europe.
+  </li>
+
+</ul>
+
+<p>If you wish to be a sponsor, or if you have already contributed and would
+like your name added here, email <a href="mailto:vda.linux@gmail.com">Denys</a>.</p>
+
+<!--#include file="footer.html" -->
diff --git a/docs/busybox.net/subversion.html b/docs/busybox.net/subversion.html
new file mode 100644 (file)
index 0000000..2c4517a
--- /dev/null
@@ -0,0 +1,51 @@
+<!--#include file="header.html" -->
+
+<h3>Accessing Source</h3>
+
+
+
+<h3>Patches</h3>
+
+<p>You can <a href="downloads/">download</a> fixes for particular releases
+of busybox, e.g. downloads/fixes-<em>major</em>-<em>minor</em>-<em>patch</em>/
+
+<h3>Anonymous Subversion Access</h3>
+
+We allow anonymous (read-only) Subversion (svn) access to everyone.  To
+grab a copy of the latest version of BusyBox using anonymous svn access:
+
+<pre>
+svn co svn://busybox.net/trunk/busybox</pre>
+
+<p>
+The <em>stable branches</em> can be obtained with
+<pre>
+svn co svn://busybox.net/branches/busybox_1_NN_stable
+</pre>
+
+<p>
+
+If you are not already familiar with using Subversion, I recommend you visit <a
+href="http://subversion.tigris.org/">the Subversion website</a>.  You might
+also want to read online or buy a copy of <a
+href="http://svnbook.red-bean.com/">the Subversion Book</a>.  If you are
+already comfortable with using CVS, you may want to skip ahead to the <a
+href="http://svnbook.red-bean.com/en/1.1/apa.html">Subversion for CVS Users</a>
+part of the Subversion Book.
+
+<p>
+
+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:
+
+<pre>
+svn update</pre>
+
+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
+<a href="developer.html">Subversion commit access</a> can be made available.
+
+<!--#include file="footer.html" -->
+
diff --git a/docs/busybox.net/svnindex.css b/docs/busybox.net/svnindex.css
new file mode 100644 (file)
index 0000000..b1ca24a
--- /dev/null
@@ -0,0 +1,92 @@
+/* A sample style sheet for displaying the Subversion directory listing
+   that is generated by mod_dav_svn and "svnindex.xsl". */
+
+body{
+  margin: 0;
+  padding: 0;
+}
+
+a {
+  color: navy;
+}
+
+.header {
+  padding-top: 5px;
+  text-align: center;
+}
+
+.footer {
+  margin-top: 8em;
+  padding: 0.5em 1em 0.5em;
+  border: 1px solid;
+  border-width: 1px 0;
+  clear: both;
+  border-color: rgb(30%,30%,50%) navy rgb(75%,80%,85%) navy;
+  background: rgb(88%,90%,92%);
+  font-size: 80%;
+}
+
+.svn {
+  margin: 3em;
+}
+
+.rev {
+  margin-right: 3px;
+  padding-left: 3px;
+  text-align: left;
+  font-size: 120%;
+}
+
+.dir a {
+  text-decoration: none;
+  color: black;
+}
+
+.file a {
+  text-decoration: none;
+  color: black;
+}
+
+.path {
+  margin: 3px;
+  padding: 3px;
+  background: #FFCC66;
+  font-size: 120%;
+}
+
+.updir {
+  margin: 3px;
+  padding: 3px;
+  margin-left: 3em;
+  background: #FFEEAA;
+}
+
+.file {
+  margin: 3px;
+  padding: 3px;
+  margin-left: 3em;
+  background: rgb(95%,95%,95%);
+}
+
+.file:hover {
+  margin: 3px;
+  padding: 3px;
+  margin-left: 3em;
+  background: rgb(100%,100%,90%);
+/*  border: 1px black solid; */
+}
+
+.dir {
+  margin: 3px;
+  padding: 3px;
+  margin-left: 3em;
+  background: rgb(90%,90%,90%);
+}
+
+.dir:hover {
+  margin: 3px;
+  padding: 3px;
+  margin-left: 3em;
+  background: rgb(100%,100%,80%);
+/*  border: 1px black solid; */
+}
diff --git a/docs/busybox.net/svnindex.xsl b/docs/busybox.net/svnindex.xsl
new file mode 100644 (file)
index 0000000..2d3297c
--- /dev/null
@@ -0,0 +1,108 @@
+<?xml version="1.0"?>
+
+<!-- A sample XML transformation style sheet for displaying the Subversion
+  directory listing that is generated by mod_dav_svn when the "SVNIndexXSLT"
+  directive is used. -->
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
+
+  <xsl:output method="html"/>
+
+  <xsl:template match="*"/>
+
+  <xsl:template match="svn">
+    <html>
+      <head>
+        <title>
+          <xsl:if test="string-length(index/@name) != 0">
+            <xsl:value-of select="index/@name"/>
+            <xsl:text>: </xsl:text>
+          </xsl:if>
+          <xsl:value-of select="index/@path"/>
+        </title>
+        <link rel="stylesheet" type="text/css" href="/svnindex.css"/>
+      </head>
+      <body>
+        <div class="header" style="font-family: lucida, helvetica; font-size: 248%">
+            <xsl:text>BUSYBOX</xsl:text>
+        </div>
+        <div class="header">
+          <a href="http://www.busybox.net"><img src="/images/busybox1.png" border="0" /></a>
+        </div>
+        <div class="svn">
+          <xsl:apply-templates/>
+        </div>
+        <div class="footer">
+          <xsl:text>Powered by </xsl:text>
+          <xsl:element name="a">
+            <xsl:attribute name="href">
+              <xsl:value-of select="@href"/>
+            </xsl:attribute>
+            <xsl:text>Subversion</xsl:text>
+          </xsl:element>
+          <xsl:text> </xsl:text>
+          <xsl:value-of select="@version"/>
+        </div>
+      </body>
+    </html>
+  </xsl:template>
+
+  <xsl:template match="index">
+    <div class="rev">
+      <xsl:value-of select="@name"/>
+      <xsl:if test="@base">
+        <xsl:if test="@name">
+          <xsl:text>:&#xA0; </xsl:text>
+        </xsl:if>
+        <xsl:value-of select="@base" />
+      </xsl:if>
+      <xsl:if test="@rev">
+        <xsl:if test="@base | @name">
+          <xsl:text> &#x2014; </xsl:text>
+        </xsl:if>
+        <xsl:text>Revision </xsl:text>
+        <xsl:value-of select="@rev"/>
+      </xsl:if>
+    </div>
+    <div class="path">
+      <xsl:value-of select="@path"/>
+    </div>
+    <xsl:apply-templates select="updir"/>
+    <xsl:apply-templates select="dir"/>
+    <xsl:apply-templates select="file"/>
+  </xsl:template>
+
+  <xsl:template match="updir">
+    <div class="updir">
+      <xsl:text>[</xsl:text>
+      <xsl:element name="a">
+        <xsl:attribute name="href">..</xsl:attribute>
+        <xsl:text>Parent Directory</xsl:text>
+      </xsl:element>
+      <xsl:text>]</xsl:text>
+    </div>
+  </xsl:template>
+
+  <xsl:template match="dir">
+    <div class="dir">
+      <xsl:element name="a">
+        <xsl:attribute name="href">
+          <xsl:value-of select="@href"/>
+        </xsl:attribute>
+        <xsl:value-of select="@name"/>
+        <xsl:text>/</xsl:text>
+      </xsl:element>
+    </div>
+  </xsl:template>
+
+  <xsl:template match="file">
+    <div class="file">
+      <xsl:element name="a">
+        <xsl:attribute name="href">
+          <xsl:value-of select="@href"/>
+        </xsl:attribute>
+        <xsl:value-of select="@name"/>
+      </xsl:element>
+    </div>
+  </xsl:template>
+
+</xsl:stylesheet>
diff --git a/docs/busybox.net/tinyutils.html b/docs/busybox.net/tinyutils.html
new file mode 100644 (file)
index 0000000..1831346
--- /dev/null
@@ -0,0 +1,86 @@
+<!--#include file="header.html" -->
+
+
+<h3>External Tiny Utilities</h3>
+
+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.
+
+<br><br>
+
+<table>
+<tr>
+ <th>Feature</th>
+ <th>Utilities</th>
+</tr>
+
+<tr>
+ <td>SSH</td>
+ <td><a href="http://matt.ucc.asn.au/dropbear/">Dropbear</a> 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.</td>
+</tr>
+
+<tr>
+ <td>SMTP</td>
+ <td><a href="ftp://ftp.debian.org/debian/pool/main/s/ssmtp/">ssmtp</a> is an extremely simple Mail Transfer Agent.</td>
+</tr>
+
+<tr>
+  <td>ntp</td>
+  <td><a href="http://doolittle.icarus.com/ntpclient/">ntpclient</a> 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.</td>
+</table>
+
+<p>In a gui environment, you'll probably want a web browser.
+<a href="http://www.konqueror.org/embedded/">Konqueror Embedded</a> requires QT
+(or QT Embedded), but not KDE.  The <a href="http://www.dillo.org/">Dillo</a>
+requires GTK+, but not Gnome.  Or you can try the <a href="http://links.twibright.com/">graphical
+version of links</a>.</p>
+
+<h3>SCRIPTING LANGUAGES</h3>
+<p>Although busybox has built-in support for shell scripts, plenty of other
+small scripting languages are available on the net.  A few examples:</p>
+<table>
+<tr>
+<th>language</th>
+<th>description</th>
+</tr>
+<tr>
+<td> <a href="http://www.foo.be/docs/tpj/issues/vol5_3/tpj0503-0003.html">microperl</a> </td>
+<td> 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.
+</tr>
+<tr>
+
+<td><a href="http://www.lua.org/pil/">Lua</a></td>
+<td>If you just want a small embedded scripting language to write <em>new</em>
+code in, this Brazilian import is lightweight, fairly popular, and has
+a complete book about it online.</td>
+</tr>
+
+<tr>
+<td><a href="http://www.star.le.ac.uk/%7Etjg/rc/">rc</a></td>
+<td>The PLAN9 shell.  Not compatible with conventional bourne shell syntax,
+but fairly lightweight and small.</td>
+</tr>
+
+</tr>
+<tr>
+<td><a href="http://www.forth.org/">forth</a></td>
+<td>A well known language for fast and small programs, decades old but still
+in use for everything from OpenBIOS to computer controlled engine timing.</td>
+</tr>
+</table>
+
+<p>For more information, you probably want to look at
+<a href="http://buildroot.uclibc.org/">buildroot</a> and
+<a href="http://gentoo-wiki.com/TinyGentoo">TinyGentoo</a>, which
+build and use tiny utilities for all sorts of things.</p>
+
+<!--#include file="footer.html" -->
+
diff --git a/docs/busybox_footer.pod b/docs/busybox_footer.pod
new file mode 100644 (file)
index 0000000..74575bd
--- /dev/null
@@ -0,0 +1,256 @@
+=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 <vda.linux@googlemail.com>
+
+=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 <br>
+
+Emanuele Aina <emanuele.aina@tiscali.it>
+       run-parts
+
+=for html <br>
+
+Erik Andersen <andersen@codepoet.org>
+
+    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 <br>
+
+Laurence Anderson <l.d.anderson@warwick.ac.uk>
+
+    rpm2cpio, unzip, get_header_cpio, read_gz interface, rpm
+
+=for html <br>
+
+Jeff Angielski <jeff@theptrgroup.com>
+
+    ftpput, ftpget
+
+=for html <br>
+
+Edward Betts <edward@debian.org>
+
+    expr, hostid, logname, whoami
+
+=for html <br>
+
+John Beppu <beppu@codepoet.org>
+
+    du, nslookup, sort
+
+=for html <br>
+
+Brian Candler <B.Candler@pobox.com>
+
+    tiny-ls(ls)
+
+=for html <br>
+
+Randolph Chung <tausq@debian.org>
+
+    fbset, ping, hostname
+
+=for html <br>
+
+Dave Cinege <dcinege@psychosis.com>
+
+    more(v2), makedevs, dutmp, modularization, auto links file,
+    various fixes, Linux Router Project maintenance
+
+=for html <br>
+
+Jordan Crouse <jordan@cosmicpenguin.net>
+
+       ipcalc
+
+=for html <br>
+
+Magnus Damm <damm@opensource.se>
+
+    tftp client insmod powerpc support
+
+=for html <br>
+
+Larry Doolittle <ldoolitt@recycle.lbl.gov>
+
+    pristine source directory compilation, lots of patches and fixes.
+
+=for html <br>
+
+Glenn Engel <glenne@engel.org>
+
+    httpd
+
+=for html <br>
+
+Gennady Feldman <gfeldman@gena01.com>
+
+    Sysklogd (single threaded syslogd, IPC Circular buffer support,
+    logread), various fixes.
+
+=for html <br>
+
+Karl M. Hegbloom <karlheg@debian.org>
+
+    cp_mv.c, the test suite, various fixes to utility.c, &c.
+
+=for html <br>
+
+Daniel Jacobowitz <dan@debian.org>
+
+    mktemp.c
+
+=for html <br>
+
+Matt Kraai <kraai@alumni.cmu.edu>
+
+    documentation, bugfixes, test suite
+
+=for html <br>
+
+Stephan Linz <linz@li-pro.net>
+
+       ipcalc, Red Hat equivalence
+
+=for html <br>
+
+John Lombardo <john@deltanet.com>
+
+    tr
+
+=for html <br>
+
+Glenn McGrath <bug1@iinet.net.au>
+
+    Common unarchving code and unarchiving applets, ifupdown, ftpgetput,
+    nameif, sed, patch, fold, install, uudecode.
+    Various bugfixes, review and apply numerous patches.
+
+=for html <br>
+
+Manuel Novoa III <mjn3@codepoet.org>
+
+    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 <br>
+
+Vladimir Oleynik <dzo@simtreas.ru>
+
+    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 <br>
+
+Bruce Perens <bruce@pixar.com>
+
+    Original author of BusyBox in 1995, 1996. Some of his code can
+    still be found hiding here and there...
+
+=for html <br>
+
+Tim Riker <Tim@Rikers.org>
+
+    bug fixes, member of fan club
+
+=for html <br>
+
+Kent Robotti <robotti@metconnect.com>
+
+    reset, tons and tons of bug reports and patches.
+
+=for html <br>
+
+Chip Rosenthal <chip@unicom.com>, <crosenth@covad.com>
+
+    wget - Contributed by permission of Covad Communications
+
+=for html <br>
+
+Pavel Roskin <proski@gnu.org>
+
+    Lots of bugs fixes and patches.
+
+=for html <br>
+
+Gyepi Sam <gyepi@praxis-sw.com>
+
+    Remote logging feature for syslogd
+
+=for html <br>
+
+Linus Torvalds <torvalds@transmeta.com>
+
+    mkswap, fsck.minix, mkfs.minix
+
+=for html <br>
+
+Mark Whitley <markw@codepoet.org>
+
+    grep, sed, cut, xargs(previous),
+    style-guide, new-applet-HOWTO, bug fixes, etc.
+
+=for html <br>
+
+Charles P. Wright <cpwright@villagenet.com>
+
+    gzip, mini-netcat(nc)
+
+=for html <br>
+
+Enrique Zanardi <ezanardi@ull.es>
+
+    tarcat (since removed), loadkmap, various fixes, Debian maintenance
+
+=for html <br>
+
+Tito Ragusa <farmatito@tiscali.it>
+
+       devfsd and size optimizations in strings, openvt and deallocvt.
+
+=cut
+
diff --git a/docs/busybox_header.pod b/docs/busybox_header.pod
new file mode 100644 (file)
index 0000000..9f2ffc4
--- /dev/null
@@ -0,0 +1,83 @@
+# vi: set sw=4 ts=4:
+
+=head1 NAME
+
+BusyBox - The Swiss Army Knife of Embedded Linux
+
+=head1 SYNTAX
+
+ busybox <applet> [arguments...]  # or
+
+ <applet> [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 CONFIG_PREFIX. CONFIG_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 CONFIG_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 CONFIG_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 applets 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 available applets include:
+
diff --git a/docs/cgi/cl.html b/docs/cgi/cl.html
new file mode 100644 (file)
index 0000000..5779d62
--- /dev/null
@@ -0,0 +1,46 @@
+<html><head><title>CGI Command line options</title></head><body><h1><img alt="" src="cl_files/CGIlogo.gif"> CGI Command line options</h1>
+<hr> <p>
+
+</p><h2>Specification</h2>
+
+The command line is only used in the case of an ISINDEX query. It is
+not used in the case of an HTML form or any as yet undefined query
+type. The server should search the query information (the <code>QUERY_STRING</code> environment variable) for a non-encoded
+= character to determine if the command line is to be used, if it
+finds one, the command line is not to be used. This trusts the clients
+to encode the = sign in ISINDEX queries, a practice which was
+considered safe at the time of the design of this specification. <p>
+
+For example, use the <a href="http://hoohoo.ncsa.uiuc.edu/cgi-bin/finger">finger script</a> and the ISINDEX interface to look up "httpd".  You will see that the script will call itself with <code>/cgi-bin/finger?httpd</code> and will actually execute "finger httpd" on the command line and output the results to you.
+</p><p>
+If the server does find a "=" in the <code>QUERY_STRING</code>,
+then the command line will not be used, and no decoding will be
+performed. The query then remains intact for processing by an
+appropriate FORM submission decoder.
+Again, as an example, use <a href="http://hoohoo.ncsa.uiuc.edu/cgi-bin/finger?httpd=name">this hyperlink</a> to submit <code>"httpd=name"</code> to the finger script.  Since this <code>QUERY_STRING</code>
+contained an unencoded "=", nothing was decoded, the script didn't know
+it was being submitted a valid query, and just gave you the default
+finger form.
+</p><p>
+If the server finds that it cannot send the string due to internal
+limitations (such as exec() or /bin/sh command line restrictions) the
+server should include NO command line information and provide the
+non-decoded query information in the environment
+variable <a href="http://hoohoo.ncsa.uiuc.edu/cgi/env.html#query"><code>QUERY_STRING</code></a>. </p><p>
+</p><hr>
+<h2>Examples</h2>
+
+Examples of the command line usage are much better <a href="http://hoohoo.ncsa.uiuc.edu/cgi/examples.html">demonstrated</a> than explained. For these
+examples, pay close attention to the script output which says what
+argc and argv are. <p>
+
+</p><hr>
+
+<a href="http://hoohoo.ncsa.uiuc.edu/cgi/interface.html"><img alt="[Back]" src="cl_files/back.gif">Return to the
+interface specification</a> <p>
+
+CGI - Common Gateway Interface
+</p><address><a href="http://hoohoo.ncsa.uiuc.edu/cgi/mailtocgi.html">cgi@ncsa.uiuc.edu</a></address>
+
+
+</body></html>
\ No newline at end of file
diff --git a/docs/cgi/env.html b/docs/cgi/env.html
new file mode 100644 (file)
index 0000000..924026b
--- /dev/null
@@ -0,0 +1,149 @@
+<html><head><title>CGI Environment Variables</title></head><body><h1><img alt="" src="env_files/CGIlogo.gif"> CGI Environment Variables</h1>
+<hr>
+
+<p>
+
+In order to pass data about the information request from the server to
+the script, the server uses command line arguments as well as
+environment variables. These environment variables are set when the
+server executes the gateway program. </p><p>
+
+</p><hr>
+<h2>Specification</h2>
+
+ <p>
+The following environment variables are not request-specific and are
+set for all requests: </p><p>
+
+</p><ul>
+<li> <code>SERVER_SOFTWARE</code> <p>
+
+    The name and version of the information server software answering
+    the request (and running the gateway). Format: name/version </p><p>
+
+</p></li><li> <code>SERVER_NAME</code> <p>
+    The server's hostname, DNS alias, or IP address as it would appear
+    in self-referencing URLs. </p><p>
+
+</p></li><li> <code>GATEWAY_INTERFACE</code> <p>
+    The revision of the CGI specification to which this server
+    complies. Format: CGI/revision</p><p>
+
+</p></li></ul>
+
+<hr>
+
+The following environment variables are specific to the request being
+fulfilled by the gateway program: <p>
+
+</p><ul>
+<li> <a name="protocol"><code>SERVER_PROTOCOL</code></a> <p>
+    The name and revision of the information protcol this request came
+    in with. Format: protocol/revision </p><p>
+
+</p></li><li> <code>SERVER_PORT</code>  <p>
+    The port number to which the request was sent. </p><p>
+
+</p></li><li> <code>REQUEST_METHOD</code> <p>
+    The method with which the request was made. For HTTP, this is
+    "GET", "HEAD", "POST", etc. </p><p>
+
+</p></li><li> <code>PATH_INFO</code> <p>
+    The extra path information, as given by the client. In other
+    words, scripts can be accessed by their virtual pathname, followed
+    by extra information at the end of this path. The extra
+    information is sent as PATH_INFO. This information should be
+    decoded by the server if it comes from a URL before it is passed
+    to the CGI script.</p><p>
+
+</p></li><li> <code>PATH_TRANSLATED</code> <p>
+    The server provides a translated version of PATH_INFO, which takes
+    the path and does any virtual-to-physical mapping to it. </p><p>
+
+</p></li><li> <code>SCRIPT_NAME</code> <p>
+    A virtual path to the script being executed, used for
+    self-referencing URLs. </p><p>
+
+</p></li><li> <a name="query"><code>QUERY_STRING</code></a> <p>
+    The information which follows the ? in the <a href="http://www.ncsa.uiuc.edu/demoweb/url-primer.html">URL</a>
+    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 <a href="http://hoohoo.ncsa.uiuc.edu/cgi/cl.html">command line decoding</a>. </p><p>
+
+</p></li><li> <code>REMOTE_HOST</code> <p>
+    The hostname making the request. If the server does not have this
+    information, it should set REMOTE_ADDR and leave this unset.</p><p>
+
+</p></li><li> <code>REMOTE_ADDR</code> <p>
+    The IP address of the remote host making the request. </p><p>
+
+</p></li><li> <code>AUTH_TYPE</code> <p>
+    If the server supports user authentication, and the script is
+    protects, this is the protocol-specific authentication method used
+    to validate the user. </p><p>
+
+</p></li><li> <code>REMOTE_USER</code> <p>
+    If the server supports user authentication, and the script is
+    protected, this is the username they have authenticated as. </p><p>
+</p></li><li> <code>REMOTE_IDENT</code> <p>
+    If the HTTP server supports RFC 931 identification, then this
+    variable will be set to the remote user name retrieved from the
+    server. Usage of this variable should be limited to logging only.
+    </p><p>
+
+</p></li><li> <a name="ct"><code>CONTENT_TYPE</code></a> <p>
+    For queries which have attached information, such as HTTP POST and
+    PUT, this is the content type of the data. </p><p>
+
+</p></li><li> <a name="cl"><code>CONTENT_LENGTH</code></a> <p>
+    The length of the said content as given by the client. </p><p>
+
+</p></li></ul>
+
+
+<a name="headers"><hr></a>
+
+In addition to these, the header lines received from the client, if
+any, are placed into the environment with the prefix HTTP_ followed by
+the header name. Any - characters in the header name are changed to _
+characters. The server may exclude any headers which it has already
+processed, such as Authorization, Content-type, and Content-length. If
+necessary, the server may choose to exclude any or all of these
+headers if including them would exceed any system environment
+limits. <p>
+
+An example of this is the HTTP_ACCEPT variable which was defined in
+CGI/1.0. Another example is the header User-Agent.</p><p>
+
+</p><ul>
+<li> <code>HTTP_ACCEPT</code> <p>
+    The MIME types which the client will accept, as given by HTTP
+    headers. Other protocols may need to get this information from
+    elsewhere. Each item in this list should be separated by commas as
+    per the HTTP spec. </p><p>
+
+    Format: type/subtype, type/subtype </p><p>
+
+
+</p></li><li> <code>HTTP_USER_AGENT</code><p>
+
+    The browser the client is using to send the request. General
+format: <code>software/version library/version</code>.</p><p>
+
+</p></li></ul>
+
+<hr>
+<h2>Examples</h2>
+
+Examples of the setting of environment variables are really much better
+<a href="http://hoohoo.ncsa.uiuc.edu/cgi/examples.html">demonstrated</a> than explained. <p>
+
+</p><hr>
+
+<a href="http://hoohoo.ncsa.uiuc.edu/cgi/interface.html"><img alt="[Back]" src="env_files/back.gif">Return to the
+interface specification</a> <p>
+
+CGI - Common Gateway Interface
+</p><address><a href="http://hoohoo.ncsa.uiuc.edu/cgi/mailtocgi.html">cgi@ncsa.uiuc.edu</a></address>
+
+</body></html>
\ No newline at end of file
diff --git a/docs/cgi/in.html b/docs/cgi/in.html
new file mode 100644 (file)
index 0000000..679306a
--- /dev/null
@@ -0,0 +1,33 @@
+<html><head><title>CGI Script input</title></head><body><h1><img alt="" src="in_files/CGIlogo.gif"> CGI Script Input</h1>
+<hr>
+
+<h2>Specification</h2>
+
+For requests which have information attached after the header, such as
+HTTP POST or PUT, the information will be sent to the script on stdin.
+<p>
+
+The server will send <a href="http://hoohoo.ncsa.uiuc.edu/cgi/env.html#cl">CONTENT_LENGTH</a> bytes on
+this file descriptor. Remember that it will give the <a href="http://hoohoo.ncsa.uiuc.edu/cgi/env.html#ct">CONTENT_TYPE</a> of the data as well. The server is
+in no way obligated to send end-of-file after the script reads
+<code>CONTENT_LENGTH</code> bytes. </p><p>
+</p><hr>
+<h2>Example</h2>
+
+Let's take a form with METHOD="POST" as an example. Let's say the form
+results are 7 bytes encoded, and look like <code>a=b&amp;b=c</code>.
+<p>
+
+In this case, the server will set CONTENT_LENGTH to 7 and CONTENT_TYPE
+to application/x-www-form-urlencoded. The first byte on the script's
+standard input will be "a", followed by the rest of the encoded string.</p><p>
+
+</p><hr>
+
+<a href="http://hoohoo.ncsa.uiuc.edu/cgi/interface.html"><img alt="[Back]" src="in_files/back.gif">Return to the
+interface specification</a> <p>
+
+CGI - Common Gateway Interface
+</p><address><a href="http://hoohoo.ncsa.uiuc.edu/cgi/mailtocgi.html">cgi@ncsa.uiuc.edu</a></address>
+
+</body></html>
\ No newline at end of file
diff --git a/docs/cgi/interface.html b/docs/cgi/interface.html
new file mode 100644 (file)
index 0000000..ea73ce3
--- /dev/null
@@ -0,0 +1,29 @@
+<html><head><title>The Common Gateway Interface Specification
+[http://hoohoo.ncsa.uiuc.edu/cgi/interface.html]
+</title></head><body><h1><img alt="" src="interface_files/CGIlogo.gif"> The CGI Specification</h1>
+
+<hr>
+
+This is the specification for CGI version 1.1, or CGI/1.1. Further
+revisions of this protocol are guaranteed to be backward compatible.
+<p>
+
+The server and the CGI script communicate in four major ways. Each of
+the following is a hotlink to graphic detail.</p><p>
+
+</p><ul>
+<li> <a href="env.html">Environment variables</a>
+</li><li> <a href="cl.html">The command line</a>
+</li><li> <a href="in.html">Standard input</a>
+</li><li> <a href="out.html">Standard output</a>
+</li></ul>
+<hr>
+
+
+<a href="http://hoohoo.ncsa.uiuc.edu/cgi/overview.html"><img alt="[Back]" src="interface_files/back.gif">Return to the overview</a> <p>
+
+
+
+CGI - Common Gateway Interface
+</p><address><a href="http://hoohoo.ncsa.uiuc.edu/cgi/mailtocgi.html">cgi@ncsa.uiuc.edu</a></address>
+</body></html>
\ No newline at end of file
diff --git a/docs/cgi/out.html b/docs/cgi/out.html
new file mode 100644 (file)
index 0000000..2203ee5
--- /dev/null
@@ -0,0 +1,126 @@
+<html><head><title>CGI Script output</title></head><body><h1><img alt="" src="out_files/CGIlogo.gif"> CGI Script Output</h1>
+<hr>
+
+<h2>Script output</h2>
+
+The script sends its output to stdout. This output can either be a
+document generated by the script, or instructions to the server for
+retrieving the desired output. <p>
+</p><hr>
+
+<h2>Script naming conventions</h2>
+
+Normally, scripts produce output which is interpreted and sent back to
+the client. An advantage of this is that the scripts do not need to
+send a full HTTP/1.0 header for every request.  <p>
+<a name="nph">
+Some scripts may want to avoid the extra overhead of the server
+parsing their output, and talk directly to the client. In order to
+distinguish these scripts from the other scripts, CGI requires that
+the script name begins with nph- if a script does not want the server
+to parse its header. In this case, it is the script's responsibility
+to return a valid HTTP/1.0 (or HTTP/0.9) response to the client.  </a></p><p>
+
+</p><hr>
+<h2><a name="nph">Parsed headers</a></h2>
+
+<a name="nph">The output of scripts begins with a small header. This header consists
+of text lines, in the same format as an </a><a href="http://www.w3.org/hypertext/WWW/Protocols/HTTP/Object_Headers.html">
+HTTP header</a>, terminated by a blank line (a line with only a
+linefeed or CR/LF). <p>
+
+Any headers which are not server directives are sent directly back to
+the client. Currently, this specification defines three server
+directives:</p><p>
+
+</p><ul>
+<li> <code>Content-type</code> <p>
+
+    This is the MIME type of the document you are returning.  </p><p>
+
+</p></li><li> <code>Location</code> <p>
+
+    This is used to specify to the server that you are returning a
+    reference to a document rather than an actual document. </p><p>
+
+    If the argument to this is a URL, the server will issue a redirect
+    to the client. </p><p>
+
+    If the argument to this is a virtual path, the server will
+    retrieve the document specified as if the client had requested
+    that document originally. ? directives will work in here, but #
+    directives must be redirected back to the client.</p><p>
+
+
+</p></li><li> <a name="status"><code>Status</code></a><p>
+
+    This is used to give the server an HTTP/1.0 <a href="http://www.w3.org/hypertext/WWW/Protocols/HTTP/HTRESP.html">status
+line</a> to send to the client. The format is <code>nnn xxxxx</code>,
+where <code>nnn</code> is the 3-digit status code, and
+<code>xxxxx</code> is the reason string, such as "Forbidden".</p><p>
+
+</p></li></ul>
+
+<hr>
+<h2>Examples</h2>
+
+Let's say I have a fromgratz to HTML converter. When my converter is
+finished with its work, it will output the following on stdout (note
+that the lines beginning and ending with --- are just for illustration
+and would not be output): <p>
+
+</p><pre>--- start of output ---
+Content-type: text/html
+
+--- end of output ---
+</pre>
+
+Note the blank line after Content-type. <p>
+
+Now, let's say I have a script which, in certain instances, wants to
+return the document <code>/path/doc.txt</code> from this server just
+as if the user had actually requested
+<code>http://server:port/path/doc.txt</code> to begin with. In this
+case, the script would output: </p><p>
+</p><pre>--- start of output ---
+Location: /path/doc.txt
+
+--- end of output ---
+</pre>
+
+The server would then perform the request and send it to the client.
+<p>
+
+Let's say that I have a script which wants to reference our gopher
+server. In this case, if the script wanted to refer the user to
+<code>gopher://gopher.ncsa.uiuc.edu/</code>, it would output: </p><p>
+
+</p><pre>--- start of output ---
+Location: gopher://gopher.ncsa.uiuc.edu/
+
+--- end of output ---
+</pre>
+
+Finally, I have a script which wants to talk to the client directly.
+In this case, if the script is referenced with <a href="http://hoohoo.ncsa.uiuc.edu/cgi/env.html#protocol"><code>SERVER_PROTOCOL</code></a> of HTTP/1.0,
+the script would output the following HTTP/1.0 response: <p>
+
+</p><pre>--- start of output ---
+HTTP/1.0 200 OK
+Server: NCSA/1.0a6
+Content-type: text/plain
+
+This is a plaintext document generated on the fly just for you.
+
+--- end of output ---
+</pre>
+
+
+<hr>
+
+<a href="http://hoohoo.ncsa.uiuc.edu/cgi/interface.html"><img alt="[Back]" src="out_files/back.gif">Return to the
+interface specification</a> <p>
+
+CGI - Common Gateway Interface
+</p><address><a href="http://hoohoo.ncsa.uiuc.edu/cgi/mailtocgi.html">cgi@ncsa.uiuc.edu</a></address>
+</body></html>
\ No newline at end of file
diff --git a/docs/contributing.txt b/docs/contributing.txt
new file mode 100644 (file)
index 0000000..aad4303
--- /dev/null
@@ -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/ctty.htm b/docs/ctty.htm
new file mode 100644 (file)
index 0000000..8f466cd
--- /dev/null
@@ -0,0 +1,476 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html><head>
+ <!-- saved from http://www.win.tue.nl/~aeb/linux/lk/lk-10.html -->
+ <meta name="GENERATOR" content="SGML-Tools 1.0.9"><title>The Linux kernel: Processes</title>
+</head>
+<body>
+<hr>
+<h2><a name="s10">10. Processes</a></h2>
+
+<p>Before looking at the Linux implementation, first a general Unix
+description of threads, processes, process groups and sessions.
+</p><p>A session contains a number of process groups, and a process group
+contains a number of processes, and a process contains a number
+of threads.
+</p><p>A session can have a controlling tty.
+At most one process group in a session can be a foreground process group.
+An interrupt character typed on a tty ("Teletype", i.e., terminal)
+causes a signal to be sent to all members of the foreground process group
+in the session (if any) that has that tty as controlling tty.
+</p><p>All these objects have numbers, and we have thread IDs, process IDs,
+process group IDs and session IDs.
+</p><p>
+</p><h2><a name="ss10.1">10.1 Processes</a>
+</h2>
+
+<p>
+</p><h3>Creation</h3>
+
+<p>A new process is traditionally started using the <code>fork()</code>
+system call:
+</p><blockquote>
+<pre>pid_t p;
+
+p = fork();
+if (p == (pid_t) -1)
+        /* ERROR */
+else if (p == 0)
+        /* CHILD */
+else
+        /* PARENT */
+</pre>
+</blockquote>
+<p>This creates a child as a duplicate of its parent.
+Parent and child are identical in almost all respects.
+In the code they are distinguished by the fact that the parent
+learns the process ID of its child, while <code>fork()</code>
+returns 0 in the child. (It can find the process ID of its
+parent using the <code>getppid()</code> system call.)
+</p><p>
+</p><h3>Termination</h3>
+
+<p>Normal termination is when the process does
+</p><blockquote>
+<pre>exit(n);
+</pre>
+</blockquote>
+
+or
+<blockquote>
+<pre>return n;
+</pre>
+</blockquote>
+
+from its <code>main()</code> procedure. It returns the single byte <code>n</code>
+to its parent.
+<p>Abnormal termination is usually caused by a signal.
+</p><p>
+</p><h3>Collecting the exit code. Zombies</h3>
+
+<p>The parent does
+</p><blockquote>
+<pre>pid_t p;
+int status;
+
+p = wait(&amp;status);
+</pre>
+</blockquote>
+
+and collects two bytes:
+<p>
+<figure>
+<eps file="absent">
+<img src="ctty_files/exit_status.png">
+</eps>
+</figure></p><p>A process that has terminated but has not yet been waited for
+is a <i>zombie</i>. It need only store these two bytes:
+exit code and reason for termination.
+</p><p>On the other hand, if the parent dies first, <code>init</code> (process 1)
+inherits the child and becomes its parent.
+</p><p>
+</p><h3>Signals</h3>
+
+<p>
+</p><h3>Stopping</h3>
+
+<p>Some signals cause a process to stop:
+<code>SIGSTOP</code> (stop!),
+<code>SIGTSTP</code> (stop from tty: probably ^Z was typed),
+<code>SIGTTIN</code> (tty input asked by background process),
+<code>SIGTTOU</code> (tty output sent by background process, and this was
+disallowed by <code>stty tostop</code>).
+</p><p>Apart from ^Z there also is ^Y. The former stops the process
+when it is typed, the latter stops it when it is read.
+</p><p>Signals generated by typing the corresponding character on some tty
+are sent to all processes that are in the foreground process group
+of the session that has that tty as controlling tty. (Details below.)
+</p><p>If a process is being traced, every signal will stop it.
+</p><p>
+</p><h3>Continuing</h3>
+
+<p><code>SIGCONT</code>: continue a stopped process.
+</p><p>
+</p><h3>Terminating</h3>
+
+<p><code>SIGKILL</code> (die! now!),
+<code>SIGTERM</code> (please, go away),
+<code>SIGHUP</code> (modem hangup),
+<code>SIGINT</code> (^C),
+<code>SIGQUIT</code> (^\), etc.
+Many signals have as default action to kill the target.
+(Sometimes with an additional core dump, when such is
+allowed by rlimit.)
+The signals <code>SIGCHLD</code> and <code>SIGWINCH</code>
+are ignored by default.
+All except <code>SIGKILL</code> and <code>SIGSTOP</code> can be
+caught or ignored or blocked.
+For details, see <code>signal(7)</code>.
+</p><p>
+</p><h2><a name="ss10.2">10.2 Process groups</a>
+</h2>
+
+<p>Every process is member of a unique <i>process group</i>,
+identified by its <i>process group ID</i>.
+(When the process is created, it becomes a member of the process group
+of its parent.)
+By convention, the process group ID of a process group
+equals the process ID of the first member of the process group,
+called the <i>process group leader</i>.
+A process finds the ID of its process group using the system call
+<code>getpgrp()</code>, or, equivalently, <code>getpgid(0)</code>.
+One finds the process group ID of process <code>p</code> using
+<code>getpgid(p)</code>.
+</p><p>One may use the command <code>ps j</code> to see PPID (parent process ID),
+PID (process ID), PGID (process group ID) and SID (session ID)
+of processes. With a shell that does not know about job control,
+like <code>ash</code>, each of its children will be in the same session
+and have the same process group as the shell. With a shell that knows
+about job control, like <code>bash</code>, the processes of one pipeline, like
+</p><blockquote>
+<pre>% cat paper | ideal | pic | tbl | eqn | ditroff &gt; out
+</pre>
+</blockquote>
+
+form a single process group.
+<p>
+</p><h3>Creation</h3>
+
+<p>A process <code>pid</code> is put into the process group <code>pgid</code> by
+</p><blockquote>
+<pre>setpgid(pid, pgid);
+</pre>
+</blockquote>
+
+If <code>pgid == pid</code> or <code>pgid == 0</code> then this creates
+a new process group with process group leader <code>pid</code>.
+Otherwise, this puts <code>pid</code> into the already existing
+process group <code>pgid</code>.
+A zero <code>pid</code> refers to the current process.
+The call <code>setpgrp()</code> is equivalent to <code>setpgid(0,0)</code>.
+<p>
+</p><h3>Restrictions on setpgid()</h3>
+
+<p>The calling process must be <code>pid</code> itself, or its parent,
+and the parent can only do this before <code>pid</code> has done
+<code>exec()</code>, and only when both belong to the same session.
+It is an error if process <code>pid</code> is a session leader
+(and this call would change its <code>pgid</code>).
+</p><p>
+</p><h3>Typical sequence</h3>
+
+<p>
+</p><blockquote>
+<pre>p = fork();
+if (p == (pid_t) -1) {
+        /* ERROR */
+} else if (p == 0) {    /* CHILD */
+        setpgid(0, pgid);
+        ...
+} else {                /* PARENT */
+        setpgid(p, pgid);
+        ...
+}
+</pre>
+</blockquote>
+
+This ensures that regardless of whether parent or child is scheduled
+first, the process group setting is as expected by both.
+<p>
+</p><h3>Signalling and waiting</h3>
+
+<p>One can signal all members of a process group:
+</p><blockquote>
+<pre>killpg(pgrp, sig);
+</pre>
+</blockquote>
+<p>One can wait for children in ones own process group:
+</p><blockquote>
+<pre>waitpid(0, &amp;status, ...);
+</pre>
+</blockquote>
+
+or in a specified process group:
+<blockquote>
+<pre>waitpid(-pgrp, &amp;status, ...);
+</pre>
+</blockquote>
+<p>
+</p><h3>Foreground process group</h3>
+
+<p>Among the process groups in a session at most one can be
+the <i>foreground process group</i> of that session.
+The tty input and tty signals (signals generated by ^C, ^Z, etc.)
+go to processes in this foreground process group.
+</p><p>A process can determine the foreground process group in its session
+using <code>tcgetpgrp(fd)</code>, where <code>fd</code> refers to its
+controlling tty. If there is none, this returns a random value
+larger than 1 that is not a process group ID.
+</p><p>A process can set the foreground process group in its session
+using <code>tcsetpgrp(fd,pgrp)</code>, where <code>fd</code> refers to its
+controlling tty, and <code>pgrp</code> is a process group in
+its session, and this session still is associated to the controlling
+tty of the calling process.
+</p><p>How does one get <code>fd</code>? By definition, <code>/dev/tty</code>
+refers to the controlling tty, entirely independent of redirects
+of standard input and output. (There is also the function
+<code>ctermid()</code> to get the name of the controlling terminal.
+On a POSIX standard system it will return <code>/dev/tty</code>.)
+Opening the name of the
+controlling tty gives a file descriptor <code>fd</code>.
+</p><p>
+</p><h3>Background process groups</h3>
+
+<p>All process groups in a session that are not foreground
+process group are <i>background process groups</i>.
+Since the user at the keyboard is interacting with foreground
+processes, background processes should stay away from it.
+When a background process reads from the terminal it gets
+a SIGTTIN signal. Normally, that will stop it, the job control shell
+notices and tells the user, who can say <code>fg</code> to continue
+this background process as a foreground process, and then this
+process can read from the terminal. But if the background process
+ignores or blocks the SIGTTIN signal, or if its process group
+is orphaned (see below), then the read() returns an EIO error,
+and no signal is sent. (Indeed, the idea is to tell the process
+that reading from the terminal is not allowed right now.
+If it wouldn't see the signal, then it will see the error return.)
+</p><p>When a background process writes to the terminal, it may get
+a SIGTTOU signal. May: namely, when the flag that this must happen
+is set (it is off by default). One can set the flag by
+</p><blockquote>
+<pre>% stty tostop
+</pre>
+</blockquote>
+
+and clear it again by
+<blockquote>
+<pre>% stty -tostop
+</pre>
+</blockquote>
+
+and inspect it by
+<blockquote>
+<pre>% stty -a
+</pre>
+</blockquote>
+
+Again, if TOSTOP is set but the background process ignores or blocks
+the SIGTTOU signal, or if its process group is orphaned (see below),
+then the write() returns an EIO error, and no signal is sent.
+<p>
+</p><h3>Orphaned process groups</h3>
+
+<p>The process group leader is the first member of the process group.
+It may terminate before the others, and then the process group is
+without leader.
+</p><p>A process group is called <i>orphaned</i> when <i>the
+parent of every member is either in the process group
+or outside the session</i>.
+In particular, the process group of the session leader
+is always orphaned.
+</p><p>If termination of a process causes a process group to become
+orphaned, and some member is stopped, then all are sent first SIGHUP
+and then SIGCONT.
+</p><p>The idea is that perhaps the parent of the process group leader
+is a job control shell. (In the same session but a different
+process group.) As long as this parent is alive, it can
+handle the stopping and starting of members in the process group.
+When it dies, there may be nobody to continue stopped processes.
+Therefore, these stopped processes are sent SIGHUP, so that they
+die unless they catch or ignore it, and then SIGCONT to continue them.
+</p><p>Note that the process group of the session leader is already
+orphaned, so no signals are sent when the session leader dies.
+</p><p>Note also that a process group can become orphaned in two ways
+by termination of a process: either it was a parent and not itself
+in the process group, or it was the last element of the process group
+with a parent outside but in the same session.
+Furthermore, that a process group can become orphaned
+other than by termination of a process, namely when some
+member is moved to a different process group.
+</p><p>
+</p><h2><a name="ss10.3">10.3 Sessions</a>
+</h2>
+
+<p>Every process group is in a unique <i>session</i>.
+(When the process is created, it becomes a member of the session
+of its parent.)
+By convention, the session ID of a session
+equals the process ID of the first member of the session,
+called the <i>session leader</i>.
+A process finds the ID of its session using the system call
+<code>getsid()</code>.
+</p><p>Every session may have a <i>controlling tty</i>,
+that then also is called the controlling tty of each of
+its member processes.
+A file descriptor for the controlling tty is obtained by
+opening <code>/dev/tty</code>. (And when that fails, there was no
+controlling tty.) Given a file descriptor for the controlling tty,
+one may obtain the SID using <code>tcgetsid(fd)</code>.
+</p><p>A session is often set up by a login process. The terminal
+on which one is logged in then becomes the controlling tty
+of the session. All processes that are descendants of the
+login process will in general be members of the session.
+</p><p>
+</p><h3>Creation</h3>
+
+<p>A new session is created by
+</p><blockquote>
+<pre>pid = setsid();
+</pre>
+</blockquote>
+
+This is allowed only when the current process is not a process group leader.
+In order to be sure of that we fork first:
+<blockquote>
+<pre>p = fork();
+if (p) exit(0);
+pid = setsid();
+</pre>
+</blockquote>
+
+The result is that the current process (with process ID <code>pid</code>)
+becomes session leader of a new session with session ID <code>pid</code>.
+Moreover, it becomes process group leader of a new process group.
+Both session and process group contain only the single process <code>pid</code>.
+Furthermore, this process has no controlling tty.
+<p>The restriction that the current process must not be a process group leader
+is needed: otherwise its PID serves as PGID of some existing process group
+and cannot be used as the PGID of a new process group.
+</p><p>
+</p><h3>Getting a controlling tty</h3>
+
+<p>How does one get a controlling terminal? Nobody knows,
+this is a great mystery.
+</p><p>The System V approach is that the first tty opened by the process
+becomes its controlling tty.
+</p><p>The BSD approach is that one has to explicitly call
+</p><blockquote>
+<pre>ioctl(fd, TIOCSCTTY, 0/1);
+</pre>
+</blockquote>
+
+to get a controlling tty.
+<p>Linux tries to be compatible with both, as always, and this
+results in a very obscure complex of conditions. Roughly:
+</p><p>The <code>TIOCSCTTY</code> ioctl will give us a controlling tty,
+provided that (i) the current process is a session leader,
+and (ii) it does not yet have a controlling tty, and
+(iii) maybe the tty should not already control some other session;
+if it does it is an error if we aren't root, or we steal the tty
+if we are all-powerful.
+[vda: correction: third parameter controls this: if 1, we steal tty from
+any such session, if 0, we don't steal]
+</p><p>Opening some terminal will give us a controlling tty,
+provided that (i) the current process is a session leader, and
+(ii) it does not yet have a controlling tty, and
+(iii) the tty does not already control some other session, and
+(iv) the open did not have the <code>O_NOCTTY</code> flag, and
+(v) the tty is not the foreground VT, and
+(vi) the tty is not the console, and
+(vii) maybe the tty should not be master or slave pty.
+</p><p>
+</p><h3>Getting rid of a controlling tty</h3>
+
+<p>If a process wants to continue as a daemon, it must detach itself
+from its controlling tty. Above we saw that <code>setsid()</code>
+will remove the controlling tty. Also the ioctl TIOCNOTTY does this.
+Moreover, in order not to get a controlling tty again as soon as it
+opens a tty, the process has to fork once more, to assure that it
+is not a session leader. Typical code fragment:
+</p><p>
+</p><pre>        if ((fork()) != 0)
+                exit(0);
+        setsid();
+        if ((fork()) != 0)
+                exit(0);
+</pre>
+<p>See also <code>daemon(3)</code>.
+</p><p>
+</p><h3>Disconnect</h3>
+
+<p>If the terminal goes away by modem hangup, and the line was not local,
+then a SIGHUP is sent to the session leader.
+Any further reads from the gone terminal return EOF.
+(Or possibly -1 with <code>errno</code> set to EIO.)
+</p><p>If the terminal is the slave side of a pseudotty, and the master side
+is closed (for the last time), then a SIGHUP is sent to the foreground
+process group of the slave side.
+</p><p>When the session leader dies, a SIGHUP is sent to all processes
+in the foreground process group. Moreover, the terminal stops being
+the controlling terminal of this session (so that it can become
+the controlling terminal of another session).
+</p><p>Thus, if the terminal goes away and the session leader is
+a job control shell, then it can handle things for its descendants,
+e.g. by sending them again a SIGHUP.
+If on the other hand the session leader is an innocent process
+that does not catch SIGHUP, it will die, and all foreground processes
+get a SIGHUP.
+</p><p>
+</p><h2><a name="ss10.4">10.4 Threads</a>
+</h2>
+
+<p>A process can have several threads. New threads (with the same PID
+as the parent thread) are started using the <code>clone</code> system
+call using the <code>CLONE_THREAD</code> flag. Threads are distinguished
+by a <i>thread ID</i> (TID). An ordinary process has a single thread
+with TID equal to PID. The system call <code>gettid()</code> returns the
+TID. The system call <code>tkill()</code> sends a signal to a single thread.
+</p><p>Example: a process with two threads. Both only print PID and TID and exit.
+(Linux 2.4.19 or later.)
+</p><pre>% cat &lt;&lt; EOF &gt; gettid-demo.c
+#include &lt;unistd.h&gt;
+#include &lt;sys/types.h&gt;
+#define CLONE_SIGHAND   0x00000800
+#define CLONE_THREAD    0x00010000
+#include &lt;linux/unistd.h&gt;
+#include &lt;errno.h&gt;
+_syscall0(pid_t,gettid)
+
+int thread(void *p) {
+        printf("thread: %d %d\n", gettid(), getpid());
+}
+
+main() {
+        unsigned char stack[4096];
+        int i;
+
+        i = clone(thread, stack+2048, CLONE_THREAD | CLONE_SIGHAND, NULL);
+        if (i == -1)
+                perror("clone");
+        else
+                printf("clone returns %d\n", i);
+        printf("parent: %d %d\n", gettid(), getpid());
+}
+EOF
+% cc -o gettid-demo gettid-demo.c
+% ./gettid-demo
+clone returns 21826
+parent: 21825 21825
+thread: 21826 21825
+%
+</pre>
+<p>
+</p><p>
+</p><hr>
+
+</body></html>
diff --git a/docs/draft-coar-cgi-v11-03-clean.html b/docs/draft-coar-cgi-v11-03-clean.html
new file mode 100644 (file)
index 0000000..d52c9b8
--- /dev/null
@@ -0,0 +1,2674 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"
+ "http://www.w3.org/TR/REC-html40/loose.dtd">
+<HTML>
+ <HEAD>
+  <TITLE>Common Gateway Interface - 1.1 *Draft 03* [http://cgi-spec.golux.com/draft-coar-cgi-v11-03-clean.html]
+  </TITLE>
+<!--#if expr="$HTTP_USER_AGENT != /Lynx/" -->
+ <!--#set var="GUI" value="1" -->
+<!--#endif -->
+  <LINK HREF="mailto:Ken.Coar@Golux.Com" rev="revised">
+  <LINK REL="STYLESHEET" HREF="cgip-style-rfc.css" TYPE="text/css">
+  <META name="latexstyle" content="rfc">
+  <META name="author" content="Ken A L Coar">
+  <META name="institute" content="IBM Corporation">
+  <META name="date" content="25 June 1999">
+  <META name="expires" content="Expires 31 December 1999">
+  <META name="document" content="INTERNET-DRAFT">
+  <META name="file" content="&lt;draft-coar-cgi-v11-03.txt&gt;">
+  <META name="group" content="INTERNET-DRAFT">
+<!--
+    There are a lot of BNF fragments in this document.  To make it work
+    in all possible browsers (including Lynx, which is used to turn it
+    into text/plain), we handle these by using PREformatted blocks with
+    a universal internal margin of 2, inside one-level DL blocks.
+ -->
+ </HEAD>
+ <BODY>
+  <!--
+    HTML doesn't do paper pagination, so we need to fake it out.  Basing
+    our formatting upon RFC2068, there are four (4) lines of header and
+    four (4) lines of footer for each page.
+
+<DIV ALIGN="CENTER">
+ <PRE>
+
+
+
+
+Coar, et al.               CGI/1.1 Specification                     May, 1998
+INTERNET-DRAFT             Expires 1 December 1998                    [Page 2]
+
+
+ </PRE>
+</DIV>
+  -->
+  <!--
+    The following weirdness wrt non-breaking spaces is to get Lynx
+    (which is barely TABLE-aware) to line the left/right justified
+    text up properly.
+  -->
+  <DIV ALIGN="CENTER">
+   <TABLE WIDTH="100%" CELLPADDING=0 CELLSPACING=0>
+    <TR VALIGN="TOP">
+     <TD ALIGN="LEFT">
+      INTERNET-DRAFT&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+     </TD>
+     <TD ALIGN="RIGHT">
+      &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Ken A L Coar
+     </TD>
+    </TR>
+    <TR VALIGN="TOP">
+     <TD ALIGN="LEFT">
+      draft-coar-cgi-v11-03.{html,txt}&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+     </TD>
+     <TD ALIGN="RIGHT">
+      &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;IBM Corporation
+     </TD>
+    </TR>
+    <TR VALIGN="TOP">
+     <TD ALIGN="LEFT">
+      &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+     </TD>
+     <TD ALIGN="RIGHT">
+      &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;D.R.T. Robinson
+     </TD>
+    </TR>
+    <TR VALIGN="TOP">
+     <TD ALIGN="LEFT">
+      &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+     </TD>
+     <TD ALIGN="RIGHT">
+      &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;E*TRADE&nbsp;UK&nbsp;Ltd.
+     </TD>
+    </TR>
+    <TR VALIGN="TOP">
+     <TD ALIGN="LEFT">
+      &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+     </TD>
+     <TD ALIGN="RIGHT">
+      &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;25 June 1999
+     </TD>
+    </TR>
+   </TABLE>
+  </DIV>
+
+  <H1 ALIGN="CENTER">
+   The WWW Common Gateway Interface
+   <BR>
+   Version 1.1
+  </H1>
+
+<!--#include virtual="I-D-statement" -->
+
+  <H2>
+   <A NAME="Abstract">
+    Abstract
+   </A>
+  </H2>
+  <P>
+  The Common Gateway Interface (CGI) is a simple interface for running
+  external programs, software or gateways under an information server
+  in a platform-independent manner. Currently, the supported information
+  servers are HTTP servers.
+  </P>
+  <P>
+  The interface has been in use by the World-Wide Web since 1993. This
+  specification defines the
+  "current practice" parameters of the
+  'CGI/1.1' interface developed and documented at the U.S. National
+  Centre for Supercomputing Applications [NCSA-CGI].
+  This document also defines the use of the CGI/1.1 interface
+  on the Unix and AmigaDOS(tm) systems.
+  </P>
+  <P>
+  Discussion of this draft occurs on the CGI-WG mailing list; see the
+  project Web page at
+  <SAMP>&lt;URL:<A HREF="http://CGI-Spec.Golux.Com/"
+                >http://CGI-Spec.Golux.Com/</A>&gt;</SAMP>
+  for details on the mailing list and the status of the project.
+  </P>
+
+<!--#if expr="$GUI" -->
+  <H2>
+   Revision History
+  </H2>
+  <P>
+  The revision history of this draft is being maintained using Web-based
+  GUI notation, such as struck-through characters and colour-coded
+  sections.  The following legend describes how to determine the origin
+  of a particular revision according to the colour of the text:
+  </P>
+  <DL COMPACT>
+   <DT>Black
+   </DT>
+   <DD>Revision 00, released 28 May 1998
+   </DD>
+   <DT>Green
+   </DT>
+   <DD>Revision 01, released 28 December 1998
+    <BR>
+    Major structure change: Section 4, "Request Metadata (Meta-Variables)"
+    was moved entirely under <A HREF="#7.0">Section 7</A>, "Data Input to the
+    CGI Script."
+    Due to the size of this change, it is noted here and the text in its
+    former location does <EM>not</EM> appear as struckthrough.  This has
+    caused major <A HREF="#6.0">sections 5</A> and following to decrement
+    by one.  Other
+    large text movements are likewise not marked up.  References to RFC
+    1738 were changed to 2396 (1738's replacement).
+   </DD>
+   <DT>Red
+   </DT>
+   <DD>Revision 02, released 2 April, 1999
+    <BR>
+    Added text to <A HREF="#8.3">section 8.3</A> defining correct handling
+    of HTTP/1.1
+    requests using "chunked" Transfer-Encoding.  Labelled metavariable
+    names in <A HREF="#8.0">section 8</A> with the appropriate detail section
+    numbers.
+    Clarified allowed usage of <SAMP>Status</SAMP> and
+    <SAMP>Location</SAMP> response header fields.  Included new
+    Internet-Draft language.
+   </DD>
+   <DT>Fuchsia
+   </DT>
+   <DD>Revision 03, released 25 June 1999
+    <BR>
+    Changed references from "HTTP" to "Protocol-Specific" for the listing of
+    things like HTTP_ACCEPT.  Changed 'entity-body' and 'content-body' to
+    'message-body.'  Added a note that response headers must comply with
+    requirements of the protocol level in use.  Added a lot of stuff about
+    security (section 11).  Clarified a bunch of productions.  Pointed out
+    that zero-length and omitted values are indistinguishable in this
+    specification.  Clarified production describing order of fields in
+    script response header.  Clarified issues surrounding encoding of
+    data.  Acknowledged additional contributors, and changed one of
+    the authors' addresses.
+   </DD>
+  </DL>
+<!--#endif -->
+
+  <H2>
+   <A NAME="Contents">
+    Table of Contents
+   </A>
+  </H2>
+  <DIV ALIGN="CENTER">
+   <PRE>
+  1 Introduction..............................................<A
+                                                               HREF="#1.0"
+                                                              >TBD</A>
+   1.1 Purpose................................................<A
+                                                               HREF="#1.1"
+                                                              >TBD</A>
+   1.2 Requirements...........................................<A
+                                                               HREF="#1.2"
+                                                              >TBD</A>
+   1.3 Specifications.........................................<A
+                                                               HREF="#1.3"
+                                                              >TBD</A>
+   1.4 Terminology............................................<A
+                                                               HREF="#1.4"
+                                                              >TBD</A>
+  2 Notational Conventions and Generic Grammar................<A
+                                                               HREF="#2.0"
+                                                              >TBD</A>
+   2.1 Augmented BNF..........................................<A
+                                                               HREF="#2.1"
+                                                              >TBD</A>
+   2.2 Basic Rules............................................<A
+                                                               HREF="#2.2"
+                                                              >TBD</A>
+  3 Protocol Parameters.......................................<A
+                                                               HREF="#3.0"
+                                                              >TBD</A>
+   3.1 URL Encoding...........................................<A
+                                                               HREF="#3.1"
+                                                              >TBD</A>
+   3.2 The Script-URI.........................................<A
+                                                               HREF="#3.2"
+                                                              >TBD</A>
+  4 Invoking the Script.......................................<A
+                                                               HREF="#4.0"
+                                                              >TBD</A>
+  5 The CGI Script Command Line...............................<A
+                                                               HREF="#5.0"
+                                                              >TBD</A>
+  6 Data Input to the CGI Script..............................<A
+                                                               HREF="#6.0"
+                                                              >TBD</A>
+   6.1 Request Metadata (Metavariables).......................<A
+                                                               HREF="#6.1"
+                                                              >TBD</A>
+    6.1.1 AUTH_TYPE...........................................<A
+                                                               HREF="#6.1.1"
+                                                              >TBD</A>
+    6.1.2 CONTENT_LENGTH......................................<A
+                                                               HREF="#6.1.2"
+                                                              >TBD</A>
+    6.1.3 CONTENT_TYPE........................................<A
+                                                               HREF="#6.1.3"
+                                                              >TBD</A>
+    6.1.4 GATEWAY_INTERFACE...................................<A
+                                                               HREF="#6.1.4"
+                                                              >TBD</A>
+    6.1.5 Protocol-Specific Metavariables.....................<A
+                                                               HREF="#6.1.5"
+                                                              >TBD</A>
+    6.1.6 PATH_INFO...........................................<A
+                                                               HREF="#6.1.6"
+                                                              >TBD</A>
+    6.1.7 PATH_TRANSLATED.....................................<A
+                                                               HREF="#6.1.7"
+                                                              >TBD</A>
+    6.1.8 QUERY_STRING........................................<A
+                                                               HREF="#6.1.8"
+                                                              >TBD</A>
+    6.1.9 REMOTE_ADDR.........................................<A
+                                                               HREF="#6.1.9"
+                                                              >TBD</A>
+    6.1.10 REMOTE_HOST........................................<A
+                                                               HREF="#6.1.10"
+                                                              >TBD</A>
+    6.1.11 REMOTE_IDENT.......................................<A
+                                                               HREF="#6.1.11"
+                                                              >TBD</A>
+    6.1.12 REMOTE_USER........................................<A
+                                                               HREF="#6.1.12"
+                                                              >TBD</A>
+    6.1.13 REQUEST_METHOD.....................................<A
+                                                               HREF="#6.1.13"
+                                                              >TBD</A>
+    6.1.14 SCRIPT_NAME........................................<A
+                                                               HREF="#6.1.14"
+                                                              >TBD</A>
+    6.1.15 SERVER_NAME........................................<A
+                                                               HREF="#6.1.15"
+                                                              >TBD</A>
+    6.1.16 SERVER_PORT........................................<A
+                                                               HREF="#6.1.16"
+                                                              >TBD</A>
+    6.1.17 SERVER_PROTOCOL....................................<A
+                                                               HREF="#6.1.17"
+                                                              >TBD</A>
+    6.1.18 SERVER_SOFTWARE....................................<A
+                                                               HREF="#6.1.18"
+                                                              >TBD</A>
+    6.2 Request Message-Bodies................................<A
+                                                               HREF="#6.2"
+                                                              >TBD</A>
+  7 Data Output from the CGI Script...........................<A
+                                                               HREF="#7.0"
+                                                              >TBD</A>
+   7.1 Non-Parsed Header Output...............................<A
+                                                               HREF="#7.1"
+                                                              >TBD</A>
+   7.2 Parsed Header Output...................................<A
+                                                               HREF="#7.2"
+                                                              >TBD</A>
+    7.2.1 CGI header fields...................................<A
+                                                               HREF="#7.2.1"
+                                                              >TBD</A>
+     7.2.1.1 Content-Type.....................................<A
+                                                               HREF="#7.2.1.1"
+                                                              >TBD</A>
+     7.2.1.2 Location.........................................<A
+                                                               HREF="#7.2.1.2"
+                                                              >TBD</A>
+     7.2.1.3 Status...........................................<A
+                                                               HREF="#7.2.1.3"
+                                                              >TBD</A>
+     7.2.1.4 Extension header fields..........................<A
+                                                               HREF="#7.2.1.3"
+                                                              >TBD</A>
+    7.2.2 HTTP header fields..................................<A
+                                                               HREF="#7.2.2"
+                                                              >TBD</A>
+  8 Server Implementation.....................................<A
+                                                               HREF="#8.0"
+                                                              >TBD</A>
+   8.1 Requirements for Servers...............................<A
+                                                               HREF="#8.1"
+                                                              >TBD</A>
+    8.1.1 Script-URI..........................................<A
+                                                               HREF="#8.1"
+                                                              >TBD</A>
+    8.1.2 Request Message-body Handling.......................<A
+                                                               HREF="#8.1.2"
+                                                              >TBD</A>
+    8.1.3 Required Metavariables..............................<A
+                                                               HREF="#8.1.3"
+                                                              >TBD</A>
+    8.1.4 Response Compliance.................................<A
+                                                               HREF="#8.1.4"
+                                                              >TBD</A>
+   8.2 Recommendations for Servers............................<A
+                                                               HREF="#8.2"
+                                                              >TBD</A>
+   8.3 Summary of Metavariables...............................<A
+                                                               HREF="#8.3"
+                                                              >TBD</A>
+  9 Script Implementation.....................................<A
+                                                               HREF="#9.0"
+                                                              >TBD</A>
+   9.1 Requirements for Scripts...............................<A
+                                                               HREF="#9.1"
+                                                              >TBD</A>
+   9.2 Recommendations for Scripts............................<A
+                                                               HREF="#9.2"
+                                                              >TBD</A>
+  10 System Specifications....................................<A
+                                                               HREF="#10.0"
+                                                              >TBD</A>
+   10.1 AmigaDOS..............................................<A
+                                                               HREF="#10.1"
+                                                              >TBD</A>
+   10.2 Unix..................................................<A
+                                                               HREF="#10.2"
+                                                              >TBD</A>
+  11 Security Considerations..................................<A
+                                                               HREF="#11.0"
+                                                              >TBD</A>
+   11.1 Safe Methods..........................................<A
+                                                               HREF="#11.1"
+                                                              >TBD</A>
+   11.2 HTTP Header Fields Containing Sensitive Information...<A
+                                                               HREF="#11.2"
+                                                              >TBD</A>
+   11.3 Script Interference with the Server...................<A
+                                                               HREF="#11.3"
+                                                              >TBD</A>
+   11.4 Data Length and Buffering Considerations..............<A
+                                                               HREF="#11.4"
+                                                              >TBD</A>
+   11.5 Stateless Processing..................................<A
+                                                               HREF="#11.5"
+                                                              >TBD</A>
+  12 Acknowledgments..........................................<A
+                                                               HREF="#12.0"
+                                                              >TBD</A>
+  13 References...............................................<A
+                                                               HREF="#13.0"
+                                                              >TBD</A>
+  14 Authors' Addresses.......................................<A
+                                                               HREF="#14.0"
+                                                              >TBD</A>
+     </PRE>
+  </DIV>
+
+  <H2>
+   <A NAME="1.0">
+    1. Introduction
+   </A>
+  </H2>
+
+  <H3>
+   <A NAME="1.1">
+    1.1. Purpose
+   </A>
+  </H3>
+  <P>
+  Together the HTTP [<A HREF="#[3]">3</A>,<A HREF="#[8]">8</A>] server
+  and the CGI script are responsible
+  for servicing a client
+  request by sending back responses. The client
+  request comprises a Universal Resource Identifier (URI)
+  [<A HREF="#[1]">1</A>], a
+  request method, and various ancillary
+  information about the request
+  provided by the transport mechanism.
+  </P>
+  <P>
+  The CGI defines the abstract parameters, known as
+  metavariables,
+  which describe the client's
+  request. Together with a
+  concrete programmer interface this specifies a platform-independent
+  interface between the script and the HTTP server.
+  </P>
+
+  <H3>
+   <A NAME="1.2">
+    1.2. Requirements
+   </A>
+  </H3>
+  <P>
+  This specification uses the same words as RFC 1123
+  [<A HREF="#[5]">5</A>] to define the
+  significance of each particular requirement. These are:
+  </P><!--#if expr="! $GUI" -->
+  <P></P><!--#endif -->
+  <DL>
+   <DT><EM>MUST</EM>
+   </DT>
+   <DD>
+    <P>
+    This word or the adjective 'required' means that the item is an
+    absolute requirement of the specification.
+    </P>
+   </DD>
+   <DT><EM>SHOULD</EM>
+   </DT>
+   <DD>
+    <P>
+    This word or the adjective 'recommended' means that there may
+    exist valid reasons in particular circumstances to ignore this
+    item, but the full implications should be understood and the case
+    carefully weighed before choosing a different course.
+    </P>
+   </DD>
+   <DT><EM>MAY</EM>
+   </DT>
+   <DD>
+    <P>
+    This word or the adjective 'optional' means that this item is
+    truly optional. One vendor may choose to include the item because
+    a particular marketplace requires it or because it enhances the
+    product, for example; another vendor may omit the same item.
+    </P>
+   </DD>
+  </DL>
+  <P>
+  An implementation is not compliant if it fails to satisfy one or more
+  of the 'must' requirements for the protocols it implements. An
+  implementation that satisfies all of the 'must' and all of the
+  'should' requirements for its features is said to be 'unconditionally
+  compliant'; one that satisfies all of the 'must' requirements but not
+  all of the 'should' requirements for its features is said to be
+  'conditionally compliant.'
+  </P>
+
+  <H3>
+   <A NAME="1.3">
+    1.3. Specifications
+   </A>
+  </H3>
+  <P>
+  Not all of the functions and features of the CGI are defined in the
+  main part of this specification. The following phrases are used to
+  describe the features which are not specified:
+  </P>
+  <DL>
+   <DT><EM>system defined</EM>
+   </DT>
+   <DD>
+    <P>
+    The feature may differ between systems, but must be the same for
+    different implementations using the same system. A system will
+    usually identify a class of operating-systems. Some systems are
+    defined in
+    <A HREF="#10.0"
+    >section 10</A> of this document.
+    New systems may be defined
+    by new specifications without revision of this document.
+    </P>
+   </DD>
+   <DT><EM>implementation defined</EM>
+   </DT>
+   <DD>
+    <P>
+    The behaviour of the feature may vary from implementation to
+    implementation, but a particular implementation must document its
+    behaviour.
+    </P>
+   </DD>
+  </DL>
+
+  <H3>
+   <A NAME="1.4">
+    1.4. Terminology
+   </A>
+  </H3>
+  <P>
+  This specification uses many terms defined in the HTTP/1.1
+  specification [<A HREF="#[8]">8</A>]; however, the following terms are
+  used here in a
+  sense which may not accord with their definitions in that document,
+  or with their common meaning.
+  </P>
+
+  <DL>
+   <DT><EM>metavariable</EM>
+   </DT>
+   <DD>
+    <P>
+    A named parameter that carries information from the server to the
+    script. It is not necessarily a variable in the operating-system's
+    environment, although that is the most common implementation.
+    </P>
+   </DD>
+
+   <DT><EM>script</EM>
+   </DT>
+   <DD>
+    <P>
+    The software which is invoked by the server <EM>via</EM> this
+    interface. It
+    need not be a standalone program, but could be a
+    dynamically-loaded or shared library, or even a subroutine in the
+    server.  It <EM>may</EM> be a set of statements
+    interpreted at run-time, as the term 'script' is frequently
+    understood, but that is not a requirement and within the context
+    of this specification the term has the broader definition stated.
+    </P>
+   </DD>
+   <DT><EM>server</EM>
+   </DT>
+   <DD>
+    <P>
+    The application program which invokes the script in order to service
+    requests.
+    </P>
+   </DD>
+  </DL>
+
+  <H2>
+   <A NAME="2.0">
+    2. Notational Conventions and Generic Grammar
+   </A>
+  </H2>
+
+  <H3>
+   <A NAME="2.1">
+    2.1. Augmented BNF
+   </A>
+  </H3>
+  <P>
+  All of the mechanisms specified in this document are described in
+  both prose and an augmented Backus-Naur Form (BNF) similar to that
+  used by RFC 822 [<A HREF="#[6]">6</A>]. This augmented BNF contains
+  the following constructs:
+  </P>
+  <DL>
+   <DT>name = definition
+   </DT>
+   <DD>
+    <P>
+    The
+    definition by the equal character ("="). Whitespace is only
+    significant in that continuation lines of a definition are
+    indented.
+    </P>
+   </DD>
+   <DT>"literal"
+   </DT>
+   <DD>
+    <P>
+    Quotation marks (") surround literal text, except for a literal
+    quotation mark, which is surrounded by angle-brackets ("&lt;" and "&gt;").
+    Unless stated otherwise, the text is case-sensitive.
+    </P>
+   </DD>
+   <DT>rule1 | rule2
+   </DT>
+   <DD>
+    <P>
+    Alternative rules are separated by a vertical bar ("|").
+    </P>
+   </DD>
+   <DT>(rule1 rule2 rule3)
+   </DT>
+   <DD>
+    <P>
+    Elements enclosed in parentheses are treated as a single element.
+    </P>
+   </DD>
+   <DT>*rule
+   </DT>
+   <DD>
+    <P>
+    A rule preceded by an asterisk ("*") may have zero or more
+    occurrences. A rule preceded by an integer followed by an asterisk
+    must occur at least the specified number of times.
+    </P>
+   </DD>
+   <DT>[rule]
+   </DT>
+   <DD>
+    <P>
+    An element enclosed in square
+    brackets ("[" and "]") is optional.
+    </P>
+   </DD>
+  </DL>
+
+  <H3>
+   <A NAME="2.2">
+    2.2. Basic Rules
+   </A>
+  </H3>
+  <P>
+  The following rules are used throughout this specification to
+  describe basic parsing constructs.
+  </P><!--#if expr="! $GUI" -->
+  <P></P><!--#endif -->
+  <PRE>
+    alpha         = lowalpha | hialpha
+    alphanum      = alpha | digit
+    lowalpha      = "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"
+    hialpha       = "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"
+    digit         = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7"
+                    | "8" | "9"
+    hex           = digit | "A" | "B" | "C" | "D" | "E" | "F" | "a"
+                    | "b" | "c" | "d" | "e" | "f"
+    escaped       = "%" hex hex
+    OCTET         = &lt;any 8-bit sequence of data&gt;
+    CHAR          = &lt;any US-ASCII character (octets 0 - 127)&gt;
+    CTL           = &lt;any US-ASCII control character
+                    (octets 0 - 31) and DEL (127)&gt;
+    CR            = &lt;US-ASCII CR, carriage return (13)&gt;
+    LF            = &lt;US-ASCII LF, linefeed (10)&gt;
+    SP            = &lt;US-ASCII SP, space (32)&gt;
+    HT            = &lt;US-ASCII HT, horizontal tab (9)&gt;
+    NL            = CR | LF
+    LWSP          = SP | HT | NL
+    tspecial      = "(" | ")" | "@" | "," | ";" | ":" | "\" | &lt;"&gt;
+                    | "/" | "[" | "]" | "?" | "&lt;" | "&gt;" | "{" | "}"
+                    | SP | HT | NL
+    token         = 1*&lt;any CHAR except CTLs or tspecials&gt;
+    quoted-string = ( &lt;"&gt; *qdtext &lt;"&gt; ) | ( "&lt;" *qatext "&gt;")
+    qdtext        = &lt;any CHAR except &lt;"&gt; and CTLs but including LWSP&gt;
+    qatext        = &lt;any CHAR except "&lt;", "&gt;" and CTLs but
+                    including LWSP&gt;
+    mark          = "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")"
+    unreserved    = alphanum | mark
+    reserved      = ";" | "/" | "?" | ":" | "@" | "&amp;" | "=" |
+                    "$" | ","
+    uric          = reserved | unreserved | escaped
+  </PRE>
+  <P>
+  Note that newline (NL) need not be a single character, but can be a
+  character sequence.
+  </P>
+
+  <H2>
+   <A NAME="3.0">
+    3. Protocol Parameters
+   </A>
+  </H2>
+
+  <H3>
+   <A NAME="3.1">
+    3.1. URL Encoding
+   </A>
+  </H3>
+  <P>
+  Some variables and constructs used here are described as being
+  'URL-encoded'. This encoding is described in section
+  2 of RFC
+  2396
+  [<A HREF="#[4]">4</A>].
+  </P>
+  <P>
+  An alternate "shortcut" encoding for representing the space
+  character exists and is in common use.  Scripts MUST be prepared to
+  recognise both '+' and '%20' as an encoded space in a
+  URL-encoded value.
+  </P>
+  <P>
+  Note that some unsafe characters may have different semantics if
+  they are encoded. The definition of which characters are unsafe
+  depends on the context.
+  For example, the following two URLs do not
+  necessarily refer to the same resource:
+  </P><!--#if expr="! $GUI" -->
+  <P></P><!--#endif -->
+  <PRE>
+    http://somehost.com/somedir%2Fvalue
+    http://somehost.com/somedir/value
+  </PRE>
+  <P>
+  See section
+  2 of RFC
+  2396 [<A HREF="#[4]">4</A>]
+  for authoritative treatment of this issue.
+  </P>
+
+  <H3>
+   <A NAME="3.2">
+    3.2. The Script-URI
+   </A>
+  </H3>
+  <P>
+  The 'Script-URI' is defined as the URI of the resource identified
+  by the metavariables.   Often,
+  this URI will be the same as
+  the URI requested by the client (the 'Client-URI'); however, it need
+  not be. Instead, it could be a URI invented by the server, and so it
+  can only be used in the context of the server and its CGI interface.
+  </P>
+  <P>
+  The Script-URI has the syntax of generic-RL as defined in section 2.1
+  of RFC 1808 [<A HREF="#[7]">7</A>], with the exception that object
+  parameters and
+  fragment identifiers are not permitted:
+  </P><!--#if expr="! $GUI" -->
+  <P></P><!--#endif -->
+  <PRE>
+    &lt;scheme&gt;://&lt;host&gt;&lt;port&gt;/&lt;path&gt;?&lt;query&gt;
+  </PRE>
+  <P>
+  The various components of the
+  Script-URI
+  are defined by some of the
+  metavariables (see
+  <A HREF="#4.0">section 4</A>
+  below);
+  </P><!--#if expr="! $GUI" -->
+  <P></P><!--#endif -->
+  <PRE>
+    script-uri = protocol "://" SERVER_NAME ":" SERVER_PORT enc-script
+                 enc-path-info "?" QUERY_STRING
+  </PRE>
+  <P>
+  where 'protocol' is obtained
+  from SERVER_PROTOCOL, 'enc-script' is a
+  URL-encoded version of SCRIPT_NAME and 'enc-path-info' is a
+  URL-encoded version of PATH_INFO.  See
+  <A HREF="#4.6">section 4.6</A> for more information about the PATH_INFO
+  metavariable.
+  </P>
+  <P>
+  Note that the scheme and the protocol are <EM>not</EM> identical;
+  for instance, a resource accessed <EM>via</EM> an SSL mechanism
+  may have a Client-URI with a scheme of "<SAMP>https</SAMP>"
+  rather than "<SAMP>http</SAMP>".   CGI/1.1 provides no means
+  for the script to reconstruct this, and therefore
+  the Script-URI includes the base protocol used.
+  </P>
+
+  <H2>
+   <A NAME="4.0">
+    4. Invoking the Script
+   </A>
+  </H2>
+  <P>
+  The
+  script is invoked in a system defined manner. Unless specified
+  otherwise, the file containing the script will be invoked as an
+  executable program.
+  </P>
+
+  <H2>
+   <A NAME="5.0">
+    5. The CGI Script Command Line
+   </A>
+  </H2>
+  <P>
+  Some systems support a method for supplying an array of strings to
+  the CGI script. This is only used in the case of an 'indexed' query.
+  This is identified by a "GET" or "HEAD" HTTP request with a URL
+  query
+  string not containing any unencoded "=" characters. For such a
+  request,
+  servers SHOULD parse the search string
+  into words, using the following rules:
+  </P><!--#if expr="! $GUI" -->
+  <P></P><!--#endif -->
+  <PRE>
+    search-string = search-word *( "+" search-word )
+    search-word   = 1*schar
+    schar         = xunreserved | escaped | xreserved
+    xunreserved   = alpha | digit | xsafe | extra
+    xsafe         = "$" | "-" | "_" | "."
+    xreserved     = ";" | "/" | "?" | ":" | "@" | "&"
+  </PRE>
+  <P>
+  After parsing, each word is URL-decoded, optionally encoded in a
+  system defined manner,
+  and then the argument list is set to the list
+  of words.
+  </P>
+  <P>
+  If the server cannot create any part of the argument list, then the
+  server SHOULD NOT generate any command line information. For example, the
+  number of arguments may be greater than operating system or server
+  limitations permit, or one of the words may not be representable as an
+  argument.
+  </P>
+  <P>
+  Scripts SHOULD check to see if the QUERY_STRING value contains an
+  unencoded "=" character, and SHOULD NOT use the command line arguments
+  if it does.
+  </P>
+
+  <H2>
+   <A NAME="6.0">
+    6. Data Input to the CGI Script
+   </A>
+  </H2>
+  <P>
+  Information about a request comes from two different sources: the
+  request header, and any associated
+  message-body.
+  Servers MUST
+  make portions of this information available to
+   scripts.
+  </P>
+
+  <H3>
+   <A NAME="6.1">
+    6.1. Request Metadata
+    (Metavariables)
+   </A>
+  </H3>
+  <P>
+  Each CGI server
+  implementation MUST define a mechanism
+  to pass data about the request from
+  the server to the script.
+  The metavariables containing these
+  data
+  are accessed by the script in a system
+  defined manner.
+  The
+  representation of the characters in the
+  metavariables is
+  system defined.
+  </P>
+  <P>
+  This specification does not distinguish between the representation of
+  null values and missing ones.  Whether null or missing values
+  (such as a query component of "?" or "", respectively) are represented
+  by undefined metavariables or by metavariables with values of "" is
+  implementation-defined.
+  </P>
+  <P>
+  Case is not significant in the
+  metavariable
+  names, in that there cannot be two
+  different variables
+  whose names differ in case only. Here they are
+  shown using a canonical representation of capitals plus underscore
+  ("_"). The actual representation of the names is system defined; for
+  a particular system the representation MAY be defined differently
+  than this.
+  </P>
+  <P>
+  Metavariable
+  values MUST be
+  considered case-sensitive except as noted
+  otherwise.
+  </P>
+  <P>
+  The canonical
+  metavariables
+  defined by this specification are:
+  </P><!--#if expr="! $GUI" -->
+  <P></P><!--#endif -->
+  <PRE>
+    AUTH_TYPE
+    CONTENT_LENGTH
+    CONTENT_TYPE
+    GATEWAY_INTERFACE
+    PATH_INFO
+    PATH_TRANSLATED
+    QUERY_STRING
+    REMOTE_ADDR
+    REMOTE_HOST
+    REMOTE_IDENT
+    REMOTE_USER
+    REQUEST_METHOD
+    SCRIPT_NAME
+    SERVER_NAME
+    SERVER_PORT
+    SERVER_PROTOCOL
+    SERVER_SOFTWARE
+  </PRE>
+  <P>
+  Metavariables with names beginning with the protocol name (<EM>e.g.</EM>,
+  "HTTP_ACCEPT") are also canonical in their description of request header
+  fields.  The number and meaning of these fields may change independently
+  of this specification.  (See also <A HREF="#6.1.5">section 6.1.5</A>.)
+  </P>
+
+  <H4>
+   <A NAME="6.1.1">
+    6.1.1. AUTH_TYPE
+   </A>
+  </H4>
+  <P>
+  This variable is specific to requests made
+  <EM>via</EM> the
+  "<CODE>http</CODE>"
+  scheme.
+  </P>
+  <P>
+  If the Script-URI
+  required access authentication for external
+  access, then the server
+  MUST set
+  the value of
+  this variable
+  from the '<SAMP>auth-scheme</SAMP>' token in
+  the request's "<SAMP>Authorization</SAMP>" header
+  field.
+  Otherwise
+  it is
+  set to NULL.
+  </P><!--#if expr="! $GUI" -->
+  <P></P><!--#endif -->
+  <PRE>
+    AUTH_TYPE   = "" | auth-scheme
+    auth-scheme = "Basic" | "Digest" | token
+  </PRE>
+  <P>
+  HTTP access authentication schemes are described in section 11 of the
+  HTTP/1.1 specification [<A HREF="#[8]">8</A>]. The auth-scheme is
+  not case-sensitive.
+  </P>
+  <P>
+  Servers
+  MUST
+  provide this metavariable
+  to scripts if the request
+  header included an "<SAMP>Authorization</SAMP>" field
+  that was authenticated.
+  </P>
+
+  <H4>
+   <A NAME="6.1.2">
+    6.1.2. CONTENT_LENGTH
+   </A>
+  </H4>
+  <P>
+  This
+  metavariable
+  is set to the
+  size of the message-body
+  entity attached to the request, if any, in decimal
+  number of octets. If no data are attached, then this
+  metavariable
+  is either NULL or not
+  defined. The syntax is
+  the same as for
+  the HTTP "<SAMP>Content-Length</SAMP>" header field (section 14.14, HTTP/1.1
+  specification [<A HREF="#[8]">8</A>]).
+  </P><!--#if expr="! $GUI" -->
+  <P></P><!--#endif -->
+  <PRE>
+    CONTENT_LENGTH = "" | 1*digit
+  </PRE>
+  <P>
+  Servers MUST provide this metavariable
+  to scripts if the request
+  was accompanied by a
+  message-body entity.
+  </P>
+
+  <H4>
+   <A NAME="6.1.3">
+    6.1.3. CONTENT_TYPE
+   </A>
+  </H4>
+  <P>
+  If the request includes a
+  message-body,
+  CONTENT_TYPE is set
+  to
+  the Internet Media Type
+  [<A HREF="#[9]">9</A>] of the attached
+  entity if the type was provided <EM>via</EM>
+  a "<SAMP>Content-type</SAMP>" field in the
+  request header, or if the server can determine it in the absence
+  of a supplied "<SAMP>Content-type</SAMP>" field. The syntax is the
+  same as for the HTTP
+  "<SAMP>Content-Type</SAMP>" header field.
+  </P><!--#if expr="! $GUI" -->
+  <P></P><!--#endif -->
+  <PRE>
+    CONTENT_TYPE = "" | media-type
+    media-type   = type "/" subtype *( ";" parameter)
+    type         = token
+    subtype      = token
+    parameter    = attribute "=" value
+    attribute    = token
+    value        = token | quoted-string
+  </PRE>
+  <P>
+  The type, subtype,
+  and parameter attribute names are not
+  case-sensitive. Parameter values MAY be case sensitive.
+  Media types and their use in HTTP are described
+  in section 3.7 of the
+  HTTP/1.1 specification [<A HREF="#[8]">8</A>].
+  </P>
+  <P>
+  Example:
+  </P><!--#if expr="! $GUI" -->
+  <P></P><!--#endif -->
+  <PRE>
+    application/x-www-form-urlencoded
+  </PRE>
+  <P>
+  There is no default value for this variable. If and only if it is
+  unset, then the script MAY attempt to determine the media type from
+  the data received. If the type remains unknown, then
+  the script MAY choose to either assume a
+  content-type of
+  <SAMP>application/octet-stream</SAMP>
+  or reject the request with  a 415 ("Unsupported Media Type")
+  error.  See <A HREF="#7.2.1.3">section 7.2.1.3</A>
+  for more information about returning error status values.
+  </P>
+  <P>
+  Servers MUST provide this metavariable
+  to scripts if
+  a "<SAMP>Content-Type</SAMP>" field was present
+  in the original request header.  If the server receives a request
+  with an attached entity but no "<SAMP>Content-Type</SAMP>"
+  header field, it MAY attempt to
+  determine the correct datatype, or it MAY omit this
+  metavariable when
+  communicating the request information to the script.
+  </P>
+
+  <H4>
+   <A NAME="6.1.4">
+    6.1.4. GATEWAY_INTERFACE
+   </A>
+  </H4>
+  <P>
+  This
+  metavariable
+  is set to
+  the dialect of CGI being used
+  by the server to communicate with the script.
+  Syntax:
+  </P><!--#if expr="! $GUI" -->
+  <P></P><!--#endif -->
+  <PRE>
+    GATEWAY_INTERFACE = "CGI" "/" major "." minor
+    major             = 1*digit
+    minor             = 1*digit
+  </PRE>
+  <P>
+  Note that the major and minor numbers are treated as separate
+  integers and hence each may be
+  more than a single
+  digit. Thus CGI/2.4 is a lower version than CGI/2.13 which in turn
+  is lower than CGI/12.3. Leading zeros in either
+  the major or the minor number MUST be ignored by scripts and
+  SHOULD NOT be generated by servers.
+  </P>
+  <P>
+  This document defines the 1.1 version of the CGI interface
+  ("CGI/1.1").
+  </P>
+  <P>
+  Servers MUST provide this metavariable
+  to scripts.
+  </P>
+
+  <H4>
+   <A NAME="6.1.5">
+    6.1.5. Protocol-Specific Metavariables
+   </A>
+  </H4>
+  <P>
+  These metavariables are specific to
+  the protocol
+  <EM>via</EM> which the request is made.
+  Interpretation of these variables depends on the value of
+  the
+  SERVER_PROTOCOL
+  metavariable
+  (see
+  <A HREF="#6.1.17">section 6.1.17</A>).
+  </P>
+  <P>
+  Metavariables
+  with names beginning with "HTTP_" contain
+  values from the request header, if the
+  scheme used was HTTP.
+  Each
+  HTTP header field name is converted to upper case, has all occurrences of
+  "-" replaced with "_",
+  and has "HTTP_" prepended to  form
+  the metavariable name.
+  Similar transformations are applied for other
+  protocols.
+  The header data MAY be presented as sent
+  by the client, or MAY be rewritten in ways which do not change its
+  semantics. If multiple header fields with the same field-name are received
+  then  the server
+  MUST  rewrite them as though they
+  had been received as a single header field having the same
+  semantics before being represented in a
+  metavariable.
+  Similarly, a header field that is received on more than one line
+  MUST be merged into a single line. The server MUST, if necessary,
+  change the representation of the data (for example, the character
+  set) to be appropriate for a CGI
+  metavariable.
+  <!-- ###NOTE: See if 2068 describes this thoroughly, and
+  point there if so. -->
+  </P>
+  <P>
+  Servers are
+  not required to create
+  metavariables for all
+  the request
+  header fields that they
+  receive. In particular,
+  they MAY
+  decline to make available any
+  header fields carrying authentication information, such as
+  "<SAMP>Authorization</SAMP>", or
+  which are available to the script
+  <EM>via</EM> other metavariables,
+  such as "<SAMP>Content-Length</SAMP>" and "<SAMP>Content-Type</SAMP>".
+  </P>
+
+  <H4>
+   <A NAME="6.1.6">
+    6.1.6. PATH_INFO
+   </A>
+  </H4>
+  <P>
+  The PATH_INFO
+  metavariable
+  specifies
+  a path to be interpreted by the CGI script. It identifies the
+  resource or sub-resource to be returned
+  by the CGI
+  script, and it is derived from the portion
+  of the URI path following the script name but preceding
+  any query data.
+  The syntax
+  and semantics are similar to a decoded HTTP URL
+  'path' token
+  (defined in
+  RFC 2396
+  [<A HREF="#[4]">4</A>]), with the exception
+  that a PATH_INFO of "/"
+  represents a single void path segment.
+  </P><!--#if expr="! $GUI" -->
+  <P></P><!--#endif -->
+  <PRE>
+    PATH_INFO = "" | ( "/" path )
+    path      = segment *( "/" segment )
+    segment   = *pchar
+    pchar     = &lt;any CHAR except "/"&gt;
+  </PRE>
+  <P>
+  The PATH_INFO string is the trailing part of the &lt;path&gt; component of
+  the Script-URI
+  (see <A HREF="#3.2">section 3.2</A>)
+  that follows the SCRIPT_NAME
+  portion of the path.
+  </P>
+  <P>
+  Servers MAY impose their own restrictions and
+  limitations on what values they will accept for PATH_INFO, and MAY
+  reject or edit any values they
+  consider objectionable before passing
+  them to the script.
+  </P>
+  <P>
+  Servers MUST make this URI component available
+  to CGI scripts.  The PATH_INFO
+  value is case-sensitive, and the
+  server MUST preserve the case of the PATH_INFO element of the URI
+  when making it available to scripts.
+  </P>
+
+  <H4>
+   <A NAME="6.1.7">
+    6.1.7. PATH_TRANSLATED
+   </A>
+  </H4>
+  <P>
+  PATH_TRANSLATED is derived by taking any path-info component of the
+  request URI (see
+  <A HREF="#6.1.6">section 6.1.6</A>), decoding it
+  (see <A HREF="#3.1">section 3.1</A>), parsing it as a URI in its own
+  right, and performing any virtual-to-physical
+  translation appropriate to map it onto the
+  server's document repository structure.
+  If the request URI includes no path-info
+  component, the PATH_TRANSLATED metavariable SHOULD NOT be defined.
+  </P><!--#if expr="! $GUI" -->
+  <P></P><!--#endif -->
+  <PRE>
+    PATH_TRANSLATED = *CHAR
+  </PRE>
+  <P>
+  For a request such as the following:
+  </P><!--#if expr="! $GUI" -->
+  <P></P><!--#endif -->
+  <PRE>
+    http://somehost.com/cgi-bin/somescript/this%2eis%2epath%2einfo
+  </PRE>
+  <P>
+  the PATH_INFO component would be decoded, and the result
+  parsed as though it were a request for the following:
+  </P><!--#if expr="! $GUI" -->
+  <P></P><!--#endif -->
+  <PRE>
+    http://somehost.com/this.is.the.path.info
+  </PRE>
+  <P>
+  This would then be translated to a
+  location in the server's document repository,
+  perhaps a filesystem path something
+  like this:
+  </P><!--#if expr="! $GUI" -->
+  <P></P><!--#endif -->
+  <PRE>
+    /usr/local/www/htdocs/this.is.the.path.info
+  </PRE>
+  <P>
+  The result of the translation is the value of PATH_TRANSLATED.
+  </P>
+  <P>
+  The value of PATH_TRANSLATED may or may not map to a valid
+  repository
+  location.
+  Servers MUST preserve the case of the path-info
+  segment if and only if the underlying
+  repository
+  supports case-sensitive
+  names.  If the
+  repository
+  is only case-aware, case-preserving, or case-blind
+  with regard to
+  document names,
+  servers are not required to preserve the
+  case of the original segment through the translation.
+  </P>
+  <P>
+  The
+  translation
+  algorithm the server uses to derive PATH_TRANSLATED is
+  implementation defined; CGI scripts which use this variable may
+  suffer limited portability.
+  </P>
+  <P>
+  Servers SHOULD provide this metavariable
+  to scripts if and only if the request URI includes a
+  path-info component.
+  </P>
+
+  <H4>
+   <A NAME="6.1.8">
+    6.1.8. QUERY_STRING
+   </A>
+  </H4>
+  <P>
+  A URL-encoded
+  string; the &lt;query&gt; part of the
+  Script-URI.
+  (See
+  <A HREF="#3.2">section 3.2</A>.)
+  </P><!--#if expr="! $GUI" -->
+  <P></P><!--#endif -->
+  <PRE>
+    QUERY_STRING = query-string
+    query-string = *uric
+  </PRE>
+  <P>
+  The URL syntax for a  query
+  string is described in
+  section 3 of
+  RFC 2396
+  [<A HREF="#[4]">4</A>].
+  </P>
+  <P>
+  Servers MUST supply this value to scripts.
+  The QUERY_STRING value is case-sensitive.
+  If the Script-URI does not include a query component,
+  the QUERY_STRING metavariable MUST be defined as an empty string ("").
+  </P>
+
+  <H4>
+   <A NAME="6.1.9">
+    6.1.9. REMOTE_ADDR
+   </A>
+  </H4>
+  <P>
+  The IP address of the client
+  sending the request to the server. This
+  is not necessarily that of the user
+  agent
+  (such as if the request came through a proxy).
+  </P><!--#if expr="! $GUI" -->
+  <P></P><!--#endif -->
+  <PRE>
+    REMOTE_ADDR  = hostnumber
+    hostnumber   = ipv4-address | ipv6-address
+  </PRE>
+  <P>
+  The definitions of <SAMP>ipv4-address</SAMP> and <SAMP>ipv6-address</SAMP>
+  are provided in Appendix B of RFC 2373 [<A HREF="#[13]">13</A>].
+  </P>
+  <P>
+  Servers MUST supply this value to scripts.
+  </P>
+
+  <H4>
+   <A NAME="6.1.10">
+    6.1.10. REMOTE_HOST
+   </A>
+  </H4>
+  <P>
+  The fully qualified domain name of the
+  client sending the request to
+  the server, if available, otherwise NULL.
+  (See <A HREF="#6.1.9">section 6.1.9</A>.)
+  Fully qualified domain names take the form as described in
+  section 3.5 of RFC 1034 [<A HREF="#[10]">10</A>] and section 2.1 of
+  RFC 1123 [<A HREF="#[5]">5</A>].  Domain names are not case sensitive.
+  </P>
+  <P>
+  Servers SHOULD provide this information to
+  scripts.
+  </P>
+
+  <H4>
+   <A NAME="6.1.11">
+    6.1.11. REMOTE_IDENT
+   </A>
+  </H4>
+  <P>
+  The identity information reported about the connection by a
+  RFC 1413 [<A HREF="#[11]">11</A>] request to the remote agent, if
+  available. Servers
+  MAY choose not
+  to support this feature, or not to request the data
+  for efficiency reasons.
+  </P><!--#if expr="! $GUI" -->
+  <P></P><!--#endif -->
+  <PRE>
+    REMOTE_IDENT = *CHAR
+  </PRE>
+  <P>
+  The data returned
+  may be used for authentication purposes, but the level
+  of trust reposed in them should be minimal.
+  </P>
+  <P>
+  Servers MAY supply this information to scripts if the
+  RFC1413 [<A HREF="#[11]">11</A>] lookup is performed.
+  </P>
+
+  <H4>
+   <A NAME="6.1.12">
+    6.1.12. REMOTE_USER
+   </A>
+  </H4>
+  <P>
+  If the request required authentication using the "Basic"
+  mechanism (<EM>i.e.</EM>, the AUTH_TYPE
+  metavariable is set
+  to "Basic"), then the value of the REMOTE_USER
+  metavariable is set to the
+  user-ID supplied.  In all other cases
+  the value of this metavariable
+  is undefined.
+  </P><!--#if expr="! $GUI" -->
+  <P></P><!--#endif -->
+  <PRE>
+    REMOTE_USER = *OCTET
+  </PRE>
+  <P>
+  This variable is specific to requests made <EM>via</EM> the
+  HTTP protocol.
+  </P>
+  <P>
+  Servers SHOULD provide this metavariable
+  to scripts.
+  </P>
+
+  <H4>
+   <A NAME="6.1.13">
+    6.1.13. REQUEST_METHOD
+   </A>
+  </H4>
+  <P>
+  The REQUEST_METHOD
+  metavariable
+  is set to the
+  method with which the request was made, as described in section
+  5.1.1 of the HTTP/1.0 specification [<A HREF="#[3]">3</A>] and
+  section 5.1.1 of the
+  HTTP/1.1 specification [<A HREF="#[8]">8</A>].
+  </P><!--#if expr="! $GUI" -->
+  <P></P><!--#endif -->
+  <PRE>
+    REQUEST_METHOD   = http-method
+    http-method      = "GET" | "HEAD" | "POST" | "PUT" | "DELETE"
+                       | "OPTIONS" | "TRACE" | extension-method
+    extension-method = token
+  </PRE>
+  <P>
+  The method is case sensitive.
+  CGI/1.1 servers MAY choose to process some methods
+  directly rather than passing them to scripts.
+  </P>
+  <P>
+  This variable is specific to requests made with HTTP.
+  </P>
+  <P>
+  Servers MUST provide this metavariable
+  to scripts.
+  </P>
+
+  <H4>
+   <A NAME="6.1.14">
+    6.1.14. SCRIPT_NAME
+   </A>
+  </H4>
+  <P>
+  The SCRIPT_NAME
+  metavariable
+  is
+  set to a URL path that could identify the CGI script (rather than the
+  script's
+  output). The syntax and semantics are identical to a
+  decoded HTTP URL 'path' token
+  (see RFC 2396
+  [<A HREF="#[4]">4</A>]).
+  </P><!--#if expr="! $GUI" -->
+  <P></P><!--#endif -->
+  <PRE>
+    SCRIPT_NAME = "" | ( "/" [ path ] )
+  </PRE>
+  <P>
+  The SCRIPT_NAME string is some leading part of the &lt;path&gt; component
+  of the Script-URI derived in some
+  implementation defined manner.
+  No PATH_INFO or QUERY_STRING segments
+  (see sections <A HREF="#6.1.6">6.1.6</A> and
+  <A HREF="#6.1.8">6.1.8</A>) are included
+  in the SCRIPT_NAME value.
+  </P>
+  <P>
+  Servers MUST provide this metavariable
+  to scripts.
+  </P>
+
+  <H4>
+   <A NAME="6.1.15">
+    6.1.15. SERVER_NAME
+   </A>
+  </H4>
+  <P>
+  The SERVER_NAME
+  metavariable
+  is set to the
+  name  of the
+  server, as
+  derived from the &lt;host&gt; part of the
+  Script-URI
+  (see <A HREF="#3.2">section 3.2</A>).
+  </P><!--#if expr="! $GUI" -->
+  <P></P><!--#endif -->
+  <PRE>
+    SERVER_NAME = hostname | hostnumber
+  </PRE>
+  <P>
+  Servers MUST provide this metavariable
+  to scripts.
+  </P>
+
+  <H4>
+   <A NAME="6.1.16">
+    6.1.16. SERVER_PORT
+   </A>
+  </H4>
+  <P>
+  The SERVER_PORT
+  metavariable
+  is set to the
+  port on which the
+  request was received, as used in the &lt;port&gt;
+  part of the Script-URI.
+  </P><!--#if expr="! $GUI" -->
+  <P></P><!--#endif -->
+  <PRE>
+    SERVER_PORT = 1*digit
+  </PRE>
+  <P>
+  If the &lt;port&gt; portion of the script-URI is blank, the actual
+  port number upon which the request was received MUST be supplied.
+  </P>
+  <P>
+  Servers MUST provide this metavariable
+  to scripts.
+  </P>
+
+  <H4>
+   <A NAME="6.1.17">
+    6.1.17. SERVER_PROTOCOL
+   </A>
+  </H4>
+  <P>
+  The SERVER_PROTOCOL
+  metavariable
+  is set to
+  the
+  name and revision of the information protocol with which
+  the
+  request
+  arrived.  This is not necessarily the same as the protocol version used by
+  the server in its response to the client.
+  </P><!--#if expr="! $GUI" -->
+  <P></P><!--#endif -->
+  <PRE>
+    SERVER_PROTOCOL   = HTTP-Version | extension-version
+                        | extension-token
+    HTTP-Version      = "HTTP" "/" 1*digit "." 1*digit
+    extension-version = protocol "/" 1*digit "." 1*digit
+    protocol          = 1*( alpha | digit | "+" | "-" | "." )
+    extension-token   = token
+  </PRE>
+  <P>
+  'protocol' is a version of the &lt;scheme&gt; part of the
+  Script-URI, but is
+  not identical to it.  For example, the scheme of a request may be
+  "<SAMP>https</SAMP>" while the protocol remains "<SAMP>http</SAMP>".
+  The protocol is not case sensitive, but
+  by convention, 'protocol' is in
+  upper case.
+  </P>
+  <P>
+  A well-known extension token value is "INCLUDED",
+  which signals that the current document is being included as part of
+  a composite document, rather than being the direct target of the
+  client request.
+  </P>
+  <P>
+  Servers MUST provide this metavariable
+  to scripts.
+  </P>
+
+  <H4>
+   <A NAME="6.1.18">
+    6.1.18. SERVER_SOFTWARE
+   </A>
+  </H4>
+  <P>
+  The SERVER_SOFTWARE
+  metavariable
+  is set to the
+  name and version of the information server software answering the
+  request (and running the gateway).
+  </P><!--#if expr="! $GUI" -->
+  <P></P><!--#endif -->
+  <PRE>
+    SERVER_SOFTWARE = 1*product
+    product         = token [ "/" product-version ]
+    product-version = token
+  </PRE>
+  <P>
+  Servers MUST provide this metavariable
+  to scripts.
+  </P>
+
+  <H3>
+   <A NAME="6.2">
+    6.2. Request Message-Bodies
+   </A>
+  </H3>
+  <P>
+  As there may be a data entity attached to the request, there MUST be
+  a system defined method for the script to read
+  these data. Unless
+  defined otherwise, this will be <EM>via</EM> the 'standard input' file
+  descriptor.
+  </P>
+  <P>
+  If the CONTENT_LENGTH value (see <A HREF="#6.1.2">section 6.1.2</A>)
+  is non-NULL, the server MUST supply at least that many bytes to
+  scripts on the standard input stream.
+  Scripts are
+  not obliged to read the data.
+  Servers MAY signal an EOF condition after CONTENT_LENGTH bytes have been
+  read, but are
+  not obligated to do so.  Therefore, scripts
+  MUST NOT
+  attempt to read more than CONTENT_LENGTH bytes, even if more data
+  are available.
+  </P>
+  <P>
+  For non-parsed header (NPH) scripts (see
+  <A HREF="#7.1">section 7.1</A>
+  below),
+  servers SHOULD
+  attempt to ensure that the data
+  supplied to the script are precisely
+  as supplied by the client and unaltered by
+  the server.
+  </P>
+  <P>
+  <A HREF="#8.1.2">Section 8.1.2</A> describes the requirements of
+  servers with regard to requests that include
+  message-bodies.
+  </P>
+
+  <H2>
+   <A NAME="7.0">
+    7. Data Output from the CGI Script
+   </A>
+  </H2>
+  <P>
+  There MUST be a system defined method for the script to send data
+  back to the server or client; a script MUST always return some data.
+  Unless defined otherwise, this will be <EM>via</EM> the 'standard
+  output' file descriptor.
+  </P>
+  <P>
+  There are two forms of output that  scripts can supply to servers: non-parsed
+  header (NPH) output, and parsed header output.
+  Servers MUST support parsed header
+  output and MAY support NPH output.  The method of
+  distinguishing between the two
+  types of output (or scripts) is implementation defined.
+  </P>
+  <P>
+  Servers MAY implement a timeout period within which data must be
+  received from scripts.  If a server implementation defines such
+  a timeout and receives no data from a script within the timeout
+  period, the server MAY terminate the script process and SHOULD
+  abort the client request with
+  either a
+  '504 Gateway Timed Out' or a
+  '500 Internal Server Error' response.
+  </P>
+
+  <H3>
+   <A NAME="7.1">
+    7.1. Non-Parsed Header Output
+   </A>
+  </H3>
+  <P>
+  Scripts using the NPH output form
+  MUST return a complete HTTP response message, as described
+  in Section 6 of the HTTP specifications
+  [<A HREF="#[3]">3</A>,<A HREF="#[8]">8</A>].
+   NPH scripts
+  MUST use the SERVER_PROTOCOL variable to determine the appropriate format
+  for a response.
+  </P>
+  <P>
+  Servers
+  SHOULD attempt to ensure that the script output is sent
+  directly to the client, with minimal
+  internal and no transport-visible
+  buffering.
+  </P>
+
+  <H3>
+   <A NAME="7.2">
+    7.2. Parsed Header Output
+   </A>
+  </H3>
+  <P>
+  Scripts using the parsed header output form MUST supply
+  a CGI response message to the server
+  as follows:
+  </P><!--#if expr="! $GUI" -->
+  <P></P><!--#endif -->
+  <PRE>
+    CGI-Response   = *optional-field CGI-Field *optional-field NL [ Message-Body ]
+    optional-field = ( CGI-Field | HTTP-Field )
+    CGI-Field      = Content-type
+                   | Location
+                   | Status
+                   | extension-header
+  </PRE>
+  <P><!-- ##### If HTTP defines x-headers, remove ours except x-cgi- -->
+  The response comprises a header and a body, separated by a blank line.
+  The body may be NULL.
+  The header fields are either CGI header fields to be interpreted by
+  the server, or HTTP header fields
+  to be included in the response returned
+  to the client
+  if the request method is HTTP. At least one
+  CGI-Field MUST be
+  supplied, but no CGI  field name may be used more than once
+  in a response.
+  If a body is supplied, then a "<SAMP>Content-type</SAMP>"
+  header field MUST be
+  supplied by the script,
+  otherwise the script MUST send a "<SAMP>Location</SAMP>"
+  or "<SAMP>Status</SAMP>" header field. If a
+  <SAMP>Location</SAMP> CGI-Field
+  is returned, then the script MUST NOT supply
+  any HTTP-Fields.
+  </P>
+  <P>
+  Each header field in a CGI-Response MUST be specified on a single line;
+  CGI/1.1 does not support continuation lines.
+  </P>
+
+  <H4>
+   <A NAME="7.2.1">
+    7.2.1. CGI header fields
+   </A>
+  </H4>
+  <P>
+  The CGI header fields have the generic syntax:
+  </P><!--#if expr="! $GUI" -->
+  <P></P><!--#endif -->
+  <PRE>
+    generic-field  = field-name ":" [ field-value ] NL
+    field-name     = token
+    field-value    = *( field-content | LWSP )
+    field-content  = *( token | tspecial | quoted-string )
+  </PRE>
+  <P>
+  The field-name is not case sensitive; a NULL field value is
+  equivalent to the header field not being sent.
+  </P>
+
+  <H4>
+   <A NAME="7.2.1.1">
+    7.2.1.1. Content-Type
+   </A>
+  </H4>
+  <P>
+  The Internet Media Type [<A HREF="#[9]">9</A>] of the entity
+  body, which is to be sent unmodified to the client.
+  </P><!--#if expr="! $GUI" -->
+  <P></P><!--#endif -->
+  <PRE>
+    Content-Type = "Content-Type" ":" media-type NL
+  </PRE>
+  <P>
+  This is actually an HTTP-Field
+  rather than a CGI-Field, but
+  it is listed here because of its importance in the CGI dialogue as
+  a member of the "one of these is required" set of header
+  fields.
+  </P>
+
+  <H4>
+   <A NAME="7.2.1.2">
+    7.2.1.2. Location
+   </A>
+  </H4>
+  <P>
+  This is used to specify to the server that the script is returning a
+  reference to a document rather than an actual document.
+  </P><!--#if expr="! $GUI" -->
+  <P></P><!--#endif -->
+  <PRE>
+    Location         = "Location" ":"
+                       ( fragment-URI | rel-URL-abs-path ) NL
+    fragment-URI     = URI [ # fragmentid ]
+    URI              = scheme ":" *qchar
+    fragmentid       = *qchar
+    rel-URL-abs-path = "/" [ hpath ] [ "?" query-string ]
+    hpath            = fpsegment *( "/" psegment )
+    fpsegment        = 1*hchar
+    psegment         = *hchar
+    hchar            = alpha | digit | safe | extra
+                       | ":" | "@" | "& | "="
+  </PRE>
+  <P>
+  The Location
+  value is either an absolute URI with optional fragment,
+  as defined in RFC 1630 [<A HREF="#[1]">1</A>], or an absolute path
+  within the server's URI space (<EM>i.e.</EM>,
+  omitting the scheme and network-related fields) and optional
+  query-string. If an absolute URI is returned by the script,
+  then the
+  server MUST generate a
+  '302 redirect' HTTP response
+  message unless the script has supplied an
+  explicit Status response header field.
+  Scripts returning an absolute URI MAY choose to
+  provide a message-body.  Servers MUST make any appropriate modifications
+  to the script's output to ensure the response to the user-agent complies
+  with the response protocol version.
+  If the Location value is a path, then the server
+  MUST generate
+  the response that it would have produced in response to a request
+  containing the URL
+  </P><!--#if expr="! $GUI" -->
+  <P></P><!--#endif -->
+  <PRE>
+    scheme "://" SERVER_NAME ":" SERVER_PORT rel-URL-abs-path
+  </PRE>
+  <P>
+  Note: If the request was accompanied by a
+  message-body
+  (such as for a POST request), and the script
+  redirects the request with a Location field, the
+  message-body
+  may not be
+  available to the resource that is the target of the redirect.
+  </P>
+
+  <H4>
+   <A NAME="7.2.1.3">
+    7.2.1.3. Status
+   </A>
+  </H4>
+  <P>
+  The "<SAMP>Status</SAMP>" header field is used to indicate to the server what
+  status code the server MUST use in the response message.
+  </P><!--#if expr="! $GUI" -->
+  <P></P><!--#endif -->
+  <PRE>
+    Status        = "Status" ":" digit digit digit SP reason-phrase NL
+    reason-phrase = *&lt;CHAR, excluding CTLs, NL&gt;
+  </PRE>
+  <P>
+  The valid status codes are listed in section 6.1.1 of the HTTP/1.0
+  specifications [<A HREF="#[3]">3</A>]. If the SERVER_PROTOCOL is
+  "HTTP/1.1", then the status codes defined in the HTTP/1.1
+  specification [<A HREF="#[8]">8</A>] may
+  be used. If the script does not return a "<SAMP>Status</SAMP>" header
+  field, then "200 OK" SHOULD be assumed by the server.
+  </P>
+  <P>
+  If a script is being used to handle a particular error or condition
+  encountered by the server, such as a '404 Not Found' error, the script
+  SHOULD use the "<SAMP>Status</SAMP>" CGI header field to propagate the error
+  condition back to the client.  <EM>E.g.</EM>, in the example mentioned it
+  SHOULD include a "Status:&nbsp;404&nbsp;Not&nbsp;Found" in the
+  header data returned to the server.
+  </P>
+
+  <H4>
+   <A NAME="7.2.1.4">
+    7.2.1.4. Extension header fields
+   </A>
+  </H4>
+  <P>
+  Scripts MAY include in their CGI response header additional fields
+  not defined in this or the HTTP specification.
+  These are called "extension" fields,
+  and have the syntax of a <SAMP>generic-field</SAMP> as defined in
+  <A HREF="#7.2.1">section 7.2.1</A>.  The name of an extension field
+  MUST NOT conflict with a field name defined in this or any other
+  specification; extension field names SHOULD begin with "X-CGI-"
+  to ensure uniqueness.
+  </P>
+
+  <H4>
+   <A NAME="7.2.2">
+    7.2.2. HTTP header fields
+   </A>
+  </H4>
+  <P>
+  The script MAY return any other header fields defined by the
+  specification
+  for the SERVER_PROTOCOL (HTTP/1.0 [<A HREF="#[3]">3</A>] or HTTP/1.1
+  [<A HREF="#[8]">8</A>]).
+  Servers MUST resolve conflicts beteen CGI header
+  and HTTP header formats or names (see <A HREF="#8.0">section 8</A>).
+  </P>
+
+  <H2>
+   <A NAME="8.0">
+    8. Server Implementation
+   </A>
+  </H2>
+  <P>
+  This section defines the requirements that must be met by HTTP
+  servers in order to provide a coherent and correct CGI/1.1
+  environment in which scripts may function.  It is intended
+  primarily for server implementors, but it is useful for
+  script authors to be familiar with the information as well.
+  </P>
+
+  <H3>
+   <A NAME="8.1">
+    8.1. Requirements for Servers
+   </A>
+  </H3>
+  <P>
+  In order to be considered CGI/1.1-compliant, a server must meet
+  certain basic criteria and provide certain minimal functionality.
+  The details of these requirements are described in the following sections.
+  </P>
+
+  <H3>
+   <A NAME="8.1.1">
+    8.1.1. Script-URI
+   </A>
+  </H3>
+  <P>
+  Servers MUST support the standard mechanism (described below) which
+  allows
+  script authors to determine
+  what URL to use in documents
+  which reference the script;
+  specifically, what URL to use in order to
+  achieve particular settings of the
+  metavariables. This
+  mechanism is as follows:
+  </P>
+  <P>
+  The server
+  MUST translate the header data from the CGI header field syntax to
+  the HTTP
+  header field syntax if these differ. For example, the character
+  sequence for
+  newline (such as Unix's ASCII NL) used by CGI scripts may not be the
+  same as that used by HTTP (ASCII CR followed by LF). The server MUST
+  also resolve any conflicts between header fields returned by the script
+  and header fields that it would otherwise send itself.
+  </P>
+
+  <H3>
+   <A NAME="8.1.2">
+    8.1.2. Request Message-body Handling
+   </A>
+  </H3>
+  <P>
+  These are the requirements for server handling of message-bodies directed
+  to CGI/1.1 resources:
+  </P>
+  <OL>
+   <LI>The message-body the server provides to the CGI script MUST
+    have any transfer encodings removed.
+   </LI>
+   <LI>The server MUST derive and provide a value for the CONTENT_LENGTH
+    metavariable that reflects the length of the message-body after any
+    transfer decoding.
+   </LI>
+   <LI>The server MUST leave intact any content-encodings of the message-body.
+   </LI>
+  </OL>
+
+  <H3>
+   <A NAME="8.1.3">
+    8.1.3. Required Metavariables
+   </A>
+  </H3>
+  <P>
+  Servers MUST provide scripts with certain information and
+  metavariables
+  as described in <A HREF="#8.3">section 8.3</A>.
+  </P>
+
+  <H3>
+   <A NAME="8.1.4">
+    8.1.4. Response Compliance
+   </A>
+  </H3>
+  <P>
+  Servers MUST ensure that responses sent to the user-agent meet all
+  requirements of the protocol level in effect.  This may involve
+  modifying, deleting, or augmenting any header
+  fields and/or message-body supplied by the script.
+  </P>
+
+  <H3>
+   <A NAME="8.2">
+    8.2. Recommendations for Servers
+   </A>
+  </H3>
+  <P>
+  Servers SHOULD provide the "<SAMP>query</SAMP>" component of the script-URI
+  as command-line arguments to scripts if it does not
+  contain any unencoded '=' characters and the command-line arguments can
+  be generated in an unambiguous manner.
+  (See <A HREF="#5.0">section 5</A>.)
+  </P>
+  <P>
+  Servers SHOULD set the AUTH_TYPE
+  metavariable to the value of the
+  '<SAMP>auth-scheme</SAMP>' token of the "<SAMP>Authorization</SAMP>"
+  field if it was supplied as part of the request header.
+  (See <A HREF="#6.1.1">section 6.1.1</A>.)
+  </P>
+  <P>
+  Where applicable, servers SHOULD set the current working directory
+  to the directory in which the script is located before invoking
+  it.
+  </P>
+  <P>
+  Servers MAY reject with error '404 Not Found'
+  any requests that would result in
+  an encoded "/" being decoded into PATH_INFO or SCRIPT_NAME, as this
+  might represent a loss of information to the script.
+  </P>
+  <P>
+  Although the server and the CGI script need not be consistent in
+  their handling of URL paths (client URLs and the PATH_INFO data,
+  respectively), server authors may wish to impose consistency.
+  So the server implementation SHOULD define its behaviour for the
+  following cases:
+  </P>
+  <OL>
+   <LI>define any restrictions on allowed characters, in particular
+    whether ASCII NUL is permitted;
+   </LI>
+   <LI>define any restrictions on allowed path segments, in particular
+    whether non-terminal NULL segments are permitted;
+   </LI>
+   <LI>define the behaviour for <SAMP>"."</SAMP> or <SAMP>".."</SAMP> path
+    segments; <EM>i.e.</EM>, whether they are prohibited, treated as
+    ordinary path
+    segments or interpreted in accordance with the relative URL
+    specification [<A HREF="#[7]">7</A>];
+   </LI>
+   <LI>define any limits of the implementation, including limits on path or
+    search string lengths, and limits on the volume of header data the server
+    will parse.
+   </LI><!-- ##### Move the field resolution/translation para below here -->
+  </OL>
+  <P>
+  Servers MAY generate the
+  Script-URI in
+  any way from the client URI,
+  or from any other data (but the behaviour SHOULD be documented).
+  </P>
+  <P>
+  For non-parsed header (NPH) scripts (see
+  <A HREF="#7.1">section 7.1</A>), servers SHOULD
+  attempt to ensure that the script input comes directly from the
+  client, with minimal buffering. For all scripts the data will be
+  as supplied by the client.
+  </P>
+
+  <H3>
+   <A NAME="8.3">
+    8.3. Summary of
+    MetaVariables
+   </A>
+  </H3>
+  <P>
+  Servers MUST provide the following
+  metavariables to
+  scripts.  See the individual descriptions for exceptions and semantics.
+  </P><!--#if expr="! $GUI" -->
+  <P></P><!--#endif -->
+  <PRE>
+    CONTENT_LENGTH (section <A HREF="#6.1.2">6.1.2</A>)
+    CONTENT_TYPE (section <A HREF="#6.1.3">6.1.3</A>)
+    GATEWAY_INTERFACE (section <A HREF="#6.1.4">6.1.4</A>)
+    PATH_INFO (section <A HREF="#6.1.6">6.1.6</A>)
+    QUERY_STRING (section <A HREF="#6.1.8">6.1.8</A>)
+    REMOTE_ADDR (section <A HREF="#6.1.9">6.1.9</A>)
+    REQUEST_METHOD (section <A HREF="#6.1.13">6.1.13</A>)
+    SCRIPT_NAME (section <A HREF="#6.1.14">6.1.14</A>)
+    SERVER_NAME (section <A HREF="#6.1.15">6.1.15</A>)
+    SERVER_PORT (section <A HREF="#6.1.16">6.1.16</A>)
+    SERVER_PROTOCOL (section <A HREF="#6.1.17">6.1.17</A>)
+    SERVER_SOFTWARE (section <A HREF="#6.1.18">6.1.18</A>)
+  </PRE>
+  <P>
+  Servers SHOULD define the following
+  metavariables for scripts.
+  See the individual descriptions for exceptions and semantics.
+  </P><!--#if expr="! $GUI" -->
+  <P></P><!--#endif -->
+  <PRE>
+    AUTH_TYPE (section <A HREF="#6.1.1">6.1.1</A>)
+    REMOTE_HOST (section <A HREF="#6.1.10">6.1.10</A>)
+  </PRE>
+  <P>
+  In addition, servers SHOULD provide
+  metavariables for all fields present
+  in the HTTP request header, with the exception of those involved with
+  access control.  Servers MAY at their discretion provide
+  metavariables
+  for access control fields.
+  </P>
+  <P>
+  Servers MAY define the following
+  metavariables.  See the individual
+  descriptions for exceptions and semantics.
+  </P><!--#if expr="! $GUI" -->
+  <P></P><!--#endif -->
+  <PRE>
+    PATH_TRANSLATED (section <A HREF="#6.1.7">6.1.7</A>)
+    REMOTE_IDENT (section <A HREF="#6.1.11">6.1.11</A>)
+    REMOTE_USER (section <A HREF="#6.1.12">6.1.12</A>)
+  </PRE>
+  <P>
+  Servers MAY
+  at their discretion define additional implementation-specific
+  extension metavariables
+  provided their names do not
+  conflict with defined header field names.  Implementation-specific
+  metavariable names SHOULD
+  be prefixed with "X_" (<EM>e.g.</EM>,
+  "X_DBA") to avoid the potential for such conflicts.
+  </P>
+
+  <H2>
+   <A NAME="9.0">
+    9.
+    Script Implementation
+   </A>
+  </H2>
+  <P>
+  This section defines the requirements and recommendations for scripts
+  that are intended to function in a CGI/1.1 environment.  It is intended
+  primarily as a reference for script authors, but server implementors
+  should be familiar with these issues as well.
+  </P>
+
+  <H3>
+   <A NAME="9.1">
+    9.1. Requirements for Scripts
+   </A>
+  </H3>
+  <P>
+  Scripts using the parsed-header method to communicate with servers
+  MUST supply a response header to the server.
+  (See <A HREF="#7.0">section 7</A>.)
+  </P>
+  <P>
+  Scripts using the NPH method to communicate with servers MUST
+  provide complete HTTP responses, and MUST use the value of the
+  SERVER_PROTOCOL metavariable
+  to determine the appropriate format.
+  (See <A HREF="#7.1">section 7.1</A>.)
+  </P>
+  <P>
+  Scripts MUST check the value of the REQUEST_METHOD
+  metavariable in order
+  to provide an appropriate response.
+  (See <A HREF="#6.1.13">section 6.1.13</A>.)
+  </P>
+  <P>
+  Scripts MUST be prepared to handled URL-encoded values in
+  metavariables.
+  In addition, they MUST recognise both "+" and "%20" in URL-encoded
+  quantities as representing the space character.
+  (See <A HREF="#3.1">section 3.1</A>.)
+  </P>
+  <P>
+  Scripts MUST ignore leading zeros in the major and minor version numbers
+  in the GATEWAY_INTERFACE
+  metavariable value. (See
+  <A HREF="#6.1.4">section 6.1.4</A>.)
+  </P>
+  <P>
+  When processing requests that include a
+  message-body, scripts
+  MUST NOT read more than CONTENT_LENGTH bytes from the input stream.
+  (See sections <A HREF="#6.1.2">6.1.2</A> and <A HREF="#6.2">6.2</A>.)
+  </P>
+
+  <H3>
+   <A NAME="9.2">
+    9.2. Recommendations for Scripts
+   </A>
+  </H3>
+  <P>
+  Servers may interrupt or terminate script execution at any time
+  and without warning, so scripts SHOULD be prepared to deal with
+  abnormal termination.
+  </P>
+  <P>
+  Scripts MUST
+  reject with
+  error '405 Method Not
+  Allowed' requests
+  made using methods that they do not support. If the script does
+  not intend
+  processing the PATH_INFO data, then it SHOULD reject the request with
+  '404 Not
+  Found' if PATH_INFO is not NULL.
+  </P>
+  <P>
+  If a script is processing the output of a form, it SHOULD
+  verify that the CONTENT_TYPE
+  is "<SAMP>application/x-www-form-urlencoded</SAMP>" [<A HREF="#[2]">2</A>]
+  or whatever other media type is expected.
+  </P>
+  <P>
+  Scripts parsing PATH_INFO,
+  PATH_TRANSLATED, or SCRIPT_NAME
+  SHOULD be careful
+  of void path segments ("<SAMP>//</SAMP>") and special path segments
+  (<SAMP>"."</SAMP> and
+  <SAMP>".."</SAMP>). They SHOULD either be removed from the path before
+  use in OS
+  system calls, or the request SHOULD be rejected with
+  '404 Not Found'.
+  </P>
+  <P>
+  As it is impossible for
+  scripts to determine the client URI that
+  initiated  a
+  request without knowledge of the specific server in
+  use, the script SHOULD NOT return "<SAMP>text/html</SAMP>"
+  documents containing
+  relative URL links without including a "<SAMP>&lt;BASE&gt;</SAMP>"
+  tag in the document.
+  </P>
+  <P>
+  When returning header fields,
+  scripts SHOULD try to send the CGI
+  header fields (see section
+  <A HREF="#7.2">7.2</A>) as soon as possible, and
+  SHOULD send them
+  before any HTTP header fields. This may
+  help reduce the server's memory requirements.
+  </P>
+
+  <H2>
+   <A NAME="10.0">
+    10. System Specifications
+   </A>
+  </H2>
+
+  <H3>
+   <A NAME="10.1">
+    10.1. AmigaDOS
+   </A>
+  </H3>
+  <P>
+  The implementation of the CGI on an AmigaDOS operating system platform
+  SHOULD use environment variables as the mechanism of providing
+  request metadata to CGI scripts.
+  </P>
+  <DL>
+   <DT><STRONG>Environment variables</STRONG>
+   </DT>
+   <DD>
+    <P>
+    These are accessed by the DOS library routine <SAMP>GetVar</SAMP>. The
+    flags argument SHOULD be 0. Case is ignored, but upper case is
+    recommended for compatibility with case-sensitive systems.
+    </P>
+   </DD>
+   <DT><STRONG>The current working directory</STRONG>
+   </DT>
+   <DD>
+    <P>
+    The current working directory for the script is set to the directory
+    containing the script.
+    </P>
+   </DD>
+   <DT><STRONG>Character set</STRONG>
+   </DT>
+   <DD>
+    <P>
+    The US-ASCII character set is used for the definition of environment
+    variable names and header
+    field names; the newline (NL) sequence is LF;
+    servers SHOULD also accept CR LF as a newline.
+    </P>
+   </DD>
+  </DL>
+
+  <H3>
+   <A NAME="10.2">
+    10.2. Unix
+   </A>
+  </H3>
+  <P>
+  The implementation of the CGI on a UNIX operating system platform
+  SHOULD use environment variables as the mechanism of providing
+  request metadata to CGI scripts.
+  </P>
+  <P>
+  For Unix compatible operating systems, the following are defined:
+  </P>
+  <DL>
+   <DT><STRONG>Environment variables</STRONG>
+   </DT>
+   <DD>
+    <P>
+    These are accessed by the C library routine <SAMP>getenv</SAMP>.
+    </P>
+   </DD>
+   <DT><STRONG>The command line</STRONG>
+   </DT>
+   <DD>
+    <P>
+    This is accessed using the
+    <SAMP>argc</SAMP> and <SAMP>argv</SAMP>
+    arguments to <SAMP>main()</SAMP>. The words have any characters
+    that
+    are 'active' in the Bourne shell escaped with a backslash.
+    If the value of the QUERY_STRING
+    metavariable
+    contains an unencoded equals-sign '=', then the command line
+    SHOULD NOT be used by the script.
+    </P>
+   </DD>
+   <DT><STRONG>The current working directory</STRONG>
+   </DT>
+   <DD>
+    <P>
+    The current working directory for the script
+    SHOULD be set to the directory
+    containing the script.
+    </P>
+   </DD>
+   <DT><STRONG>Character set</STRONG>
+   </DT>
+   <DD>
+    <P>
+    The US-ASCII character set is used for the definition of environment
+    variable names and header field names; the newline (NL) sequence is LF;
+    servers SHOULD also accept CR LF as a newline.
+    </P>
+   </DD>
+  </DL>
+
+  <H2>
+   <A NAME="11.0">
+    11. Security Considerations
+   </A>
+  </H2>
+
+  <H3>
+   <A NAME="11.1">
+    11.1. Safe Methods
+   </A>
+  </H3>
+  <P>
+  As discussed in the security considerations of the HTTP
+  specifications [<A HREF="#[3]">3</A>,<A HREF="#[8]">8</A>], the
+  convention has been established that the
+  GET and HEAD methods should be 'safe'; they should cause no
+  side-effects and only have the significance of resource retrieval.
+  </P>
+  <P>
+  CGI scripts are responsible for enforcing any HTTP security considerations
+  [<A HREF="#[3]">3</A>,<A HREF="#[8]">8</A>]
+  with respect to the protocol version level of the request and
+  any side effects generated by the scripts on behalf of
+  the server.  Primary
+  among these
+  are the considerations of safe and idempotent methods.  Idempotent
+  requests are those that may be repeated an arbitrary number of times
+  and produce side effects identical to a single request.
+  </P>
+
+  <H3>
+   <A NAME="11.2">
+    11.2. HTTP Header
+    Fields Containing Sensitive Information
+   </A>
+  </H3>
+  <P>
+  Some HTTP header fields may carry sensitive information which the server
+  SHOULD NOT pass on to the script unless explicitly configured to do
+  so. For example, if the server protects the script using the
+  "<SAMP>Basic</SAMP>"
+  authentication scheme, then the client will send an
+  "<SAMP>Authorization</SAMP>"
+  header field containing a username and password. If the server, rather
+  than the script, validates this information then the password SHOULD
+  NOT be passed on to the script <EM>via</EM> the HTTP_AUTHORIZATION
+  metavariable
+  without careful consideration.
+  This also applies to the
+  Proxy-Authorization header field and the corresponding
+  HTTP_PROXY_AUTHORIZATION
+  metavariable.
+  </P>
+
+  <H3>
+   <A NAME="11.3">
+    11.3. Script
+    Interference with the Server
+   </A>
+  </H3>
+  <P>
+  The most common implementation of CGI invokes the script as a child
+  process using the same user and group as the server process. It
+  SHOULD therefore be ensured that the script cannot interfere with the
+  server process, its configuration, or documents.
+  </P>
+  <P>
+  If the script is executed by calling a function linked in to the
+  server software (either at compile-time or run-time) then precautions
+  SHOULD be taken to protect the core memory of the server, or to
+  ensure that untrusted code cannot be executed.
+  </P>
+
+  <H3>
+   <A NAME="11.4">
+    11.4. Data Length and Buffering Considerations
+   </A>
+  </H3>
+  <P>
+  This specification places no limits on the length of message-bodies
+  presented to the script.  Scripts should not assume that statically
+  allocated buffers of any size are sufficient to contain the entire
+  submission at one time.  Use of a fixed length buffer without careful
+  overflow checking may result in an attacker exploiting 'stack-smashing'
+  or 'stack-overflow' vulnerabilities of the operating system.
+  Scripts may spool large submissions to disk or other buffering media,
+  but a rapid succession of large submissions may result in denial of
+  service conditions.  If the CONTENT_LENGTH of a message-body is larger
+  than resource considerations allow, scripts should respond with an
+  error status appropriate for the protocol version; potentially applicable
+  status codes include '503 Service Unavailable' (HTTP/1.0 and HTTP/1.1),
+  '413 Request Entity Too Large' (HTTP/1.1), and
+  '414 Request-URI Too Long' (HTTP/1.1).
+  </P>
+
+  <H3>
+   <A NAME="11.5">
+    11.5. Stateless Processing
+   </A>
+  </H3>
+  <P>
+  The stateless nature of the Web makes each script execution and resource
+  retrieval independent of all others even when multiple requests constitute a
+  single conceptual Web transaction.  Because of this, a script should not
+  make any assumptions about the context of the user-agent submitting a
+  request.  In particular, scripts should examine data obtained from the client
+  and verify that they are valid, both in form and content, before allowing
+  them to be used for sensitive purposes such as input to other
+  applications, commands, or operating system services.  These uses
+  include, but are not
+  limited to: system call arguments, database writes, dynamically evaluated
+  source code, and input to billing or other secure processes.  It is important
+  that applications be protected from invalid input regardless of whether
+  the invalidity is the result of user error, logic error, or malicious action.
+  </P>
+  <P>
+  Authors of scripts involved in multi-request transactions should be
+  particularly cautios about validating the state information;
+  undesirable effects may result from the substitution of dangerous
+  values for portions of the submission which might otherwise be
+  presumed safe.  Subversion of this type occurs when alterations
+  are made to data from a prior stage of the transaction that were
+  not meant to be controlled by the client (<EM>e.g.</EM>, hidden
+  HTML form elements, cookies, embedded URLs, <EM>etc.</EM>).
+  </P>
+
+  <H2>
+   <A NAME="12.0">
+    12. Acknowledgements
+   </A>
+  </H2>
+  <P>
+  This work is based on a draft published in 1997 by David R. Robinson,
+  which in turn was based on the original CGI interface that arose out of
+  discussions on the <EM>www-talk</EM> mailing list. In particular,
+  Rob McCool, John Franks, Ari Luotonen,
+  George Phillips and
+  Tony Sanders deserve special recognition for their efforts in
+  defining and implementing the early versions of this interface.
+  </P>
+  <P>
+  This document has also greatly benefited from the comments and
+  suggestions made by  Chris Adie, Dave Kristol,
+  Mike Meyer, David Morris, Jeremy Madea,
+  Patrick M<SUP>c</SUP>Manus, Adam Donahue,
+  Ross Patterson, and Harald Alvestrand.
+  </P>
+
+  <H2>
+   <A NAME="13.0">
+    13. References
+   </A>
+  </H2>
+  <DL COMPACT>
+   <DT><A NAME="[1]">[1]</A>
+   </DT>
+   <DD>Berners-Lee, T., 'Universal Resource Identifiers in WWW: A
+       Unifying Syntax for the Expression of Names and Addresses of
+       Objects on the Network as used in the World-Wide Web', RFC 1630,
+       CERN, June 1994.
+       <P>
+       </P>
+   </DD>
+   <DT><A NAME="[2]">[2]</A>
+   </DT>
+   <DD>Berners-Lee, T. and Connolly, D., 'Hypertext Markup Language -
+        2.0', RFC 1866, MIT/W3C, November 1995.
+       <P>
+       </P>
+   </DD>
+   <DT><A NAME="[3]">[3]</A>
+   </DT>
+   <DD>Berners-Lee, T., Fielding, R. T. and Frystyk, H.,
+          'Hypertext Transfer Protocol -- HTTP/1.0', RFC 1945, MIT/LCS,
+          UC Irvine, May 1996.
+       <P>
+       </P>
+   </DD>
+
+  <DT><A NAME="[4]">[4]</A>
+  </DT>
+  <DD>Berners-Lee, T., Fielding, R., and Masinter, L., Editors,
+   'Uniform Resource Identifiers (URI): Generic Syntax', RFC 2396,
+   MIT, U.C. Irvine, Xerox Corporation, August 1996.
+   <P>
+   </P>
+  </DD>
+
+  <DT><A NAME="[5]">[5]</A>
+  </DT>
+  <DD>Braden, R., Editor, 'Requirements for Internet Hosts --
+          Application and Support', STD 3, RFC 1123, IETF, October 1989.
+       <P>
+       </P>
+   </DD>
+   <DT><A NAME="[6]">[6]</A>
+   </DT>
+   <DD>Crocker, D.H., 'Standard for the Format of ARPA Internet Text
+          Messages', STD 11, RFC 822, University of Delaware, August 1982.
+       <P>
+       </P>
+   </DD>
+  <DT><A NAME="[7]">[7]</A>
+  </DT>
+  <DD>Fielding, R., 'Relative Uniform Resource Locators', RFC 1808,
+          UC Irvine, June 1995.
+       <P>
+       </P>
+   </DD>
+   <DT><A NAME="[8]">[8]</A>
+   </DT>
+   <DD>Fielding, R., Gettys, J., Mogul, J., Frystyk, H. and
+          Berners-Lee, T., 'Hypertext Transfer Protocol -- HTTP/1.1',
+          RFC 2068, UC Irvine, DEC,
+         MIT/LCS, January 1997.
+       <P>
+       </P>
+   </DD>
+   <DT><A NAME="[9]">[9]</A>
+   </DT>
+   <DD>Freed, N. and Borenstein N., 'Multipurpose Internet Mail
+          Extensions (MIME) Part Two: Media Types', RFC 2046, Innosoft,
+          First Virtual, November 1996.
+       <P>
+       </P>
+   </DD>
+   <DT><A NAME="[10]">[10]</A>
+   </DT>
+   <DD>Mockapetris, P., 'Domain Names - Concepts and Facilities',
+          STD 13, RFC 1034, ISI, November 1987.
+       <P>
+       </P>
+   </DD>
+   <DT><A NAME="[11]">[11]</A>
+   </DT>
+   <DD>St. Johns, M., 'Identification Protocol', RFC 1431, US
+          Department of Defense, February 1993.
+       <P>
+       </P>
+   </DD>
+   <DT><A NAME="[12]">[12]</A>
+   </DT>
+   <DD>'Coded Character Set -- 7-bit American Standard Code for
+          Information Interchange', ANSI X3.4-1986.
+       <P>
+       </P>
+   </DD>
+   <DT><A NAME="[13]">[13]</A>
+   </DT>
+   <DD>Hinden, R. and Deering, S.,
+          'IP Version 6 Addressing Architecture', RFC 2373,
+         Nokia, Cisco Systems,
+          July 1998.
+       <P>
+       </P>
+   </DD>
+  </DL>
+
+  <H2>
+   <A NAME="14.0">
+    14. Authors' Addresses
+   </A>
+  </H2>
+  <ADDRESS>
+   <P>
+   Ken A L Coar
+   <BR>
+   MeepZor Consulting
+   <BR>
+   7824 Mayfaire Crest Lane, Suite 202
+   <BR>
+   Raleigh, NC   27615-4875
+   <BR>
+   U.S.A.
+   </P>
+   <P>
+   Tel: +1 (919) 254.4237
+   <BR>
+   Fax: +1 (919) 254.5250
+   <BR>
+   Email:
+   <A
+    HREF="mailto:Ken.Coar@Golux.Com"
+   ><SAMP>Ken.Coar@Golux.Com</SAMP></A>
+   </P>
+  </ADDRESS>
+  <ADDRESS>
+   <P>
+   David Robinson
+   <BR>
+   E*TRADE UK Ltd
+   <BR>
+   Mount Pleasant House
+   <BR>
+   2 Mount Pleasant
+   <BR>
+   Huntingdon Road
+   <BR>
+   Cambridge CB3 0RN
+   <BR>
+   UK
+   </P>
+   <P>
+   Tel: +44 (1223) 566926
+   <BR>
+   Fax: +44 (1223) 506288
+   <BR>
+   Email:
+   <A
+    HREF="mailto:drtr@etrade.co.uk"
+   ><SAMP>drtr@etrade.co.uk</SAMP></A>
+  </ADDRESS>
+
+ </BODY>
+</HTML>
diff --git a/docs/ifupdown_design.txt b/docs/ifupdown_design.txt
new file mode 100644 (file)
index 0000000..9df5792
--- /dev/null
@@ -0,0 +1,44 @@
+This document is meant to convince you to not use ifup/ifdown.
+
+
+The general problem with ifupdown is that it is "copulated in vertical
+fashion" by design. It tries to do the job of shell script in C,
+and this is invariably doomed to fail. You need ifup/ifdown
+to be adaptable by local admins, and C is an extremely poor choice
+for that.
+
+We are doomed to have problems with ifup/ifdown. Just look as this code:
+
+static const struct dhcp_client_t ext_dhcp_clients[] = {
+       { "dhcpcd", "<up cmd>", "<down cmd>" },
+       { "dhclient", ........ },
+       { "pump", ........ },
+       { "udhcpc", ........ },
+};
+
+static int dhcp_down(struct interface_defn_t *ifd, execfn *exec)
+{
+#if ENABLE_FEATURE_IFUPDOWN_EXTERNAL_DHCP
+       int i ;
+       for (i = 0; i < ARRAY_SIZE(ext_dhcp_clients); 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);
+#elif ENABLE_APP_UDHCPC
+       return execute("kill "
+                      "`cat /var/run/udhcpc.%iface%.pid` 2>/dev/null", ifd, exec);
+#else
+       return 0; /* no dhcp support */
+#endif
+}
+
+How the hell it is supposed to work reliably this way? Just imagine that
+admin is using pump and ifup/ifdown. It works. Then, for whatever reason,
+admin installs dhclient, but does NOT use it. ifdown will STOP WORKING,
+just because it will see installed dhclient binary in e.g. /usr/bin/dhclient!
+This is stupid.
+
+I seriously urge people to not use ifup/ifdown.
+Use something less brain damaged.
diff --git a/docs/keep_data_small.txt b/docs/keep_data_small.txt
new file mode 100644 (file)
index 0000000..2ddbefa
--- /dev/null
@@ -0,0 +1,216 @@
+               Keeping data small
+
+When many applets are compiled into busybox, all rw data and
+bss for each applet are concatenated. Including those from libc,
+if static busybox is built. When busybox is started, _all_ this data
+is allocated, not just that one part for selected applet.
+
+What "allocated" exactly means, depends on arch.
+On NOMMU it's probably bites the most, actually using real
+RAM for rwdata and bss. On i386, bss is lazily allocated
+by COWed zero pages. Not sure about rwdata - also COW?
+
+In order to keep busybox NOMMU and small-mem systems friendly
+we should avoid large global data in our applets, and should
+minimize usage of libc functions which implicitly use
+such structures.
+
+Small experiment to measure "parasitic" bbox memory consumption:
+here we start 1000 "busybox sleep 10" in parallel.
+busybox binary is practically allyesconfig static one,
+built against uclibc. Run on x86-64 machine with 64-bit kernel:
+
+bash-3.2# nmeter '%t %c %m %p %[pn]'
+23:17:28 .......... 168M    0  147
+23:17:29 .......... 168M    0  147
+23:17:30 U......... 168M    1  147
+23:17:31 SU........ 181M  244  391
+23:17:32 SSSSUUU... 223M  757 1147
+23:17:33 UUU....... 223M    0 1147
+23:17:34 U......... 223M    1 1147
+23:17:35 .......... 223M    0 1147
+23:17:36 .......... 223M    0 1147
+23:17:37 S......... 223M    0 1147
+23:17:38 .......... 223M    1 1147
+23:17:39 .......... 223M    0 1147
+23:17:40 .......... 223M    0 1147
+23:17:41 .......... 210M    0  906
+23:17:42 .......... 168M    1  147
+23:17:43 .......... 168M    0  147
+
+This requires 55M of memory. Thus 1 trivial busybox applet
+takes 55k of memory on 64-bit x86 kernel.
+
+On 32-bit kernel we need ~26k per applet.
+
+Script:
+
+i=1000; while test $i != 0; do
+        echo -n .
+        busybox sleep 30 &
+        i=$((i - 1))
+done
+echo
+wait
+
+(Data from NOMMU arches are sought. Provide 'size busybox' output too)
+
+
+               Example 1
+
+One example how to reduce global data usage is in
+archival/libunarchive/decompress_unzip.c:
+
+/* This is somewhat complex-looking arrangement, but it allows
+ * to place decompressor state either in bss or in
+ * malloc'ed space simply by changing #defines below.
+ * Sizes on i386:
+ * text    data     bss     dec     hex
+ * 5256       0     108    5364    14f4 - bss
+ * 4915       0       0    4915    1333 - malloc
+ */
+#define STATE_IN_BSS 0
+#define STATE_IN_MALLOC 1
+
+(see the rest of the file to get the idea)
+
+This example completely eliminates globals in that module.
+Required memory is allocated in unpack_gz_stream() [its main module]
+and then passed down to all subroutines which need to access 'globals'
+as a parameter.
+
+
+               Example 2
+
+In case you don't want to pass this additional parameter everywhere,
+take a look at archival/gzip.c. Here all global data is replaced by
+single global pointer (ptr_to_globals) to allocated storage.
+
+In order to not duplicate ptr_to_globals in every applet, you can
+reuse single common one. It is defined in libbb/messages.c
+as struct globals *const ptr_to_globals, but the struct globals is
+NOT defined in libbb.h. You first define your own struct:
+
+struct globals { int a; char buf[1000]; };
+
+and then declare that ptr_to_globals is a pointer to it:
+
+#define G (*ptr_to_globals)
+
+ptr_to_globals is declared as constant pointer.
+This helps gcc understand that it won't change, resulting in noticeably
+smaller code. In order to assign it, use SET_PTR_TO_GLOBALS macro:
+
+       SET_PTR_TO_GLOBALS(xzalloc(sizeof(G)));
+
+Typically it is done in <applet>_main().
+
+Now you can reference "globals" by G.a, G.buf and so on, in any function.
+
+
+               bb_common_bufsiz1
+
+There is one big common buffer in bss - bb_common_bufsiz1. It is a much
+earlier mechanism to reduce bss usage. Each applet can use it for
+its needs. Library functions are prohibited from using it.
+
+'G.' trick can be done using bb_common_bufsiz1 instead of malloced buffer:
+
+#define G (*(struct globals*)&bb_common_bufsiz1)
+
+Be careful, though, and use it only if globals fit into bb_common_bufsiz1.
+Since bb_common_bufsiz1 is BUFSIZ + 1 bytes long and BUFSIZ can change
+from one libc to another, you have to add compile-time check for it:
+
+if (sizeof(struct globals) > sizeof(bb_common_bufsiz1))
+       BUG_<applet>_globals_too_big();
+
+
+               Drawbacks
+
+You have to initialize it by hand. xzalloc() can be helpful in clearing
+allocated storage to 0, but anything more must be done by hand.
+
+All global variables are prefixed by 'G.' now. If this makes code
+less readable, use #defines:
+
+#define dev_fd (G.dev_fd)
+#define sector (G.sector)
+
+
+               Word of caution
+
+If applet doesn't use much of global data, converting it to use
+one of above methods is not worth the resulting code obfuscation.
+If you have less than ~300 bytes of global data - don't bother.
+
+
+               gcc's data alignment problem
+
+The following attribute added in vi.c:
+
+static int tabstop;
+static struct termios term_orig __attribute__ ((aligned (4)));
+static struct termios term_vi __attribute__ ((aligned (4)));
+
+reduces bss size by 32 bytes, because gcc sometimes aligns structures to
+ridiculously large values. asm output diff for above example:
+
+ tabstop:
+        .zero   4
+        .section        .bss.term_orig,"aw",@nobits
+-       .align 32
++       .align 4
+        .type   term_orig, @object
+        .size   term_orig, 60
+ term_orig:
+        .zero   60
+        .section        .bss.term_vi,"aw",@nobits
+-       .align 32
++       .align 4
+        .type   term_vi, @object
+        .size   term_vi, 60
+
+gcc doesn't seem to have options for altering this behaviour.
+
+gcc 3.4.3 and 4.1.1 tested:
+char c = 1;
+// gcc aligns to 32 bytes if sizeof(struct) >= 32
+struct {
+    int a,b,c,d;
+    int i1,i2,i3;
+} s28 = { 1 };    // struct will be aligned to 4 bytes
+struct {
+    int a,b,c,d;
+    int i1,i2,i3,i4;
+} s32 = { 1 };    // struct will be aligned to 32 bytes
+// same for arrays
+char vc31[31] = { 1 }; // unaligned
+char vc32[32] = { 1 }; // aligned to 32 bytes
+
+-fpack-struct=1 reduces alignment of s28 to 1 (but probably
+will break layout of many libc structs) but s32 and vc32
+are still aligned to 32 bytes.
+
+I will try to cook up a patch to add a gcc option for disabling it.
+Meanwhile, this is where it can be disabled in gcc source:
+
+gcc/config/i386/i386.c
+int
+ix86_data_alignment (tree type, int align)
+{
+#if 0
+  if (AGGREGATE_TYPE_P (type)
+       && TYPE_SIZE (type)
+       && TREE_CODE (TYPE_SIZE (type)) == INTEGER_CST
+       && (TREE_INT_CST_LOW (TYPE_SIZE (type)) >= 256
+           || TREE_INT_CST_HIGH (TYPE_SIZE (type))) && align < 256)
+    return 256;
+#endif
+
+Result (non-static busybox built against glibc):
+
+# size /usr/srcdevel/bbox/fix/busybox.t0/busybox busybox
+   text    data     bss     dec     hex filename
+ 634416    2736   23856  661008   a1610 busybox
+ 632580    2672   22944  658196   a0b14 busybox_noalign
diff --git a/docs/logging_and_backgrounding.txt b/docs/logging_and_backgrounding.txt
new file mode 100644 (file)
index 0000000..62a6d15
--- /dev/null
@@ -0,0 +1,96 @@
+       Logging and backgrounding
+
+By default, bb_[p]error_msg[_and_die] messages go to stderr,
+and of course, usually applets do not auto-background. :)
+
+Historically, daemons and inetd services are different.
+
+Busybox is trying to provide compatible behavior, thus if an applet
+is emulating an existing utility, it should mimic it. If utility
+auto-backgrounds itself, busybox applet should do the same.
+If utility normally logs to syslog, busybox applet should do
+the same too.
+
+However, busybox should not needlessly restrict the freedom
+of the users. And users have different needs and different preferences.
+Some might like logging everything from daemons to syslog.
+Others prefer running stuff under runsv/svlogd and thus would like
+logging to stderr and no daemonization.
+
+To help with that, busybox applets should have options to override
+default behavior, whatever that is for a given applet.
+
+
+Current sutiation is a bit of a mess:
+
+acpid - auto-backgrounds unless -d
+crond - auto-backgrounds unless -f, logs to syslog unless -d or -L.
+    option -d logs to stderr, -L FILE logs to FILE
+devfsd - (obsolete)
+dnsd - option -d makes it background and log to syslog
+fakeidentd - inetd service. Auto-backgrounds and logs to syslog
+    if no -f and no -i and no -w (-i is "inetd service" flag,
+    -w is "inetd-wait service" flag)
+ftpd - inetd service. Logs to syslog with -S, with -v logs to strerr too
+httpd - auto-backgrounds unless -f or -i (-i is "inetd service" flag)
+inetd - auto-backgrounds unless -f, logs to syslog unless -e
+klogd - auto-backgrounds unless -n
+syslogd - auto-backgrounds unless -n
+telnetd - auto-backgrounds unless -f or -i (-i is "inetd service" flag)
+udhcpc - auto-backgrounds unless -f after lease is obtained,
+    option -b makes it background sooner (when lease attempt
+    fails and retries start),
+    after backgrounding it stops logging to stderr;
+    logs to stderr, but option -S makes it log *also* to syslog
+udhcpd - auto-backgrounds and do not log to stderr unless -f,
+    otherwise logs to stderr, but option -S makes it log *also* to syslog
+zcip - auto-backgrounds and logs *also* to syslog unless -f
+
+Total: 13 applets (+1 obsolete),
+ 4 log to syslog by default (crond fakeidentd inetd zcip),
+ 5 never log to syslog (acpid httpd telnetd klogd syslogd, last two
+ - for obviously correct reasons),
+ there are no daemons which always log to syslog,
+ 12 auto-background if not run as inetd servies (all except dnsd.
+ Note that there is no "standard" dnsd AFAIKS). But see below
+ for daemons (tcpsvd etc) which don't auto-background.
+
+miscutils/crond.c:            logmode = LOGMODE_SYSLOG;
+networking/dnsd.c:            logmode = LOGMODE_SYSLOG;
+networking/ftpd.c:            logmode = LOGMODE_NONE;
+networking/ftpd.c:            logmode |= LOGMODE_SYSLOG;
+networking/inetd.c:           logmode = LOGMODE_SYSLOG;
+networking/isrv_identd.c:     logmode = LOGMODE_SYSLOG;
+networking/telnetd.c:         logmode = LOGMODE_SYSLOG;
+networking/udhcp/dhcpc.c:     logmode = LOGMODE_NONE;
+networking/udhcp/dhcpc.c:     logmode |= LOGMODE_SYSLOG;
+networking/udhcp/dhcpc.c:     logmode &= ~LOGMODE_STDIO;
+networking/udhcp/dhcpd.c:     logmode = LOGMODE_NONE;
+networking/udhcp/dhcpd.c:     logmode |= LOGMODE_SYSLOG;
+networking/zcip.c:            logmode |= LOGMODE_SYSLOG;
+
+
+These daemons never auto-background and never log to syslog:
+
+lpd - inetd service. Has nothing to log so far, though
+dhcprelay - standard behavior
+inotifyd - standard behavior
+runsv - standard behavior
+runsvdir - standard behavior
+svlogd - standard behavior
+tcpsvd, udpsvd - standard behavior
+tftpd - standard behavior
+
+
+Non-daemons (seems to be use syslog for a good reason):
+
+networking/nameif.c:          logmode |= LOGMODE_SYSLOG;
+loginutils/chpasswd.c:        logmode = LOGMODE_BOTH;
+loginutils/chpasswd.c:        logmode = LOGMODE_STDIO;
+loginutils/getty.c:           logmode = LOGMODE_BOTH;
+loginutils/getty.c:           logmode = LOGMODE_NONE;
+loginutils/passwd.c:          logmode = LOGMODE_STDIO;
+loginutils/passwd.c:          logmode = LOGMODE_BOTH;
+loginutils/sulogin.c:         logmode = LOGMODE_SYSLOG; (used if stdio isn't a tty)
+loginutils/sulogin.c:         logmode = LOGMODE_BOTH;
+util-linux/mount.c:           logmode = LOGMODE_SYSLOG; (used in a backgrounded NFS mount helper)
diff --git a/docs/mdev.txt b/docs/mdev.txt
new file mode 100644 (file)
index 0000000..a8a816c
--- /dev/null
@@ -0,0 +1,127 @@
+-------------
+ MDEV Primer
+-------------
+
+For those of us who know how to use mdev, a primer might seem lame.  For
+everyone else, mdev is a weird black box that they hear is awesome, but can't
+seem to get their head around how it works.  Thus, a primer.
+
+-----------
+ Basic Use
+-----------
+
+Mdev has two primary uses: initial population and dynamic updates.  Both
+require sysfs support in the kernel and have it mounted at /sys.  For dynamic
+updates, you also need to have hotplugging enabled in your kernel.
+
+Here's a typical code snippet from the init script:
+[0] mount -t proc proc /proc
+[1] mount -t sysfs sysfs /sys
+[2] echo /bin/mdev > /proc/sys/kernel/hotplug
+[3] mdev -s
+
+Alternatively, without procfs the above becomes:
+[1] mount -t sysfs sysfs /sys
+[2] sysctl -w kernel.hotplug=/bin/mdev
+[3] mdev -s
+
+
+Of course, a more "full" setup would entail executing this before the previous
+code snippet:
+[4] mount -t tmpfs -o size=64k,mode=0755 tmpfs /dev
+[5] mkdir /dev/pts
+[6] mount -t devpts devpts /dev/pts
+
+The simple explanation here is that [1] you need to have /sys mounted before
+executing mdev.  Then you [2] instruct the kernel to execute /bin/mdev whenever
+a device is added or removed so that the device node can be created or
+destroyed.  Then you [3] seed /dev with all the device nodes that were created
+while the system was booting.
+
+For the "full" setup, you want to [4] make sure /dev is a tmpfs filesystem
+(assuming you're running out of flash).  Then you want to [5] create the
+/dev/pts mount point and finally [6] mount the devpts filesystem on it.
+
+-------------
+ MDEV Config   (/etc/mdev.conf)
+-------------
+
+Mdev has an optional config file for controlling ownership/permissions of
+device nodes if your system needs something more than the default root/root
+660 permissions.
+
+The file has the format:
+    <device regex>       <uid>:<gid> <octal permissions>
+ or @<maj[,min1[-min2]]> <uid>:<gid> <octal permissions>
+
+For example:
+    hd[a-z][0-9]* 0:3 660
+
+The config file parsing stops at the first matching line.  If no line is
+matched, then the default of 0:0 660 is used.  To set your own default, simply
+create your own total match like so:
+       .* 1:1 777
+
+You can rename/move device nodes by using the next optional field.
+       <device regex> <uid>:<gid> <octal permissions> [=path]
+So if you want to place the device node into a subdirectory, make sure the path
+has a trailing /.  If you want to rename the device node, just place the name.
+       hda 0:3 660 =drives/
+This will move "hda" into the drives/ subdirectory.
+       hdb 0:3 660 =cdrom
+This will rename "hdb" to "cdrom".
+
+Similarly, ">path" renames/moves the device but it also creates
+a direct symlink /dev/DEVNAME to the renamed/moved device.
+
+If you also enable support for executing your own commands, then the file has
+the format:
+       <device regex> <uid>:<gid> <octal permissions> [=path] [@|$|*<command>]
+    or
+       <device regex> <uid>:<gid> <octal permissions> [>path] [@|$|*<command>]
+The special characters have the meaning:
+       @ Run after creating the device.
+       $ Run before removing the device.
+       * Run both after creating and before removing the device.
+
+The command is executed via the system() function (which means you're giving a
+command to the shell), so make sure you have a shell installed at /bin/sh.  You
+should also keep in mind that the kernel executes hotplug helpers with stdin,
+stdout, and stderr connected to /dev/null.
+
+For your convenience, the shell env var $MDEV is set to the device name.  So if
+the device "hdc" was matched, MDEV would be set to "hdc".
+
+----------
+ FIRMWARE
+----------
+
+Some kernel device drivers need to request firmware at runtime in order to
+properly initialize a device.  Place all such firmware files into the
+/lib/firmware/ directory.  At runtime, the kernel will invoke mdev with the
+filename of the firmware which mdev will load out of /lib/firmware/ and into
+the kernel via the sysfs interface.  The exact filename is hardcoded in the
+kernel, so look there if you need to know how to name the file in userspace.
+
+------------
+ SEQUENCING
+------------
+
+Kernel does not serialize hotplug events. It increments SEQNUM environmental
+variable for each successive hotplug invocation. Normally, mdev doesn't care.
+This may reorder hotplug and hot-unplug events, with typical symptoms of
+device nodes sometimes not created as expected.
+
+However, if /dev/mdev.seq file is found, mdev will compare its
+contents with SEQNUM. It will retry up to two seconds, waiting for them
+to match. If they match exactly (not even trailing '\n' is allowed),
+or if two seconds pass, mdev runs as usual, then it rewrites /dev/mdev.seq
+with SEQNUM+1.
+
+IOW: this will serialize concurrent mdev invocations.
+
+If you want to activate this feature, execute "echo >/dev/mdev.seq" prior to
+setting mdev to be the hotplug handler. This writes single '\n' to the file.
+NB: mdev recognizes /dev/mdev.seq consisting of single '\n' characher
+as a special case. IOW: this will not make your first hotplug event
+to stall for two seconds.
diff --git a/docs/new-applet-HOWTO.txt b/docs/new-applet-HOWTO.txt
new file mode 100644 (file)
index 0000000..6f89cbe
--- /dev/null
@@ -0,0 +1,182 @@
+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 - Trying to keep it updated.
+
+When doing this you should consider using the latest svn trunk.
+This is a good thing if you plan to getting it commited into mainline.
+
+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 <applet>_main instead of main.
+And be sure to put it in <applet>.c. Usage does not have to be taken care of by
+your applet.
+Make sure to #include "libbb.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:
+
+(busybox.h already includes most usual header files. You do not need
+#include <stdio.h> etc...)
+
+
+----begin example code------
+
+/* vi: set sw=4 ts=4: */
+/*
+ * Mini mu implementation for busybox
+ *
+ * Copyright (C) [YEAR] by [YOUR NAME] <YOUR EMAIL>
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.h"
+#include "other.h"
+
+int mu_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int mu_main(int argc, char **argv)
+{
+       int fd;
+       ssize_t n;
+       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.
+
+And it may be possible that some of the other applets uses functions you
+could use. If so, you have to rip the function out of the applet and make
+a libbb function out of it.
+
+Adding a libbb function:
+------------------------
+
+Make a new file named <function_name>.c
+
+----start example code------
+
+#include "libbb.h"
+#include "other.h"
+
+int function(char *a)
+{
+       return *a;
+}
+
+----end example code------
+
+Add <function_name>.o in the right alphabetically sorted place
+in libbb/Kbuild. You should look at the conditional part of
+libbb/Kbuild aswell.
+
+You should also try to find a suitable place in include/libbb.h for
+the function declaration. If not, add it somewhere anyway, with or without
+ifdefs to include or not.
+
+You can look at libbb/Config.in and try to find out if the function is
+tuneable and add it there if it is.
+
+
+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 Kbuild in the chosen directory:
+
+lib-$(CONFIG_MU)               += mu.o
+
+Add the applet to Config.in in the chosen directory:
+
+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:
+
+Be sure to read the top of applets.h before adding your applet.
+
+       /* all programs above here are alphabetically "less than" 'mu' */
+       USE_MU(APPLET(mu, _BB_DIR_USR_BIN, _BB_SUID_NEVER))
+       /* all programs below here are alphabetically "greater than" 'mu' */
+
+
+The Grand Announcement
+----------------------
+
+Then create a diff by adding the new files with svn (remember your libbb files)
+       svn add <where you put it>/mu.c
+eventually also:
+       svn add libbb/function.c
+then
+       svn diff
+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/nofork_noexec.txt b/docs/nofork_noexec.txt
new file mode 100644 (file)
index 0000000..06c789a
--- /dev/null
@@ -0,0 +1,79 @@
+       NOEXEC and NOFORK applets.
+
+Unix shells traditionally execute some commands internally in the attempt
+to dramatically speed up execution. It will be slow as hell if for every
+"echo blah" shell will fork and exec /bin/echo. To this end, shells
+have to _reimplement_ these commands internally.
+
+Busybox is unique in this regard because it already is a collection
+of reimplemented Unix commands, and we can do the same trick
+for speeding up busybox shells, and more. NOEXEC and NOFORK applets
+are exactly those applets which are eligible for these tricks.
+
+Applet will be subject to NOFORK/NOEXEC tricks if it is marked as such
+in applets.h. FEATURE_PREFER_APPLETS is a config option which
+globally enables usage of NOFORK/NOEXEC tricks.
+If it is enabled, FEATURE_SH_STANDALONE can be enabled too,
+and then shells will use NOFORK/NOEXEC tricks for ordinary commands.
+NB: shell builtins use these tricks regardless of FEATURE_SH_STANDALONE
+or FEATURE_PREFER_APPLETS.
+
+In C, if you want to call a program and wait for it, use
+spawn_and_wait(argv), BB_EXECVP(prog,argv) or BB_EXECLP(prog,argv0,...).
+They check whether program name is an applet name and optionally
+do NOFORK/NOEXEC thing depending on configuration.
+
+
+       NOEXEC
+
+NOEXEC applet should work correctly if another applet forks and then
+executes exit(<applet>_main(argc,argv)) in the child. The rules
+roughly are:
+
+* do not expect shared global variables/buffers to be in their
+  "initialized" state. Examples: xfunc_error_retval can be != 1,
+  bb_common_bufsiz1 can be scribbled over, ...
+* do not expect that stdio wasn't used before. Calling set[v]buf()
+  can be disastrous.
+* ...
+
+NOEXEC applets save only one half of fork+exec overhead.
+NOEXEC trick is disabled for NOMMU build.
+
+
+       NOFORK
+
+NOFORK applet should work correctly if another applet simply runs
+<applet>_main(argc,argv) and then continues with its business (xargs,
+find, shells can do it). This poses much more serious limitations
+on what applet can/cannot do:
+
+* all NOEXEC limitations apply.
+* do not ever exit() or exec().
+  - xfuncs are okay. They are using special trick to return
+    to the caller applet instead of dying when they detect "x" condition.
+  - you may "exit" to caller applet by calling xfunc_die(). Return value
+    is taken from xfunc_error_retval.
+  - fflush_stdout_and_exit(n) is ok to use.
+* do not use shared global data, or save/restore shared global data
+  prior to returning. (e.g. bb_common_bufsiz1 is off-limits).
+  - getopt32() is ok to use. You do not need to save/restore option_mask32,
+    it is already done by core code.
+* if you allocate memory, you can use xmalloc() only on the very first
+  allocation. All other allocations should use malloc[_or_warn]().
+  After first allocation, you cannot use any xfuncs.
+  Otherwise, failing xfunc will return to caller applet
+  without freeing malloced data!
+* All allocated data, opened files, signal handlers, termios settings,
+  O_NONBLOCK flags etc should be freed/closed/restored prior to return.
+* ...
+
+NOFORK applets give the most of speed advantage, but are trickiest
+to implement. In order to minimize amount of bugs and maintenance,
+prime candidates for NOFORK-ification are those applets which
+are small and easy to audit, and those which are more likely to be
+frequently executed from shell/find/xargs, particularly in shell
+script loops. Applets which mess with signal handlers, termios etc
+are probably not worth the effort.
+
+Any NOFORK applet is also a NOEXEC applet.
diff --git a/docs/sigint.htm b/docs/sigint.htm
new file mode 100644 (file)
index 0000000..e230f4d
--- /dev/null
@@ -0,0 +1,627 @@
+<HTML>
+<HEAD>
+<link rel="SHORTCUT ICON" href="http://www.cons.org/favicon.ico">
+<TITLE>Proper handling of SIGINT/SIGQUIT [http://www.cons.org/cracauer/sigint.html]</TITLE>
+<!-- Created by: GNU m4 using $Revision: 1.20 $ of crawww.m4lib on 11-Feb-2005 -->
+<BODY BGCOLOR="#fff8e1">
+<CENTER><H2>Proper handling of SIGINT/SIGQUIT</H2></CENTER>
+<img src=linie.png width="100%" alt=" ">
+<P>
+
+<table border=1 cellpadding=4>
+<tr><th valign=top align=left>Abstract: </th>
+<td valign=top align=left>
+In UNIX terminal sessions, you usually have a key like
+<code>C-c</code> (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.
+
+<p>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.
+
+<P>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.
+
+
+
+</td></tr><tr><th valign=top align=left>Intended audience: </th>
+<td valign=top align=left>Programmers who implement programs that catch SIGINT/SIGQUIT.
+<BR>Programmers who implements shells or shell-like programs that
+execute batches of programs.
+
+<p>Users who have problems problems getting rid of runaway shell
+scripts using <code>Control-C</code>. 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.
+
+
+</td></tr><tr><th valign=top align=left>Required knowledge: </th>
+<td valign=top align=left>You have to know what it means to catch SIGINT or SIGQUIT and how
+processes are waiting for other processes (childs) they spawned.
+
+
+</td></tr></table>
+<img src=linie.png width="100%" alt=" ">
+
+
+<H3>Basic concepts</H3>
+
+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.
+
+<p>You may change the key that triggers the signal using
+<code>stty</code> and running programs may remap the SIGINT-sending
+key at any time they like, without your intervention and without
+asking you first.
+
+<p>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.
+
+<p>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.
+
+<p>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.
+
+<H3>What we try to achieve</H3>
+
+The problem is with shell scripts (or similar programs that call
+several subprograms one after another).
+
+<p>Let us consider the most basic script:
+<PRE>
+#! /bin/sh
+program1
+program2
+</PRE>
+and the usual run looks like this:
+<PRE>
+$ sh myscript
+[output of program1]
+[output of program2]
+$
+</PRE>
+
+<p>Let us assume that both programs do nothing special on SIGINT, they
+just exit.
+
+<p>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.
+
+<p>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.
+
+<p>here is what would happen if the shell doesn't do anything:
+<PRE>
+$ 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]
+</PRE>
+
+
+<p>Consider a more annoying example:
+<pre>
+#! /bin/sh
+# let's assume there are 300 *.dat files
+for file in *.dat ; do
+       dat2ascii $dat
+done
+</pre>
+
+If your shell wouldn't end if the user hits <code>C-c</code>,
+<code>C-c</code> would just end <strong>one</strong> dat2ascii run and
+the script would continue. Thus, you had to hit <code>C-c</code> up to
+300 times to end this script.
+
+<H3>Alternatives to do so</H3>
+
+<p>There are several ways to handle abortion of shell scripts when
+SIGINT is received while a foreground child runs:
+
+<menu>
+
+<li>As just outlined, the shellscript may just continue, ignoring the
+fact that the user hit <code>C-c</code>. 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.
+
+<p><li>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.
+
+<p><li>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.
+
+<p><li>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 <sl>WUE</sl> 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.
+
+</menu>
+
+<H3>The problem</H3>
+
+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?
+
+<p>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.
+
+<p>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.
+
+<p>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.
+
+<p>Consider this script:
+<PRE>
+#! /bin/sh
+emacs /tmp/foo
+cp /tmp/foo /home/user/mail/sent
+</PRE>
+
+<p>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.
+
+<p>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.
+
+<H3>One more example</H3>
+
+<p>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.
+<code>C-g</code> is a normal editing key in emacs. Technically it
+sends SIGINT (it was <code>C-c</code>, but emacs remapped the key) to
+<menu>
+<li>emacs
+<li>the shell between your mailer and emacs, the one from your mailers
+    system("emacs /tmp/bla.44") command
+<li>the mailer itself
+<li>possibly another shell if your mailer was called by a shell script
+or from another application using system(3)
+<li>your interactive shell (which ignores it since it is interactive
+and hence is not relevant to this discussion)
+</menu>
+
+<p>If everyone just exits on SIGINT, you will be left with nothing but
+your login shell, without asking.
+
+<p>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.
+
+<p>Understand the difference: While <code>C-g</code> is used an a kind
+of abort key in emacs, it isn't the major "abort everything" key. When
+you use <code>C-g</code> in emacs, you want to end some internal emacs
+command. You don't want your whole emacs and mailer session to end.
+
+<p>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.
+
+<p>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.
+
+<H3>A further look at the alternatives</H3>
+
+<p>Still considering this script to examine the shell's actions in the
+IUE, WUE and ICE way of handling SIGINT:
+<PRE>
+#! /bin/sh
+emacs /tmp/foo
+cp /tmp/foo /home/user/mail/sent
+</PRE>
+
+<p>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.
+
+<p>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.
+
+<p>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.
+
+<p>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.
+
+<p>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).
+
+<p>this still work for the normal script without SIGINT-using
+programs:</p>
+<PRE>
+#! /bin/sh
+program1
+program2
+</PRE>
+
+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.
+
+<p>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.
+
+<p>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.
+
+<H3>So everything is well with WCE?</H3>
+
+Well, almost.
+
+<p>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.
+
+<p>Unless a program messes with signal handling, the system does this
+automatically.
+
+<p>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.
+
+<p>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.
+
+<p>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.
+
+<p>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.
+
+<p>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.
+
+<p>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.
+
+<H3>How to be a proper program</H3>
+
+<p>(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.)
+
+<p>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.
+
+<p>But once you catch SIGINT, you have to act.
+
+<p>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".
+
+<p>If so, you have to tell the calling program about it by exiting
+with the "I-exited-on-SIGINT" status.
+
+<p>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.
+
+<PRE>
+void sigint_handler(int sig)
+{
+       <do some cleanup>
+       signal(SIGINT, SIG_DFL);
+       kill(getpid(), SIGINT);
+}
+</PRE>
+
+Notes:
+
+<MENU>
+
+<LI>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.
+
+<P>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.
+
+<P><LI>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.
+
+<P><LI>POSIX 1003.1 allows all these calls to appear in signal
+handlers, so it is portable.
+
+</MENU>
+
+<p>In a bourne shell script, you can catch signals using the
+<code>trap</code> 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.
+
+<p>Consider this example from FreeBSD's <code>mkdep</code>, which is a
+bourne shell script.
+
+<pre>
+TMP=_mkdep$$
+trap 'rm -f $TMP ; trap 2 ; kill -2 $$' 1 2 3 13 15
+</pre>
+
+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).
+
+<P>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.
+
+<P>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.
+
+<H3>How to be a proper shell</H3>
+
+All this applies only for the script-executing case. Most shells will
+also have interactive modes where things are different.
+
+<MENU>
+
+<LI>Do nothing special when SIGINT appears while you wait for a child.
+You don't even have to remember that one happened.
+
+<P><LI>Wait for child to exit, get the exit status. Do not truncate it
+to type char.
+
+<P><LI>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".
+
+<P><LI>If the latter applies, discontinue the script.
+
+<P><LI>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
+
+<PRE>
+       signal(SIGINT, SIG_DFL);
+       kill(getpid(), SIGINT);
+</PRE>
+
+</MENU>
+
+<H3>Other remarks</H3>
+
+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.
+
+<H3>What to fight</H3>
+
+Make sure all programs <em>really</em> 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.
+
+<p>Make sure people understand why you can't fake an exit-on-signal by
+doing exit(...) using any numerical status.
+
+<p>Make sure you use a shell that behaves right. Especially if you
+develop programs, since it will help seeing problems.
+
+<H3>Concrete examples how to fix programs:</H3>
+<ul>
+
+<li>The fix for FreeBSD's
+<A HREF="http://www.freebsd.org/cgi/cvsweb.cgi/src/usr.bin/time/time.c.diff?r1=1.10&r2=1.11">time(1)</A>. 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.
+
+<p><li>Fix for FreeBSD's
+<A HREF="http://www.freebsd.org/cgi/cvsweb.cgi/src/usr.bin/truss/main.c.diff?r1=1.9&r2=1.10">truss(1)</A>.
+
+<p><li>The fix for FreeBSD's
+<A HREF="http://www.freebsd.org/cgi/cvsweb.cgi/src/usr.bin/mkdep/mkdep.gcc.sh.diff?r1=1.8.2.1&r2=1.8.2.2">mkdep(1)</A>, a shell script.
+
+
+<p><li>Fix for FreeBSD's make(1), <A HREF="http://www.freebsd.org/cgi/cvsweb.cgi/src/usr.bin/make/job.c.diff?r1=1.9&r2=1.10">part 1</A>,
+<A HREF="http://www.freebsd.org/cgi/cvsweb.cgi/src/usr.bin/make/compat.c.diff?r1=1.10&r2=1.11">part 2</A>.
+
+</ul>
+
+<H3>Testsuite for shells</H3>
+
+I have a collection of shellscripts that test shells for the
+behavior. See my <A HREF="download/">download dir</A> 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.
+
+<H3>Appendix 1 - table of implementation choices</H3>
+
+<table border cellpadding=2>
+
+<tr valign=top>
+<th>Method sign</th>
+<th>Does what?</th>
+<th>Example shells that implement it:</th>
+<th>What happens when a shellscript called emacs, the user used
+<code>C-g</code> and the script has additional commands in it?</th>
+<th>What happens when a shellscript called emacs, the user did not use
+<code>C-c</code> and the script has additional commands in it?</th>
+<th>What happens if a non-interactive child catches SIGINT?</th>
+<th>To behave properly, childs must do what?</th>
+</tr>
+
+<tr valign=top align=left>
+<td>IUE</td>
+<td>The shell executing a script exits immediately if it receives
+SIGINT.</td>
+<td>4.4BSD ash (ash), NetBSD, FreeBSD prior to 3.0/22.8</td>
+<td>The editor session is lost and subsequent commands are not
+executed.</td>
+<td>The editor continues as normal and the subsequent commands are
+executed. </td>
+<td>The scripts ends immediately, returning to the caller even before
+the current foreground child of the shell exits. </td>
+<td>It doesn't matter what the child does or how it exits, even if the
+child continues to operate, the shell returns. </td>
+</tr>
+
+<tr valign=top align=left>
+<td>WUE</td>
+<td>If the shell executing a script received SIGINT while a foreground
+process was running, it will exit after that child's exit.</td>
+<td>pdksh (OpenBSD /bin/sh)</td>
+<td>The editor continues as normal, but subsequent commands from the
+script are not executed.</td>
+<td>The editor continues as normal and subsequent commands are
+executed. </td>
+<td>The scripts returns to its caller after the current foreground
+child exits, no matter how the child exited. </td>
+<td>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. </td>
+</tr>
+
+<tr valign=top align=left>
+<td>WCE</td>
+<td>The 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).  </td>
+<td>bash (Linux /bin/sh), most commercial /bin/sh, FreeBSD /bin/sh
+from 3.0/2.2.8.</td>
+<td>The editor continues as normal and subsequent commands are
+executed. </td>
+<td>The editor continues as normal and subsequent commands are
+executed. </td>
+<td>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. </td>
+<td>The child must be implemented right, or the user will not be able
+to break shell scripts reliably.</td>
+</tr>
+
+</table>
+
+<P><img src=linie.png width="100%" alt=" ">
+<BR>&copy;2005 Martin Cracauer &lt;cracauer @ cons.org&gt;
+<A HREF="http://www.cons.org/cracauer/">http://www.cons.org/cracauer/</A>
+<BR>Last changed: $Date: 2005/02/11 21:44:43 $
+</BODY></HTML>
diff --git a/docs/style-guide.txt b/docs/style-guide.txt
new file mode 100644 (file)
index 0000000..7560d69
--- /dev/null
@@ -0,0 +1,714 @@
+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 preferred 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<num_items;i++){
+
+       Do this instead:
+
+               for (i = 0; i < num_items; i++) {
+
+       While it extends the line a bit longer, the spaced version is more
+       readable. An allowable exception to this rule is the situation where
+       excluding the spacing makes it more obvious that we are dealing with a
+       single term (even if it is a compound term) such as:
+
+               if (str[idx] == '/' && str[idx-1] != '\\')
+
+       or
+
+               if ((argc-1) - (optind+1) > 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 {
+
+If you have long logic statements that need to be wrapped, then uncuddling
+the bracket to improve readability is allowed. Generally, this style makes
+it easier for reader to notice that 2nd and following lines are still
+inside 'if':
+
+               if (some_really_long_checks && some_other_really_long_checks
+                && some_more_really_long_checks
+                && even_more_of_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...
+       }
+
+
+Labels
+~~~~~~
+
+Labels should start at the beginning of the line, not indented to the block
+level (because they do not "belong" to block scope, only to whole function).
+
+       if (foo) {
+               stmt;
+ label:
+               stmt2;
+               stmt;
+       }
+
+(Putting label at position 1 prevents diff -p from confusing label for function
+name, but it's not a policy of busybox project to enforce such a minor detail).
+
+
+
+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. FIFO_TYPE,
+   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 <type> var' for declaring constants.
+
+       Don't do this:
+
+               #define CONST 80
+
+       Do this instead, when the variable is in a header file and will be used in
+       several source files:
+
+               enum { CONST = 80 };
+
+Although enum may look ugly to some people, it is better for code size.
+With "const int" compiler may fail to optimize it out and will reserve
+a real storage in rodata for it! (Hopefully, newer gcc will get better
+at it...).  With "define", you have slight risk of polluting namespace
+(#define doesn't allow you to redefine the name in the inner scopes),
+and complex "define" are evaluated each time they uesd, not once
+at declarations like enums. Also, the preprocessor does _no_ type checking
+whatsoever, making it much more error prone.
+
+
+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)
+
+               #if ENABLE_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                safe_strncpy
+strncpy      may fail to 0-terminate dst safe_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.
+
+Another relatively new trick of similar nature is explained
+in keep_data_small.txt.
+
+
+
+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 = xmalloc_fgets(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 getopt32() 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, if you need to parse long options:
+
+       #include <getopt.h>
+
+Then have long options defined:
+
+       static const struct option <applet>_long_options[] = {
+               { "list",    0, NULL, 't' },
+               { "extract", 0, NULL, 'x' },
+               { NULL, 0, NULL, 0 }
+       };
+
+And a code block similar to the following near the top of your applet_main()
+routine:
+
+       char *str_b;
+
+       opt_complementary = "cryptic_string";
+       applet_long_options = <applet>_long_options; /* if you have them */
+       opt = getopt32(argc, argv, "ab:c", &str_b);
+       if (opt & 1) {
+               handle_option_a();
+       }
+       if (opt & 2) {
+               handle_option_b(str_b);
+       }
+       if (opt & 4) {
+               handle_option_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.
+
+For more info and examples, examine getopt32.c, tar.c, wget.c etc.
diff --git a/docs/tar_pax.txt b/docs/tar_pax.txt
new file mode 100644 (file)
index 0000000..e56c27b
--- /dev/null
@@ -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", <length>, <keyword>, <value>
+
+The <length> field shall be the decimal length of the extended
+header record in octets, including length string itself and the
+trailing <newline>.
+
+[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 <value> 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 <value> 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
+<value> 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 <value> 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
+<value>s; all characters within the <value> 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 (file)
index 0000000..9a0088a
--- /dev/null
@@ -0,0 +1,68 @@
+#
+# 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.
+
+### NB: this one is now provided by util-linux/volume_id/*
+### 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 (file)
index 0000000..9f58ce0
--- /dev/null
@@ -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_CHATTR) += chattr.o e2fs_lib.o
+lib-$(CONFIG_LSATTR) += lsattr.o e2fs_lib.o
+
+lib-$(CONFIG_FSCK) += fsck.o
diff --git a/e2fsprogs/README b/e2fsprogs/README
new file mode 100644 (file)
index 0000000..eb158e5
--- /dev/null
@@ -0,0 +1,12 @@
+Authors and contributors of original e2fsprogs:
+
+Remy Card <card@masi.ibp.fr>
+Theodore Ts'o <tytso@mit.edu>
+Stephen C. Tweedie <sct@redhat.com>
+Andreas Gruenbacher, <a.gruenbacher@computer.org>
+Kaz Kylheku <kaz@ashi.footprints.net>
+F.W. ten Wolde <franky@duteca.et.tudelft.nl>
+Jeremy Fitzhardinge <jeremy@zip.com.au>
+M.J.E. Mol <marcel@duteca.et.tudelft.nl>
+Miquel van Smoorenburg <miquels@drinkel.ow.org>
+Uwe Ohse <uwe@tirka.gun.de>
diff --git a/e2fsprogs/chattr.c b/e2fsprogs/chattr.c
new file mode 100644 (file)
index 0000000..b41919b
--- /dev/null
@@ -0,0 +1,172 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * chattr.c            - Change file attributes on an ext2 file system
+ *
+ * Copyright (C) 1993, 1994  Remy Card <card@masi.ibp.fr>
+ *                           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 "libbb.h"
+#include "e2fs_lib.h"
+
+#define OPT_ADD 1
+#define OPT_REM 2
+#define OPT_SET 4
+#define OPT_SET_VER 8
+
+struct globals {
+       unsigned long version;
+       unsigned long af;
+       unsigned long rf;
+       smallint flags;
+       smallint recursive;
+};
+
+static unsigned long get_flag(char c)
+{
+       const char *fp = strchr(e2attr_flags_sname_chattr, c);
+       if (fp)
+               return e2attr_flags_value_chattr[fp - e2attr_flags_sname_chattr];
+       bb_show_usage();
+}
+
+static int decode_arg(const char *arg, struct globals *gp)
+{
+       unsigned long *fl;
+       char opt = *arg++;
+
+       fl = &gp->af;
+       if (opt == '-') {
+               gp->flags |= OPT_REM;
+               fl = &gp->rf;
+       } else if (opt == '+') {
+               gp->flags |= OPT_ADD;
+       } else if (opt == '=') {
+               gp->flags |= OPT_SET;
+       } else
+               return 0;
+
+       while (*arg)
+               *fl |= get_flag(*arg++);
+
+       return 1;
+}
+
+static void change_attributes(const char *name, struct globals *gp);
+
+static int chattr_dir_proc(const char *dir_name, struct dirent *de, void *gp)
+{
+       char *path = concat_subpath_file(dir_name, de->d_name);
+       /* path is NULL if de->d_name is "." or "..", else... */
+       if (path) {
+               change_attributes(path, gp);
+               free(path);
+       }
+       return 0;
+}
+
+static void change_attributes(const char *name, struct globals *gp)
+{
+       unsigned long fsflags;
+       struct stat st;
+
+       if (lstat(name, &st) != 0) {
+               bb_perror_msg("stat %s", name);
+               return;
+       }
+       if (S_ISLNK(st.st_mode) && gp->recursive)
+               return;
+
+       /* Don't try to open device files, fifos etc.  We probably
+        * ought to display an error if the file was explicitly given
+        * 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 (gp->flags & OPT_SET_VER)
+               if (fsetversion(name, gp->version) != 0)
+                       bb_perror_msg("setting version on %s", name);
+
+       if (gp->flags & OPT_SET) {
+               fsflags = gp->af;
+       } else {
+               if (fgetflags(name, &fsflags) != 0) {
+                       bb_perror_msg("reading flags on %s", name);
+                       goto skip_setflags;
+               }
+               /*if (gp->flags & OPT_REM) - not needed, rf is zero otherwise */
+                       fsflags &= ~gp->rf;
+               /*if (gp->flags & OPT_ADD) - not needed, af is zero otherwise */
+                       fsflags |= gp->af;
+               /* What is this? And why it's not done for SET case? */
+               if (!S_ISDIR(st.st_mode))
+                       fsflags &= ~EXT2_DIRSYNC_FL;
+       }
+       if (fsetflags(name, fsflags) != 0)
+               bb_perror_msg("setting flags on %s", name);
+
+ skip_setflags:
+       if (gp->recursive && S_ISDIR(st.st_mode))
+               iterate_on_dir(name, chattr_dir_proc, gp);
+}
+
+int chattr_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int chattr_main(int argc UNUSED_PARAM, char **argv)
+{
+       struct globals g;
+       char *arg;
+
+       memset(&g, 0, sizeof(g));
+
+       /* parse the args */
+       while ((arg = *++argv)) {
+               /* take care of -R and -v <version> */
+               if (arg[0] == '-'
+                && (arg[1] == 'R' || arg[1] == 'v')
+                && !arg[2]
+               ) {
+                       if (arg[1] == 'R') {
+                               g.recursive = 1;
+                               continue;
+                       }
+                       /* arg[1] == 'v' */
+                       if (!*++argv)
+                               bb_show_usage();
+                       g.version = xatoul(*argv);
+                       g.flags |= OPT_SET_VER;
+                       continue;
+               }
+
+               if (!decode_arg(arg, &g))
+                       break;
+       }
+
+       /* run sanity checks on all the arguments given us */
+       if (!*argv)
+               bb_show_usage();
+       if ((g.flags & OPT_SET) && (g.flags & (OPT_ADD|OPT_REM)))
+               bb_error_msg_and_die("= is incompatible with - and +");
+       if (g.rf & g.af)
+               bb_error_msg_and_die("can't set and unset a flag");
+       if (!g.flags)
+               bb_error_msg_and_die("must use '-v', =, - or +");
+
+       /* now run chattr on all the files passed to us */
+       do change_attributes(*argv, &g); while (*++argv);
+
+       return EXIT_SUCCESS;
+}
diff --git a/e2fsprogs/e2fs_defs.h b/e2fsprogs/e2fs_defs.h
new file mode 100644 (file)
index 0000000..379640e
--- /dev/null
@@ -0,0 +1,561 @@
+/* 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)
+ *
+ * Copyright (C) 1991, 1992  Linus Torvalds
+ */
+
+#ifndef LINUX_EXT2_FS_H
+#define LINUX_EXT2_FS_H 1
+
+/*
+ * 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(uint32_t))
+
+/*
+ * 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 */
+       uint32_t        aclh_size;
+       uint32_t        aclh_file_count;
+       uint32_t        aclh_acle_count;
+       uint32_t        aclh_first_acle;
+};
+
+struct ext2_acl_entry {        /* Access Control List Entry */
+       uint32_t        acle_size;
+       uint16_t        acle_perms;     /* Access permissions */
+       uint16_t        acle_type;      /* Type of entry */
+       uint16_t        acle_tag;       /* User or group identity */
+       uint16_t        acle_pad1;
+       uint32_t        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 {
+       uint32_t        bg_block_bitmap;        /* Blocks bitmap block */
+       uint32_t        bg_inode_bitmap;        /* Inodes bitmap block */
+       uint32_t        bg_inode_table;         /* Inodes table block */
+       uint16_t        bg_free_blocks_count;   /* Free blocks count */
+       uint16_t        bg_free_inodes_count;   /* Free inodes count */
+       uint16_t        bg_used_dirs_count;     /* Directories count */
+       uint16_t        bg_pad;
+       uint32_t        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 {
+       uint32_t        reserved_zero;
+       uint8_t         hash_version; /* 0 now, 1 at release */
+       uint8_t         info_length; /* 8 */
+       uint8_t         indirect_levels;
+       uint8_t         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 {
+       uint32_t hash;
+       uint32_t block;
+};
+
+struct ext2_dx_countlimit {
+       uint16_t limit;
+       uint16_t 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 {
+       uint16_t        i_mode;         /* File mode */
+       uint16_t        i_uid;          /* Low 16 bits of Owner Uid */
+       uint32_t        i_size;         /* Size in bytes */
+       uint32_t        i_atime;        /* Access time */
+       uint32_t        i_ctime;        /* Creation time */
+       uint32_t        i_mtime;        /* Modification time */
+       uint32_t        i_dtime;        /* Deletion Time */
+       uint16_t        i_gid;          /* Low 16 bits of Group Id */
+       uint16_t        i_links_count;  /* Links count */
+       uint32_t        i_blocks;       /* Blocks count */
+       uint32_t        i_flags;        /* File flags */
+       union {
+               struct {
+                       uint32_t  l_i_reserved1;
+               } linux1;
+               struct {
+                       uint32_t  h_i_translator;
+               } hurd1;
+               struct {
+                       uint32_t  m_i_reserved1;
+               } masix1;
+       } osd1;                         /* OS dependent 1 */
+       uint32_t        i_block[EXT2_N_BLOCKS];/* Pointers to blocks */
+       uint32_t        i_generation;   /* File version (for NFS) */
+       uint32_t        i_file_acl;     /* File ACL */
+       uint32_t        i_dir_acl;      /* Directory ACL */
+       uint32_t        i_faddr;        /* Fragment address */
+       union {
+               struct {
+                       uint8_t         l_i_frag;       /* Fragment number */
+                       uint8_t         l_i_fsize;      /* Fragment size */
+                       uint16_t        i_pad1;
+                       uint16_t        l_i_uid_high;   /* these 2 fields    */
+                       uint16_t        l_i_gid_high;   /* were reserved2[0] */
+                       uint32_t        l_i_reserved2;
+               } linux2;
+               struct {
+                       uint8_t         h_i_frag;       /* Fragment number */
+                       uint8_t         h_i_fsize;      /* Fragment size */
+                       uint16_t        h_i_mode_high;
+                       uint16_t        h_i_uid_high;
+                       uint16_t        h_i_gid_high;
+                       uint32_t        h_i_author;
+               } hurd2;
+               struct {
+                       uint8_t         m_i_frag;       /* Fragment number */
+                       uint8_t         m_i_fsize;      /* Fragment size */
+                       uint16_t        m_pad1;
+                       uint32_t        m_i_reserved2[2];
+               } masix2;
+       } osd2;                         /* OS dependent 2 */
+};
+
+/*
+ * Permanent part of an large inode on the disk
+ */
+struct ext2_inode_large {
+       uint16_t        i_mode;         /* File mode */
+       uint16_t        i_uid;          /* Low 16 bits of Owner Uid */
+       uint32_t        i_size;         /* Size in bytes */
+       uint32_t        i_atime;        /* Access time */
+       uint32_t        i_ctime;        /* Creation time */
+       uint32_t        i_mtime;        /* Modification time */
+       uint32_t        i_dtime;        /* Deletion Time */
+       uint16_t        i_gid;          /* Low 16 bits of Group Id */
+       uint16_t        i_links_count;  /* Links count */
+       uint32_t        i_blocks;       /* Blocks count */
+       uint32_t        i_flags;        /* File flags */
+       union {
+               struct {
+                       uint32_t  l_i_reserved1;
+               } linux1;
+               struct {
+                       uint32_t  h_i_translator;
+               } hurd1;
+               struct {
+                       uint32_t  m_i_reserved1;
+               } masix1;
+       } osd1;                         /* OS dependent 1 */
+       uint32_t        i_block[EXT2_N_BLOCKS];/* Pointers to blocks */
+       uint32_t        i_generation;   /* File version (for NFS) */
+       uint32_t        i_file_acl;     /* File ACL */
+       uint32_t        i_dir_acl;      /* Directory ACL */
+       uint32_t        i_faddr;        /* Fragment address */
+       union {
+               struct {
+                       uint8_t         l_i_frag;       /* Fragment number */
+                       uint8_t         l_i_fsize;      /* Fragment size */
+                       uint16_t        i_pad1;
+                       uint16_t        l_i_uid_high;   /* these 2 fields    */
+                       uint16_t        l_i_gid_high;   /* were reserved2[0] */
+                       uint32_t        l_i_reserved2;
+               } linux2;
+               struct {
+                       uint8_t         h_i_frag;       /* Fragment number */
+                       uint8_t         h_i_fsize;      /* Fragment size */
+                       uint16_t        h_i_mode_high;
+                       uint16_t        h_i_uid_high;
+                       uint16_t        h_i_gid_high;
+                       uint32_t        h_i_author;
+               } hurd2;
+               struct {
+                       uint8_t         m_i_frag;       /* Fragment number */
+                       uint8_t         m_i_fsize;      /* Fragment size */
+                       uint16_t        m_pad1;
+                       uint32_t        m_i_reserved2[2];
+               } masix2;
+       } osd2;                         /* OS dependent 2 */
+       uint16_t        i_extra_isize;
+       uint16_t        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 {
+       uint32_t        s_inodes_count;         /* Inodes count */
+       uint32_t        s_blocks_count;         /* Blocks count */
+       uint32_t        s_r_blocks_count;       /* Reserved blocks count */
+       uint32_t        s_free_blocks_count;    /* Free blocks count */
+       uint32_t        s_free_inodes_count;    /* Free inodes count */
+       uint32_t        s_first_data_block;     /* First Data Block */
+       uint32_t        s_log_block_size;       /* Block size */
+       int32_t         s_log_frag_size;        /* Fragment size */
+       uint32_t        s_blocks_per_group;     /* # Blocks per group */
+       uint32_t        s_frags_per_group;      /* # Fragments per group */
+       uint32_t        s_inodes_per_group;     /* # Inodes per group */
+       uint32_t        s_mtime;                /* Mount time */
+       uint32_t        s_wtime;                /* Write time */
+       uint16_t        s_mnt_count;            /* Mount count */
+       int16_t         s_max_mnt_count;        /* Maximal mount count */
+       uint16_t        s_magic;                /* Magic signature */
+       uint16_t        s_state;                /* File system state */
+       uint16_t        s_errors;               /* Behaviour when detecting errors */
+       uint16_t        s_minor_rev_level;      /* minor revision level */
+       uint32_t        s_lastcheck;            /* time of last check */
+       uint32_t        s_checkinterval;        /* max. time between checks */
+       uint32_t        s_creator_os;           /* OS */
+       uint32_t        s_rev_level;            /* Revision level */
+       uint16_t        s_def_resuid;           /* Default uid for reserved blocks */
+       uint16_t        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...
+        */
+       uint32_t        s_first_ino;            /* First non-reserved inode */
+       uint16_t        s_inode_size;           /* size of inode structure */
+       uint16_t        s_block_group_nr;       /* block group # of this superblock */
+       uint32_t        s_feature_compat;       /* compatible feature set */
+       uint32_t        s_feature_incompat;     /* incompatible feature set */
+       uint32_t        s_feature_ro_compat;    /* readonly-compatible feature set */
+       uint8_t         s_uuid[16];             /* 128-bit uuid for volume */
+       char            s_volume_name[16];      /* volume name */
+       char            s_last_mounted[64];     /* directory where last mounted */
+       uint32_t        s_algorithm_usage_bitmap; /* For compression */
+       /*
+        * Performance hints.  Directory preallocation should only
+        * happen if the EXT2_FEATURE_COMPAT_DIR_PREALLOC flag is on.
+        */
+       uint8_t s_prealloc_blocks;      /* Nr of blocks to try to preallocate*/
+       uint8_t s_prealloc_dir_blocks;  /* Nr to preallocate for dirs */
+       uint16_t        s_reserved_gdt_blocks;  /* Per group table for online growth */
+       /*
+        * Journaling support valid if EXT2_FEATURE_COMPAT_HAS_JOURNAL set.
+        */
+       uint8_t         s_journal_uuid[16];     /* uuid of journal superblock */
+       uint32_t        s_journal_inum;         /* inode number of journal file */
+       uint32_t        s_journal_dev;          /* device number of journal file */
+       uint32_t        s_last_orphan;          /* start of list of inodes to delete */
+       uint32_t        s_hash_seed[4];         /* HTREE hash seed */
+       uint8_t         s_def_hash_version;     /* Default hash version to use */
+       uint8_t         s_jnl_backup_type;      /* Default type of journal backup */
+       uint16_t        s_reserved_word_pad;
+       uint32_t        s_default_mount_opts;
+       uint32_t        s_first_meta_bg;        /* First metablock group */
+       uint32_t        s_mkfs_time;            /* When the filesystem was created */
+       uint32_t        s_jnl_blocks[17];       /* Backup of the journal inode */
+       uint32_t        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 {
+       uint32_t        inode;                  /* Inode number */
+       uint16_t        rec_len;                /* Directory entry length */
+       uint16_t        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 {
+       uint32_t        inode;                  /* Inode number */
+       uint16_t        rec_len;                /* Directory entry length */
+       uint8_t         name_len;               /* Name length */
+       uint8_t         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
diff --git a/e2fsprogs/e2fs_lib.c b/e2fsprogs/e2fs_lib.c
new file mode 100644 (file)
index 0000000..3e8d956
--- /dev/null
@@ -0,0 +1,226 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * See README for additional information
+ *
+ * Licensed under GPLv2, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.h"
+#include "e2fs_lib.h"
+
+#define HAVE_EXT2_IOCTLS 1
+
+#if INT_MAX == LONG_MAX
+#define IF_LONG_IS_SAME(...) __VA_ARGS__
+#define IF_LONG_IS_WIDER(...)
+#else
+#define IF_LONG_IS_SAME(...)
+#define IF_LONG_IS_WIDER(...) __VA_ARGS__
+#endif
+
+static void close_silently(int fd)
+{
+       int e = errno;
+       close(fd);
+       errno = e;
+}
+
+
+/* Iterate a function on each entry of a directory */
+int iterate_on_dir(const char *dir_name,
+               int (*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 = 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);
+       }
+       closedir(dir);
+       free(de);
+       return 0;
+}
+
+
+/* Get/set a file version on an ext2 file system */
+int fgetsetversion(const char *name, unsigned long *get_version, unsigned long set_version)
+{
+#if HAVE_EXT2_IOCTLS
+       int fd, r;
+       IF_LONG_IS_WIDER(int ver;)
+
+       fd = open(name, O_NONBLOCK);
+       if (fd == -1)
+               return -1;
+       if (!get_version) {
+               IF_LONG_IS_WIDER(
+                       ver = (int) set_version;
+                       r = ioctl(fd, EXT2_IOC_SETVERSION, &ver);
+               )
+               IF_LONG_IS_SAME(
+                       r = ioctl(fd, EXT2_IOC_SETVERSION, (void*)&set_version);
+               )
+       } else {
+               IF_LONG_IS_WIDER(
+                       r = ioctl(fd, EXT2_IOC_GETVERSION, &ver);
+                       *get_version = ver;
+               )
+               IF_LONG_IS_SAME(
+                       r = ioctl(fd, EXT2_IOC_GETVERSION, (void*)get_version);
+               )
+       }
+       close_silently(fd);
+       return r;
+#else /* ! HAVE_EXT2_IOCTLS */
+       errno = EOPNOTSUPP;
+       return -1;
+#endif /* ! HAVE_EXT2_IOCTLS */
+}
+
+
+/* Get/set a file flags on an ext2 file system */
+int fgetsetflags(const char *name, unsigned long *get_flags, unsigned long set_flags)
+{
+#if HAVE_EXT2_IOCTLS
+       struct stat buf;
+       int fd, r;
+       IF_LONG_IS_WIDER(int f;)
+
+       if (stat(name, &buf) == 0 /* stat is ok */
+        && !S_ISREG(buf.st_mode) && !S_ISDIR(buf.st_mode)
+       ) {
+               goto notsupp;
+       }
+       fd = open(name, O_NONBLOCK); /* neither read nor write asked for */
+       if (fd == -1)
+               return -1;
+
+       if (!get_flags) {
+               IF_LONG_IS_WIDER(
+                       f = (int) set_flags;
+                       r = ioctl(fd, EXT2_IOC_SETFLAGS, &f);
+               )
+               IF_LONG_IS_SAME(
+                       r = ioctl(fd, EXT2_IOC_SETFLAGS, (void*)&set_flags);
+               )
+       } else {
+               IF_LONG_IS_WIDER(
+                       r = ioctl(fd, EXT2_IOC_GETFLAGS, &f);
+                       *get_flags = f;
+               )
+               IF_LONG_IS_SAME(
+                       r = ioctl(fd, EXT2_IOC_GETFLAGS, (void*)get_flags);
+               )
+       }
+
+       close_silently(fd);
+       return r;
+ notsupp:
+#endif /* HAVE_EXT2_IOCTLS */
+       errno = EOPNOTSUPP;
+       return -1;
+}
+
+
+/* Print file attributes on an ext2 file system */
+const uint32_t e2attr_flags_value[] = {
+#ifdef ENABLE_COMPRESSION
+       EXT2_COMPRBLK_FL,
+       EXT2_DIRTY_FL,
+       EXT2_NOCOMPR_FL,
+       EXT2_ECOMPR_FL,
+#endif
+       EXT2_INDEX_FL,
+       EXT2_SECRM_FL,
+       EXT2_UNRM_FL,
+       EXT2_SYNC_FL,
+       EXT2_DIRSYNC_FL,
+       EXT2_IMMUTABLE_FL,
+       EXT2_APPEND_FL,
+       EXT2_NODUMP_FL,
+       EXT2_NOATIME_FL,
+       EXT2_COMPR_FL,
+       EXT3_JOURNAL_DATA_FL,
+       EXT2_NOTAIL_FL,
+       EXT2_TOPDIR_FL
+};
+
+const char e2attr_flags_sname[] =
+#ifdef ENABLE_COMPRESSION
+       "BZXE"
+#endif
+       "I"
+       "suSDiadAcjtT";
+
+static const char e2attr_flags_lname[] =
+#ifdef ENABLE_COMPRESSION
+       "Compressed_File" "\0"
+       "Compressed_Dirty_File" "\0"
+       "Compression_Raw_Access" "\0"
+       "Compression_Error" "\0"
+#endif
+       "Indexed_directory" "\0"
+       "Secure_Deletion" "\0"
+       "Undelete" "\0"
+       "Synchronous_Updates" "\0"
+       "Synchronous_Directory_Updates" "\0"
+       "Immutable" "\0"
+       "Append_Only" "\0"
+       "No_Dump" "\0"
+       "No_Atime" "\0"
+       "Compression_Requested" "\0"
+       "Journaled_Data" "\0"
+       "No_Tailmerging" "\0"
+       "Top_of_Directory_Hierarchies" "\0"
+       /* Another trailing NUL is added by compiler */;
+
+void print_e2flags(FILE *f, unsigned long flags, unsigned options)
+{
+       const uint32_t *fv;
+       const char *fn;
+
+       fv = e2attr_flags_value;
+       if (options & PFOPT_LONG) {
+               int first = 1;
+               fn = e2attr_flags_lname;
+               do {
+                       if (flags & *fv) {
+                               if (!first)
+                                       fputs(", ", f);
+                               fputs(fn, f);
+                               first = 0;
+                       }
+                       fv++;
+                       fn += strlen(fn) + 1;
+               } while (*fn);
+               if (first)
+                       fputs("---", f);
+       } else {
+               fn = e2attr_flags_sname;
+               do  {
+                       char c = '-';
+                       if (flags & *fv)
+                               c = *fn;
+                       fputc(c, f);
+                       fv++;
+                       fn++;
+               } while (*fn);
+       }
+}
diff --git a/e2fsprogs/e2fs_lib.h b/e2fsprogs/e2fs_lib.h
new file mode 100644 (file)
index 0000000..25b26d3
--- /dev/null
@@ -0,0 +1,47 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * See README for additional information
+ *
+ * This file can be redistributed under the terms of the GNU Library General
+ * Public License
+ */
+
+/* Constants and structures */
+#include "e2fs_defs.h"
+
+PUSH_AND_SET_FUNCTION_VISIBILITY_TO_HIDDEN
+
+/* Iterate a function on each entry of a directory */
+int iterate_on_dir(const char *dir_name,
+               int (*func)(const char *, struct dirent *, void *),
+               void *private);
+
+/* Get/set a file version on an ext2 file system */
+int fgetsetversion(const char *name, unsigned long *get_version, unsigned long set_version);
+#define fgetversion(name, version) fgetsetversion(name, version, 0)
+#define fsetversion(name, version) fgetsetversion(name, NULL, version)
+
+/* Get/set a file flags on an ext2 file system */
+int fgetsetflags(const char *name, unsigned long *get_flags, unsigned long set_flags);
+#define fgetflags(name, flags) fgetsetflags(name, flags, 0)
+#define fsetflags(name, flags) fgetsetflags(name, NULL, flags)
+
+/* Must be 1 for compatibility with `int long_format'. */
+#define PFOPT_LONG  1
+/* Print file attributes on an ext2 file system */
+void print_e2flags(FILE *f, unsigned long flags, unsigned options);
+
+extern const uint32_t e2attr_flags_value[];
+extern const char e2attr_flags_sname[];
+
+/* If you plan to ENABLE_COMPRESSION, see e2fs_lib.c and chattr.c - */
+/* make sure that chattr doesn't accept bad options! */
+#ifdef ENABLE_COMPRESSION
+#define e2attr_flags_value_chattr (&e2attr_flags_value[5])
+#define e2attr_flags_sname_chattr (&e2attr_flags_sname[5])
+#else
+#define e2attr_flags_value_chattr (&e2attr_flags_value[1])
+#define e2attr_flags_sname_chattr (&e2attr_flags_sname[1])
+#endif
+
+POP_SAVED_FUNCTION_VISIBILITY
diff --git a/e2fsprogs/fsck.c b/e2fsprogs/fsck.c
new file mode 100644 (file)
index 0000000..3c6cafb
--- /dev/null
@@ -0,0 +1,1085 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * fsck --- 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, <tytso@mit.edu>
+ *
+ * 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.
+ *
+ * Licensed under GPLv2, see file LICENSE in this tarball for details.
+ */
+
+/* All filesystem specific hooks have been removed.
+ * If filesystem cannot be determined, we will execute
+ * "fsck.auto". Currently this also happens if you specify
+ * UUID=xxx or LABEL=xxx as an object to check.
+ * Detection code for that is also probably has to be in fsck.auto.
+ *
+ * In other words, this is _really_ is just a driver program which
+ * spawns actual fsck.something for each filesystem to check.
+ * It doesn't guess filesystem types from on-disk format.
+ */
+
+#include "libbb.h"
+
+/* "progress indicator" code is somewhat buggy and ext[23] specific.
+ * We should be filesystem agnostic. IOW: there should be a well-defined
+ * API for fsck.something, NOT ad-hoc hacks in generic fsck. */
+#define DO_PROGRESS_INDICATOR 0
+
+#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 */
+
+/*
+ * Internal structure for mount table entries.
+ */
+
+struct fs_info {
+       struct fs_info *next;
+       char    *device;
+       char    *mountpt;
+       char    *type;
+       char    *opts;
+       int     passno;
+       int     flags;
+};
+
+#define FLAG_DONE 1
+#define FLAG_PROGRESS 2
+/*
+ * Structure to allow exit codes to be stored
+ */
+struct fsck_instance {
+       struct fsck_instance *next;
+       int     pid;
+       int     flags;
+#if DO_PROGRESS_INDICATOR
+       time_t  start_time;
+#endif
+       char    *prog;
+       char    *device;
+       char    *base_device; /* /dev/hda for /dev/hdaN etc */
+};
+
+static const char ignored_types[] ALIGN1 =
+       "ignore\0"
+       "iso9660\0"
+       "nfs\0"
+       "proc\0"
+       "sw\0"
+       "swap\0"
+       "tmpfs\0"
+       "devpts\0";
+
+#if 0
+static const char really_wanted[] ALIGN1 =
+       "minix\0"
+       "ext2\0"
+       "ext3\0"
+       "jfs\0"
+       "reiserfs\0"
+       "xiafs\0"
+       "xfs\0";
+#endif
+
+#define BASE_MD "/dev/md"
+
+static char **devices;
+static char **args;
+static int num_devices;
+static int num_args;
+static int verbose;
+
+#define FS_TYPE_FLAG_NORMAL 0
+#define FS_TYPE_FLAG_OPT    1
+#define FS_TYPE_FLAG_NEGOPT 2
+static char **fs_type_list;
+static uint8_t *fs_type_flag;
+static smallint fs_type_negated;
+
+static volatile smallint cancel_requested;
+static smallint doall;
+static smallint noexecute;
+static smallint serialize;
+static smallint skip_root;
+/* static smallint like_mount; */
+static smallint notitle;
+static smallint parallel_root;
+static smallint force_all_parallel;
+
+#if DO_PROGRESS_INDICATOR
+static smallint progress;
+static int progress_fd;
+#endif
+
+static int num_running;
+static int max_running;
+static char *fstype;
+static struct fs_info *filesys_info;
+static struct fs_info *filesys_last;
+static struct fsck_instance *instance_list;
+
+/*
+ * 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.
+ */
+#if ENABLE_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", NULL
+};
+#endif
+
+static char *base_device(const char *device)
+{
+       char *str, *cp;
+#if ENABLE_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' || !isdigit(cp[1])
+                || cp[2] != 'd' || !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;
+       }
+
+#if ENABLE_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 void free_instance(struct fsck_instance *p)
+{
+       free(p->prog);
+       free(p->device);
+       free(p->base_device);
+       free(p);
+}
+
+static struct fs_info *create_fs_device(const char *device, const char *mntpnt,
+                                       const char *type, const char *opts,
+                                       int passno)
+{
+       struct fs_info *fs;
+
+       fs = xzalloc(sizeof(*fs));
+       fs->device = xstrdup(device);
+       fs->mountpt = xstrdup(mntpnt);
+       if (strchr(type, ','))
+               type = (char *)"auto";
+       fs->type = xstrdup(type);
+       fs->opts = xstrdup(opts ? opts : "");
+       fs->passno = passno < 0 ? 1 : passno;
+       /*fs->flags = 0; */
+       /*fs->next = NULL; */
+
+       if (!filesys_info)
+               filesys_info = fs;
+       else
+               filesys_last->next = fs;
+       filesys_last = fs;
+
+       return fs;
+}
+
+/* Load the filesystem database from /etc/fstab */
+static void load_fs_info(const char *filename)
+{
+       FILE *fstab;
+       struct mntent mte;
+       struct fs_info *fs;
+
+       fstab = setmntent(filename, "r");
+       if (!fstab) {
+               bb_perror_msg("cannot read %s", filename);
+               return;
+       }
+
+       // Loop through entries
+       while (getmntent_r(fstab, &mte, bb_common_bufsiz1, COMMON_BUFSIZE)) {
+               //bb_info_msg("CREATE[%s][%s][%s][%s][%d]", mte.mnt_fsname, mte.mnt_dir,
+               //      mte.mnt_type, mte.mnt_opts,
+               //      mte.mnt_passno);
+               fs = create_fs_device(mte.mnt_fsname, mte.mnt_dir,
+                       mte.mnt_type, mte.mnt_opts,
+                       mte.mnt_passno);
+       }
+       endmntent(fstab);
+}
+
+/* Lookup filesys in /etc/fstab and return the corresponding entry. */
+static struct fs_info *lookup(char *filesys)
+{
+       struct fs_info *fs;
+
+       for (fs = filesys_info; fs; fs = fs->next) {
+               if (strcmp(filesys, fs->device) == 0
+                || (fs->mountpt && strcmp(filesys, fs->mountpt) == 0)
+               )
+                       break;
+       }
+
+       return fs;
+}
+
+#if DO_PROGRESS_INDICATOR
+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;
+}
+#endif
+
+
+/*
+ * Send a signal to all outstanding fsck child processes
+ */
+static void kill_all_if_cancel_requested(void)
+{
+       static smallint kill_sent;
+
+       struct fsck_instance *inst;
+
+       if (!cancel_requested || kill_sent)
+               return;
+
+       for (inst = instance_list; inst; inst = inst->next) {
+               if (inst->flags & FLAG_DONE)
+                       continue;
+               kill(inst->pid, SIGTERM);
+       }
+       kill_sent = 1;
+}
+
+/*
+ * Wait for one child process to exit; when it does, unlink it from
+ * the list of executing child processes, free, and return its exit status.
+ * If there is no exited child, return -1.
+ */
+static int wait_one(int flags)
+{
+       int status;
+       int sig;
+       struct fsck_instance *inst, *prev;
+       pid_t pid;
+
+       if (!instance_list)
+               return -1;
+       /* if (noexecute) { already returned -1; } */
+
+       while (1) {
+               pid = waitpid(-1, &status, flags);
+               kill_all_if_cancel_requested();
+               if (pid == 0) /* flags == WNOHANG and no children exited */
+                       return -1;
+               if (pid < 0) {
+                       if (errno == EINTR)
+                               continue;
+                       if (errno == ECHILD) { /* paranoia */
+                               bb_error_msg("wait: no more children");
+                               return -1;
+                       }
+                       bb_perror_msg("wait");
+                       continue;
+               }
+               prev = NULL;
+               inst = instance_list;
+               do {
+                       if (inst->pid == pid)
+                               goto child_died;
+                       prev = inst;
+                       inst = inst->next;
+               } while (inst);
+       }
+ child_died:
+
+       if (WIFEXITED(status))
+               status = WEXITSTATUS(status);
+       else if (WIFSIGNALED(status)) {
+               sig = WTERMSIG(status);
+               status = EXIT_UNCORRECTED;
+               if (sig != SIGINT) {
+                       printf("Warning: %s %s terminated "
+                               "by 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;
+       }
+
+#if DO_PROGRESS_INDICATOR
+       if (progress && (inst->flags & FLAG_PROGRESS) && !progress_active()) {
+               struct fsck_instance *inst2;
+               for (inst2 = instance_list; inst2; inst2 = inst2->next) {
+                       if (inst2->flags & FLAG_DONE)
+                               continue;
+                       if (strcmp(inst2->type, "ext2") != 0
+                        && strcmp(inst2->type, "ext3") != 0
+                       ) {
+                               continue;
+                       }
+                       /* ext[23], we will send USR1
+                        * (request to start displaying progress bar)
+                        *
+                        * 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(NULL) - 1)
+                               sleep(1);
+                       kill(inst2->pid, SIGUSR1);
+                       inst2->flags |= FLAG_PROGRESS;
+                       break;
+               }
+       }
+#endif
+
+       if (prev)
+               prev->next = inst->next;
+       else
+               instance_list = inst->next;
+       if (verbose > 1)
+               printf("Finished with %s (exit status %d)\n",
+                      inst->device, status);
+       num_running--;
+       free_instance(inst);
+
+       return status;
+}
+
+/*
+ * Wait until all executing child processes have exited; return the
+ * logical OR of all of their exit code values.
+ */
+#define FLAG_WAIT_ALL           0
+#define FLAG_WAIT_ATLEAST_ONE   WNOHANG
+static int wait_many(int flags)
+{
+       int exit_status;
+       int global_status = 0;
+       int wait_flags = 0;
+
+       while ((exit_status = wait_one(wait_flags)) != -1) {
+               global_status |= exit_status;
+               wait_flags |= flags;
+       }
+       return global_status;
+}
+
+/*
+ * Execute a particular fsck program, and link it into the list of
+ * child processes we are waiting for.
+ */
+static void execute(const char *type, const char *device,
+               const char *mntpt /*, int interactive */)
+{
+       char *argv[num_args + 4]; /* see count below: */
+       int argc;
+       int i;
+       struct fsck_instance *inst;
+       pid_t pid;
+
+       argv[0] = xasprintf("fsck.%s", type); /* 1 */
+       for (i = 0; i < num_args; i++)
+               argv[i+1] = args[i]; /* num_args */
+       argc = num_args + 1;
+
+#if DO_PROGRESS_INDICATOR
+       if (progress && !progress_active()) {
+               if (strcmp(type, "ext2") == 0
+                || strcmp(type, "ext3") == 0
+               ) {
+                       argv[argc++] = xasprintf("-C%d", progress_fd); /* 1 */
+                       inst->flags |= FLAG_PROGRESS;
+               }
+       }
+#endif
+
+       argv[argc++] = (char*)device; /* 1 */
+       argv[argc] = NULL; /* 1 */
+
+       if (verbose || noexecute) {
+               printf("[%s (%d) -- %s]", argv[0], num_running,
+                                       mntpt ? mntpt : device);
+               for (i = 0; i < argc; i++)
+                       printf(" %s", argv[i]);
+               bb_putchar('\n');
+       }
+
+       /* Fork and execute the correct program. */
+       pid = -1;
+       if (!noexecute) {
+               pid = spawn(argv);
+               if (pid < 0)
+                       bb_simple_perror_msg(argv[0]);
+       }
+
+#if DO_PROGRESS_INDICATOR
+       free(argv[num_args + 1]);
+#endif
+
+       /* No child, so don't record an instance */
+       if (pid <= 0) {
+               free(argv[0]);
+               return;
+       }
+
+       inst = xzalloc(sizeof(*inst));
+       inst->pid = pid;
+       inst->prog = argv[0];
+       inst->device = xstrdup(device);
+       inst->base_device = base_device(device);
+#if DO_PROGRESS_INDICATOR
+       inst->start_time = time(NULL);
+#endif
+
+       /* Add to the list of running fsck's.
+        * (was adding to the end, but adding to the front is simpler...) */
+       inst->next = instance_list;
+       instance_list = inst;
+}
+
+/*
+ * 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 "auto".
+ */
+static void fsck_device(struct fs_info *fs /*, int interactive */)
+{
+       const char *type;
+
+       if (strcmp(fs->type, "auto") != 0) {
+               type = fs->type;
+               if (verbose > 2)
+                       bb_info_msg("using filesystem type '%s' %s",
+                                       type, "from fstab");
+       } else if (fstype
+        && (fstype[0] != 'n' || fstype[1] != 'o') /* != "no" */
+        && strncmp(fstype, "opts=", 5) != 0
+        && strncmp(fstype, "loop", 4) != 0
+        && !strchr(fstype, ',')
+       ) {
+               type = fstype;
+               if (verbose > 2)
+                       bb_info_msg("using filesystem type '%s' %s",
+                                       type, "from -t");
+       } else {
+               type = "auto";
+               if (verbose > 2)
+                       bb_info_msg("using filesystem type '%s' %s",
+                                       type, "(default)");
+       }
+
+       num_running++;
+       execute(type, fs->device, fs->mountpt /*, interactive */);
+}
+
+/*
+ * 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 != NULL);
+
+       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;
+}
+
+/*
+ * 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 *s;
+       int len;
+
+       if (!optlist)
+               return 0;
+
+       len = strlen(opt);
+       s = optlist - 1;
+       while (1) {
+               s = strstr(s + 1, opt);
+               if (!s)
+                       return 0;
+               /* neither "opt.." nor "xxx,opt.."? */
+               if (s != optlist && s[-1] != ',')
+                       continue;
+               /* neither "..opt" nor "..opt,xxx"? */
+               if (s[len] != '\0' && s[len] != ',')
+                       continue;
+               return 1;
+       }
+}
+
+/* See if the filesystem matches the criteria given by the -t option */
+static int fs_match(struct fs_info *fs)
+{
+       int n, ret, checked_type;
+       char *cp;
+
+       if (!fs_type_list)
+               return 1;
+
+       ret = 0;
+       checked_type = 0;
+       n = 0;
+       while (1) {
+               cp = fs_type_list[n];
+               if (!cp)
+                       break;
+               switch (fs_type_flag[n]) {
+               case FS_TYPE_FLAG_NORMAL:
+                       checked_type++;
+                       if (strcmp(cp, fs->type) == 0)
+                               ret = 1;
+                       break;
+               case FS_TYPE_FLAG_NEGOPT:
+                       if (opt_in_list(cp, fs->opts))
+                               return 0;
+                       break;
+               case FS_TYPE_FLAG_OPT:
+                       if (!opt_in_list(cp, fs->opts))
+                               return 0;
+                       break;
+               }
+               n++;
+       }
+       if (checked_type == 0)
+               return 1;
+
+       return (fs_type_negated ? !ret : ret);
+}
+
+/* Check if we should ignore this filesystem. */
+static int ignore(struct fs_info *fs)
+{
+       /*
+        * If the pass number is 0, ignore it.
+        */
+       if (fs->passno == 0)
+               return 1;
+
+       /*
+        * If a specific fstype is specified, and it doesn't match,
+        * ignore it.
+        */
+       if (!fs_match(fs))
+               return 1;
+
+       /* Are we ignoring this type? */
+       if (index_in_strings(ignored_types, fs->type) >= 0)
+               return 1;
+
+       /* We can and want to check this file system type. */
+       return 0;
+}
+
+/* Check all file systems, using the /etc/fstab table. */
+static int check_all(void)
+{
+       struct fs_info *fs;
+       int status = EXIT_OK;
+       smallint not_done_yet;
+       smallint pass_done;
+       int passno;
+
+       if (verbose)
+               puts("Checking all filesystems");
+
+       /*
+        * 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 (LONE_CHAR(fs->mountpt, '/')) {
+                               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;
+                               break;
+                       }
+               }
+       }
+       /*
+        * This is for the bone-headed user who has root
+        * filesystem listed twice.
+        * "Skip root" will skip _all_ root entries.
+        */
+       if (skip_root)
+               for (fs = filesys_info; fs; fs = fs->next)
+                       if (LONE_CHAR(fs->mountpt, '/'))
+                               fs->flags |= FLAG_DONE;
+
+       not_done_yet = 1;
+       passno = 1;
+       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 didn't
+                        * do it yet.
+                        */
+                       if (fs->passno > passno) {
+                               not_done_yet = 1;
+                               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)
+                               puts("----------------------------------");
+                       passno++;
+               } else
+                       not_done_yet = 1;
+       }
+       kill_all_if_cancel_requested();
+       status |= wait_many(FLAG_WAIT_ATLEAST_ONE);
+       return status;
+}
+
+/*
+ * Deal with the fsck -t argument.
+ * Huh, for mount "-t novfat,nfs" means "neither vfat nor nfs"!
+ * Why here we require "-t novfat,nonfs" ??
+ */
+static void compile_fs_type(char *fs_type)
+{
+       char *s;
+       int num = 2;
+       smallint negate;
+
+       s = fs_type;
+       while ((s = strchr(s, ','))) {
+               num++;
+               s++;
+       }
+
+       fs_type_list = xzalloc(num * sizeof(fs_type_list[0]));
+       fs_type_flag = xzalloc(num * sizeof(fs_type_flag[0]));
+       fs_type_negated = -1; /* not yet known is it negated or not */
+
+       num = 0;
+       s = fs_type;
+       while (1) {
+               char *comma;
+
+               negate = 0;
+               if (s[0] == 'n' && s[1] == 'o') { /* "no.." */
+                       s += 2;
+                       negate = 1;
+               } else if (s[0] == '!') {
+                       s++;
+                       negate = 1;
+               }
+
+               if (strcmp(s, "loop") == 0)
+                       /* loop is really short-hand for opts=loop */
+                       goto loop_special_case;
+               if (strncmp(s, "opts=", 5) == 0) {
+                       s += 5;
+ loop_special_case:
+                       fs_type_flag[num] = negate ? FS_TYPE_FLAG_NEGOPT : FS_TYPE_FLAG_OPT;
+               } else {
+                       if (fs_type_negated == -1)
+                               fs_type_negated = negate;
+                       if (fs_type_negated != negate)
+                               bb_error_msg_and_die(
+"either all or none of the filesystem types passed to -t must be prefixed "
+"with 'no' or '!'");
+               }
+               comma = strchr(s, ',');
+               fs_type_list[num++] = comma ? xstrndup(s, comma-s) : xstrdup(s);
+               if (!comma)
+                       break;
+               s = comma + 1;
+       }
+}
+
+static void parse_args(char **argv)
+{
+       int i, j;
+       char *arg, *tmp;
+       char *options;
+       int optpos;
+       int opts_for_fsck = 0;
+
+       /* in bss, so already zeroed
+       num_devices = 0;
+       num_args = 0;
+       instance_list = NULL;
+       */
+
+       for (i = 1; argv[i]; i++) {
+               arg = argv[i];
+
+               /* "/dev/blk" or "/path" or "UUID=xxx" or "LABEL=xxx" */
+               if ((arg[0] == '/' && !opts_for_fsck) || strchr(arg, '=')) {
+// FIXME: must check that arg is a blkdev, or resolve
+// "/path", "UUID=xxx" or "LABEL=xxx" into block device name
+// ("UUID=xxx"/"LABEL=xxx" can probably shifted to fsck.auto duties)
+                       devices = xrealloc_vector(devices, 2, num_devices);
+                       devices[num_devices++] = xstrdup(arg);
+                       continue;
+               }
+
+               if (arg[0] != '-' || opts_for_fsck) {
+                       args = xrealloc_vector(args, 2, num_args);
+                       args[num_args++] = xstrdup(arg);
+                       continue;
+               }
+
+               if (LONE_CHAR(arg + 1, '-')) { /* "--" ? */
+                       opts_for_fsck = 1;
+                       continue;
+               }
+
+               optpos = 0;
+               options = NULL;
+               for (j = 1; arg[j]; j++) {
+                       switch (arg[j]) {
+                       case 'A':
+                               doall = 1;
+                               break;
+#if DO_PROGRESS_INDICATOR
+                       case 'C':
+                               progress = 1;
+                               if (arg[++j]) { /* -Cn */
+                                       progress_fd = xatoi_u(&arg[j]);
+                                       goto next_arg;
+                               }
+                               /* -C n */
+                               if (!argv[++i]) bb_show_usage();
+                               progress_fd = xatoi_u(argv[i]);
+                               goto next_arg;
+#endif
+                       case 'V':
+                               verbose++;
+                               break;
+                       case 'N':
+                               noexecute = 1;
+                               break;
+                       case 'R':
+                               skip_root = 1;
+                               break;
+                       case 'T':
+                               notitle = 1;
+                               break;
+/*                     case 'M':
+                               like_mount = 1;
+                               break; */
+                       case 'P':
+                               parallel_root = 1;
+                               break;
+                       case 's':
+                               serialize = 1;
+                               break;
+                       case 't':
+                               if (fstype)
+                                       bb_show_usage();
+                               if (arg[++j])
+                                       tmp = &arg[j];
+                               else if (argv[++i])
+                                       tmp = argv[i];
+                               else
+                                       bb_show_usage();
+                               fstype = xstrdup(tmp);
+                               compile_fs_type(fstype);
+                               goto next_arg;
+                       case '?':
+                               bb_show_usage();
+                               break;
+                       default:
+                               optpos++;
+                               /* one extra for '\0' */
+                               options = xrealloc(options, optpos + 2);
+                               options[optpos] = arg[j];
+                               break;
+                       }
+               }
+ next_arg:
+               if (optpos) {
+                       options[0] = '-';
+                       options[optpos + 1] = '\0';
+                       args = xrealloc_vector(args, 2, num_args);
+                       args[num_args++] = options;
+               }
+       }
+       if (getenv("FSCK_FORCE_ALL_PARALLEL"))
+               force_all_parallel = 1;
+       tmp = getenv("FSCK_MAX_INST");
+       if (tmp)
+               max_running = xatoi(tmp);
+}
+
+static void signal_cancel(int sig UNUSED_PARAM)
+{
+       cancel_requested = 1;
+}
+
+int fsck_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int fsck_main(int argc UNUSED_PARAM, char **argv)
+{
+       int i, status;
+       /*int interactive;*/
+       const char *fstab;
+       struct fs_info *fs;
+
+       /* we want wait() to be interruptible */
+       signal_no_SA_RESTART_empty_mask(SIGINT, signal_cancel);
+       signal_no_SA_RESTART_empty_mask(SIGTERM, signal_cancel);
+
+       setbuf(stdout, NULL);
+
+       parse_args(argv);
+
+       if (!notitle)
+               puts("fsck (busybox "BB_VER", "BB_BT")");
+
+       /* Even plain "fsck /dev/hda1" needs fstab to get fs type,
+        * so we are scanning it anyway */
+       fstab = getenv("FSTAB_FILE");
+       if (!fstab)
+               fstab = "/etc/fstab";
+       load_fs_info(fstab);
+
+       /*interactive = (num_devices == 1) | serialize;*/
+
+       if (num_devices == 0)
+               /*interactive =*/ serialize = doall = 1;
+       if (doall)
+               return check_all();
+
+       status = 0;
+       for (i = 0; i < num_devices; i++) {
+               if (cancel_requested) {
+                       kill_all_if_cancel_requested();
+                       break;
+               }
+
+               fs = lookup(devices[i]);
+               if (!fs)
+                       fs = create_fs_device(devices[i], "", "auto", NULL, -1);
+               fsck_device(fs /*, interactive */);
+
+               if (serialize
+                || (max_running && (num_running >= max_running))
+               ) {
+                       int exit_status = wait_one(0);
+                       if (exit_status >= 0)
+                               status |= exit_status;
+                       if (verbose > 1)
+                               puts("----------------------------------");
+               }
+       }
+       status |= wait_many(FLAG_WAIT_ALL);
+       return status;
+}
diff --git a/e2fsprogs/lsattr.c b/e2fsprogs/lsattr.c
new file mode 100644 (file)
index 0000000..23a54b7
--- /dev/null
@@ -0,0 +1,109 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * lsattr.c            - List file attributes on an ext2 file system
+ *
+ * Copyright (C) 1993, 1994  Remy Card <card@masi.ibp.fr>
+ *                           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 "libbb.h"
+#include "e2fs_lib.h"
+
+enum {
+       OPT_RECUR      = 0x1,
+       OPT_ALL        = 0x2,
+       OPT_DIRS_OPT   = 0x4,
+       OPT_PF_LONG    = 0x8,
+       OPT_GENERATION = 0x10,
+};
+
+static void list_attributes(const char *name)
+{
+       unsigned long fsflags;
+       unsigned long generation;
+
+       if (fgetflags(name, &fsflags) != 0)
+               goto read_err;
+
+       if (option_mask32 & OPT_GENERATION) {
+               if (fgetversion(name, &generation) != 0)
+                       goto read_err;
+               printf("%5lu ", generation);
+       }
+
+       if (option_mask32 & OPT_PF_LONG) {
+               printf("%-28s ", name);
+               print_e2flags(stdout, fsflags, PFOPT_LONG);
+               bb_putchar('\n');
+       } else {
+               print_e2flags(stdout, fsflags, 0);
+               printf(" %s\n", name);
+       }
+
+       return;
+ read_err:
+       bb_perror_msg("reading %s", name);
+}
+
+static int lsattr_dir_proc(const char *dir_name, struct dirent *de,
+                          void *private UNUSED_PARAM)
+{
+       struct stat st;
+       char *path;
+
+       path = concat_path_file(dir_name, de->d_name);
+
+       if (lstat(path, &st) != 0)
+               bb_perror_msg("stat %s", path);
+       else if (de->d_name[0] != '.' || (option_mask32 & OPT_ALL)) {
+               list_attributes(path);
+               if (S_ISDIR(st.st_mode) && (option_mask32 & OPT_RECUR)
+                && !DOT_OR_DOTDOT(de->d_name)
+               ) {
+                       printf("\n%s:\n", path);
+                       iterate_on_dir(path, lsattr_dir_proc, NULL);
+                       bb_putchar('\n');
+               }
+       }
+
+       free(path);
+       return 0;
+}
+
+static void lsattr_args(const char *name)
+{
+       struct stat st;
+
+       if (lstat(name, &st) == -1) {
+               bb_perror_msg("stat %s", name);
+       } else if (S_ISDIR(st.st_mode) && !(option_mask32 & OPT_DIRS_OPT)) {
+               iterate_on_dir(name, lsattr_dir_proc, NULL);
+       } else {
+               list_attributes(name);
+       }
+}
+
+int lsattr_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int lsattr_main(int argc UNUSED_PARAM, char **argv)
+{
+       getopt32(argv, "Radlv");
+       argv += optind;
+
+       if (!*argv)
+               *--argv = (char*)".";
+       do lsattr_args(*argv++); while (*argv);
+
+       return EXIT_SUCCESS;
+}
diff --git a/e2fsprogs/old_e2fsprogs/Config.in b/e2fsprogs/old_e2fsprogs/Config.in
new file mode 100644 (file)
index 0000000..5990f55
--- /dev/null
@@ -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/old_e2fsprogs/Kbuild b/e2fsprogs/old_e2fsprogs/Kbuild
new file mode 100644 (file)
index 0000000..b05bb92
--- /dev/null
@@ -0,0 +1,16 @@
+# 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_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/old_e2fsprogs/README b/e2fsprogs/old_e2fsprogs/README
new file mode 100644 (file)
index 0000000..fac0901
--- /dev/null
@@ -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/old_e2fsprogs/blkid/Kbuild b/e2fsprogs/old_e2fsprogs/blkid/Kbuild
new file mode 100644 (file)
index 0000000..ddcfdfd
--- /dev/null
@@ -0,0 +1,23 @@
+# 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.
+
+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/old_e2fsprogs/blkid/blkid.h b/e2fsprogs/old_e2fsprogs/blkid/blkid.h
new file mode 100644 (file)
index 0000000..9a3c2af
--- /dev/null
@@ -0,0 +1,104 @@
+/* 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 1
+
+#include <sys/types.h>
+#include <linux/types.h>
+
+#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
diff --git a/e2fsprogs/old_e2fsprogs/blkid/blkidP.h b/e2fsprogs/old_e2fsprogs/blkid/blkidP.h
new file mode 100644 (file)
index 0000000..d6b2b42
--- /dev/null
@@ -0,0 +1,186 @@
+/* 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 1
+
+#include <sys/types.h>
+#include <stdio.h>
+
+#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 <stdio.h>
+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
diff --git a/e2fsprogs/old_e2fsprogs/blkid/blkid_getsize.c b/e2fsprogs/old_e2fsprogs/blkid/blkid_getsize.c
new file mode 100644 (file)
index 0000000..941efa4
--- /dev/null
@@ -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 <stdio.h>
+#include <unistd.h>
+#ifdef HAVE_ERRNO_H
+#include <errno.h>
+#endif
+#include <fcntl.h>
+#ifdef HAVE_SYS_IOCTL_H
+#include <sys/ioctl.h>
+#endif
+#ifdef HAVE_LINUX_FD_H
+#include <linux/fd.h>
+#endif
+#ifdef HAVE_SYS_DISKLABEL_H
+#include <sys/disklabel.h>
+#include <sys/stat.h>
+#endif
+#ifdef HAVE_SYS_DISK_H
+#ifdef HAVE_SYS_QUEUE_H
+#include <sys/queue.h> /* for LIST_HEAD */
+#endif
+#include <sys/disk.h>
+#endif
+#ifdef __linux__
+#include <sys/utsname.h>
+#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/old_e2fsprogs/blkid/cache.c b/e2fsprogs/old_e2fsprogs/blkid/cache.c
new file mode 100644 (file)
index 0000000..d1d2914
--- /dev/null
@@ -0,0 +1,125 @@
+/* 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 <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#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"));
+
+       cache = xzalloc(sizeof(struct blkid_struct_cache));
+
+       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/old_e2fsprogs/blkid/dev.c b/e2fsprogs/old_e2fsprogs/blkid/dev.c
new file mode 100644 (file)
index 0000000..bb0cc91
--- /dev/null
@@ -0,0 +1,213 @@
+/* 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 <stdlib.h>
+#include <string.h>
+
+#include "blkidP.h"
+
+blkid_dev blkid_new_dev(void)
+{
+       blkid_dev dev;
+
+       dev = xzalloc(sizeof(struct blkid_struct_dev));
+
+       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");
+       }
+       bb_putchar('\n');
+}
+#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 <getopt.h>
+#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/old_e2fsprogs/blkid/devname.c b/e2fsprogs/old_e2fsprogs/blkid/devname.c
new file mode 100644 (file)
index 0000000..348e5d4
--- /dev/null
@@ -0,0 +1,367 @@
+/* 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 <stdio.h>
+#include <string.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#ifdef HAVE_SYS_TYPES_H
+#include <sys/types.h>
+#endif
+#include <sys/stat.h>
+#ifdef HAVE_ERRNO_H
+#include <errno.h>
+#endif
+#ifdef HAVE_SYS_MKDEV_H
+#include <sys/mkdev.h>
+#endif
+#include <time.h>
+
+#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;
+}
+
+#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 <dirent.h>
+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_for_read(lvm_device)) == 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 (LONE_CHAR(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 (LONE_CHAR(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_for_read(PROC_EVMS_VOLUMES);
+       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(NULL) - 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_for_read(PROC_PARTITIONS);
+       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(NULL);
+       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/old_e2fsprogs/blkid/devno.c b/e2fsprogs/old_e2fsprogs/blkid/devno.c
new file mode 100644 (file)
index 0000000..ae326f8
--- /dev/null
@@ -0,0 +1,222 @@
+/* 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 <stdio.h>
+#include <string.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#include <stdlib.h>
+#include <string.h>
+#ifdef HAVE_SYS_TYPES_H
+#include <sys/types.h>
+#endif
+#include <sys/stat.h>
+#include <dirent.h>
+#ifdef HAVE_ERRNO_H
+#include <errno.h>
+#endif
+#ifdef HAVE_SYS_MKDEV_H
+#include <sys/mkdev.h>
+#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);
+}
+
+/* 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/old_e2fsprogs/blkid/list.c b/e2fsprogs/old_e2fsprogs/blkid/list.c
new file mode 100644 (file)
index 0000000..04d61a1
--- /dev/null
@@ -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/old_e2fsprogs/blkid/list.h b/e2fsprogs/old_e2fsprogs/blkid/list.h
new file mode 100644 (file)
index 0000000..a24baaa
--- /dev/null
@@ -0,0 +1,73 @@
+/* vi: set sw=4 ts=4: */
+#if !defined(_BLKID_LIST_H) && !defined(LIST_HEAD)
+#define BLKID_LIST_H 1
+
+#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
diff --git a/e2fsprogs/old_e2fsprogs/blkid/probe.c b/e2fsprogs/old_e2fsprogs/blkid/probe.c
new file mode 100644 (file)
index 0000000..1f7188e
--- /dev/null
@@ -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 <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#ifdef HAVE_SYS_STAT_H
+#include <sys/stat.h>
+#endif
+#ifdef HAVE_SYS_MKDEV_H
+#include <sys/mkdev.h>
+#endif
+#ifdef HAVE_ERRNO_H
+#include <errno.h>
+#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 = 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(NULL);
+       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 = 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(NULL);
+               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/old_e2fsprogs/blkid/probe.h b/e2fsprogs/old_e2fsprogs/blkid/probe.h
new file mode 100644 (file)
index 0000000..b6d8f8e
--- /dev/null
@@ -0,0 +1,374 @@
+/* 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 1
+
+#include <linux/types.h>
+
+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/old_e2fsprogs/blkid/read.c b/e2fsprogs/old_e2fsprogs/blkid/read.c
new file mode 100644 (file)
index 0000000..67bc8ee
--- /dev/null
@@ -0,0 +1,461 @@
+/* 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 <stdio.h>
+#include <ctype.h>
+#include <string.h>
+#include <time.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <errno.h>
+
+#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 <stdlib.h>
+
+#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="value"> ...]>device_name</device>
+ *
+ *     The following tags are required for each entry:
+ *     <ID="id">       unique (within this file) ID number of this device
+ *     <TIME="time">   (ascii time_t) time this entry was last read from disk
+ *     <TYPE="type">   (detected) type of filesystem/data for this partition
+ *
+ *     The following tags may be present, depending on the device contents
+ *     <LABEL="label"> (user supplied) label (volume name, etc)
+ *     <UUID="uuid">   (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 "<device" return 1 -> continue parsing line
+ * line starts with "<foo", empty, or # return 0 -> 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, "<device", 7)) {
+               DBG(DEBUG_READ, printf("found device header: %8s\n", p));
+               p += 7;
+
+               *cp = p;
+               return 1;
+       }
+
+       if (*p == '<')
+               return 0;
+
+       return -BLKID_ERR_CACHE;
+}
+
+/* Consume the remaining XML on the line (cosmetic only) */
+static int parse_end(char **cp)
+{
+       *cp = skip_over_blank(*cp);
+
+       if (!strncmp(*cp, "</device>", 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:
+ * <device foo=bar>devname</device>
+ * <device>devname<foo>bar</foo></device>
+ */
+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 </device> 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 <NAME>value</NAME> 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 <device [TAG="value" ...]>/dev/foo</device>
+ *
+ * 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);
+}
+
+#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");
+       }
+       bb_putchar('\n');
+}
+
+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/old_e2fsprogs/blkid/resolve.c b/e2fsprogs/old_e2fsprogs/blkid/resolve.c
new file mode 100644 (file)
index 0000000..7942de2
--- /dev/null
@@ -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 <stdio.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#include <stdlib.h>
+#include <fcntl.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#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 : "<missing>");
+       } else {
+               value = blkid_get_devname(cache, argv[1], NULL);
+               printf("%s has tag %s\n", value ? value : "<none>", argv[1]);
+       }
+       blkid_put_cache(cache);
+       return value ? 0 : 1;
+}
+#endif
diff --git a/e2fsprogs/old_e2fsprogs/blkid/save.c b/e2fsprogs/old_e2fsprogs/blkid/save.c
new file mode 100644 (file)
index 0000000..3600260
--- /dev/null
@@ -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 <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/types.h>
+#ifdef HAVE_SYS_STAT_H
+#include <sys/stat.h>
+#endif
+#ifdef HAVE_SYS_MKDEV_H
+#include <sys/mkdev.h>
+#endif
+#ifdef HAVE_ERRNO_H
+#include <errno.h>
+#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,
+               "<device DEVNO=\"0x%04lx\" TIME=\"%lu\"",
+               (unsigned long) dev->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</device>\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_for_write(filename);
+               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/old_e2fsprogs/blkid/tag.c b/e2fsprogs/old_e2fsprogs/blkid/tag.c
new file mode 100644 (file)
index 0000000..c0a93df
--- /dev/null
@@ -0,0 +1,431 @@
+/* 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 <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+#include "blkidP.h"
+
+static blkid_tag blkid_new_tag(void)
+{
+       blkid_tag tag;
+
+       tag = xzalloc(sizeof(struct blkid_struct_tag));
+
+       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 <getopt.h>
+#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/old_e2fsprogs/chattr.c b/e2fsprogs/old_e2fsprogs/chattr.c
new file mode 100644 (file)
index 0000000..ae39d92
--- /dev/null
@@ -0,0 +1,220 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * chattr.c            - Change file attributes on an ext2 file system
+ *
+ * Copyright (C) 1993, 1994  Remy Card <card@masi.ibp.fr>
+ *                           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 <sys/types.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/param.h>
+#include <sys/stat.h>
+#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 = &af;
+       } 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] || (de->d_name[1] == '.' && !de->d_name[2]))
+       ) {
+               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) MAIN_EXTERNALLY_VISIBLE;
+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 <version> */
+               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/old_e2fsprogs/e2fsbb.h b/e2fsprogs/old_e2fsprogs/e2fsbb.h
new file mode 100644 (file)
index 0000000..d31c319
--- /dev/null
@@ -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<<ERRCODE_RANGE)-1)))
+
+/* header defines */
+#define ENABLE_HTREE 1
+#define HAVE_ERRNO_H 1
+#define HAVE_EXT2_IOCTLS 1
+#define HAVE_LINUX_FD_H 1
+#define HAVE_MNTENT_H 1
+#define HAVE_NETINET_IN_H 1
+#define HAVE_NET_IF_H 1
+#define HAVE_SYS_IOCTL_H 1
+#define HAVE_SYS_MOUNT_H 1
+#define HAVE_SYS_QUEUE_H 1
+#define HAVE_SYS_STAT_H 1
+#define HAVE_SYS_TYPES_H 1
+#define HAVE_UNISTD_H 1
+
+/* Endianness */
+#if BB_BIG_ENDIAN
+#define ENABLE_SWAPFS 1
+#define WORDS_BIGENDIAN 1
+#endif
+
+#endif
diff --git a/e2fsprogs/old_e2fsprogs/e2fsck.c b/e2fsprogs/old_e2fsprogs/e2fsck.c
new file mode 100644 (file)
index 0000000..d1f8d1e
--- /dev/null
@@ -0,0 +1,13548 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * e2fsck
+ *
+ * Copyright (C) 1993, 1994, 1995, 1996, 1997 Theodore Ts'o.
+ * Copyright (C) 2006 Garrett Kajmowicz
+ *
+ * Dictionary Abstract Data Type
+ * Copyright (C) 1997 Kaz Kylheku <kaz@ashi.footprints.net>
+ * 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 <sct@redhat.com>, 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 <kaz@ashi.footprints.net>
+ *
+ * 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(NULL);
+       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);
+}
+
+/*
+ * 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      <blk>                   block number
+ *      %B      <blkcount>              integer
+ *      %c      <blk2>                  block number
+ *      %Di     <dirent>->ino           inode number
+ *      %Dn     <dirent>->name          string
+ *      %Dr     <dirent>->rec_len
+ *      %Dl     <dirent>->name_len
+ *      %Dt     <dirent>->filetype
+ *      %d      <dir>                   inode number
+ *      %g      <group>                 integer
+ *      %i      <ino>                   inode number
+ *      %Is     <inode> -> i_size
+ *      %IS     <inode> -> i_extra_isize
+ *      %Ib     <inode> -> i_blocks
+ *      %Il     <inode> -> i_links_count
+ *      %Im     <inode> -> i_mode
+ *      %IM     <inode> -> i_mtime
+ *      %IF     <inode> -> i_faddr
+ *      %If     <inode> -> i_file_acl
+ *      %Id     <inode> -> i_dir_acl
+ *      %Iu     <inode> -> i_uid
+ *      %Ig     <inode> -> i_gid
+ *      %j      <ino2>                  inode number
+ *      %m      <com_err error message>
+ *      %N      <num>
+ *      %p      ext2fs_get_pathname of directory <ino>
+ *      %P      ext2fs_get_pathname of <dirent>->ino with <ino2> as
+ *                      the containing directory.  (If dirent is NULL
+ *                      then return the pathname of directory <ino2>)
+ *      %q      ext2fs_get_pathname of directory <dir>
+ *      %Q      ext2fs_get_pathname of directory <ino> with <dir> as
+ *                      the containing directory.
+ *      %s      <str>                   miscellaneous string
+ *      %S      backup superblock
+ *      %X      <num> 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 '@<i>' is expanded by looking up the index
+ * letter <i> 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_("<The NULL inode>"),                 /* 0 */
+       N_("<The bad blocks inode>"),           /* 1 */
+       "/",                                    /* 2 */
+       N_("<The ACL index inode>"),            /* 3 */
+       N_("<The ACL data inode>"),             /* 4 */
+       N_("<The boot loader inode>"),          /* 5 */
+       N_("<The undelete directory inode>"),   /* 6 */
+       N_("<The group descriptor inode>"),     /* 7 */
+       N_("<The journal inode>"),              /* 8 */
+       N_("<Reserved inode 9>"),               /* 9 */
+       N_("<Reserved inode 10>"),              /* 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)) {
+                       bb_putchar('^');
+                       ch ^= 0x40; /* ^@, ^A, ^B; ^? for DEL */
+               }
+               bb_putchar(ch);
+       }
+}
+
+
+/*
+ * 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;
+                       bb_putchar(toupper(*str++));
+               }
+               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 '%':
+               bb_putchar('%');
+               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':
+               fputs(error_message(ctx->errcode), stdout);
+               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':
+               fputs((ctx->str ? ctx->str : "NULL"), stdout);
+               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(NULL);
+                                       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(NULL);
+                               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(NULL);
+                                       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(NULL);
+               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(NULL);
+               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(NULL);
+                       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(NULL);
+       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(NULL);
+       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(NULL);
+       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(NULL);
+       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;
+}
+
+/*
+ * 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(NULL);
+                       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 <sct@redhat.com>, 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; i<table->hash_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(NULL);
+               } 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);
+}
+
+/*
+ * 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_stdin_TCSANOW(&tmp);
+#endif
+
+       if (def == 1)
+               defstr = "<y>";
+       else if (def == 0)
+               defstr = "<n>";
+       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_stdin_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_stdin_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);
+       }
+}
+
+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_for_read("/proc/apm");
+       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_for_read(fname);
+                       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(NULL);
+
+       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);
+       }
+       bb_putchar('\n');
+       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)
+               bb_putchar('|');
+       else
+               bb_putchar(spinner[ctx->progress_pos & 3]);
+       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);
+               xwrite_str(ctx->progress_fd, 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=<ea_version (1 or 2)>\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);
+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/old_e2fsprogs/e2fsck.h b/e2fsprogs/old_e2fsprogs/e2fsck.h
new file mode 100644 (file)
index 0000000..73d398f
--- /dev/null
@@ -0,0 +1,640 @@
+/* vi: set sw=4 ts=4: */
+#include <sys/types.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <time.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <setjmp.h>
+#include <errno.h>
+#include <getopt.h>
+#include <limits.h>
+#include <stddef.h>
+#include <assert.h>
+#include <signal.h>
+#include <sys/stat.h>
+#include <sys/resource.h>
+#include <sys/param.h>
+#include <sys/mount.h>
+#include <sys/ioctl.h>
+#include <termios.h>
+#include <mntent.h>
+#include <dirent.h>
+#include "ext2fs/kernel-list.h"
+#include <sys/types.h>
+#include <linux/types.h>
+
+/*
+ * 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 "libbb.h"
+
+#ifdef HAVE_CONIO_H
+#undef HAVE_TERMIOS_H
+#include <conio.h>
+#define read_a_char()   getch()
+#else
+#ifdef HAVE_TERMIOS_H
+#include <termios.h>
+#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/old_e2fsprogs/e2p/Kbuild b/e2fsprogs/old_e2fsprogs/e2p/Kbuild
new file mode 100644 (file)
index 0000000..c0ff824
--- /dev/null
@@ -0,0 +1,15 @@
+# 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.
+
+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/old_e2fsprogs/e2p/e2p.h b/e2fsprogs/old_e2fsprogs/e2p/e2p.h
new file mode 100644 (file)
index 0000000..bad2d6a
--- /dev/null
@@ -0,0 +1,64 @@
+/* vi: set sw=4 ts=4: */
+#include "libbb.h"
+#include <sys/types.h>         /* Needed by dirent.h on netbsd */
+#include <stdio.h>
+#include <dirent.h>
+
+#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_e2flags() */
+
+#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/old_e2fsprogs/e2p/feature.c b/e2fsprogs/old_e2fsprogs/e2p/feature.c
new file mode 100644 (file)
index 0000000..b45754f
--- /dev/null
@@ -0,0 +1,187 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * feature.c --- convert between features and strings
+ *
+ * Copyright (C) 1999  Theodore Ts'o <tytso@mit.edu>
+ *
+ * This file can be redistributed under the terms of the GNU Library General
+ * Public License
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <errno.h>
+
+#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/old_e2fsprogs/e2p/fgetsetflags.c b/e2fsprogs/old_e2fsprogs/e2p/fgetsetflags.c
new file mode 100644 (file)
index 0000000..008b798
--- /dev/null
@@ -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 <card@masi.ibp.fr>
+ *                           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 <errno.h>
+#endif
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#include <sys/types.h>
+#include <sys/stat.h>
+#ifdef HAVE_EXT2_IOCTLS
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#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/old_e2fsprogs/e2p/fgetsetversion.c b/e2fsprogs/old_e2fsprogs/e2p/fgetsetversion.c
new file mode 100644 (file)
index 0000000..8d79054
--- /dev/null
@@ -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 <card@masi.ibp.fr>
+ *                           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 <errno.h>
+#endif
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#include <fcntl.h>
+#include <sys/ioctl.h>
+
+#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/old_e2fsprogs/e2p/hashstr.c b/e2fsprogs/old_e2fsprogs/e2p/hashstr.c
new file mode 100644 (file)
index 0000000..697ffad
--- /dev/null
@@ -0,0 +1,70 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * feature.c --- convert between features and strings
+ *
+ * Copyright (C) 1999  Theodore Ts'o <tytso@mit.edu>
+ *
+ * This file can be redistributed under the terms of the GNU Library General
+ * Public License
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <errno.h>
+
+#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/old_e2fsprogs/e2p/iod.c b/e2fsprogs/old_e2fsprogs/e2p/iod.c
new file mode 100644 (file)
index 0000000..23ab8d5
--- /dev/null
@@ -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 <card@masi.ibp.fr>
+ *                           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 <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+
+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 = 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/old_e2fsprogs/e2p/ls.c b/e2fsprogs/old_e2fsprogs/e2p/ls.c
new file mode 100644 (file)
index 0000000..9d29db6
--- /dev/null
@@ -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 <card@masi.ibp.fr>
+ *                                 Laboratoire MASI, Institut Blaise Pascal
+ *                                 Universite Pierre et Marie Curie (Paris VI)
+ *
+ * Copyright (C) 1995, 1996, 1997  Theodore Ts'o <tytso@mit.edu>
+ *
+ * This file can be redistributed under the terms of the GNU Library General
+ * Public License
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <string.h>
+#include <grp.h>
+#include <pwd.h>
+#include <time.h>
+
+#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 "<none>";
+
+       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, "<none>");
+       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, "<not available>");
+       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/old_e2fsprogs/e2p/mntopts.c b/e2fsprogs/old_e2fsprogs/e2p/mntopts.c
new file mode 100644 (file)
index 0000000..17c26c4
--- /dev/null
@@ -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 <tytso@mit.edu>
+ *
+ * This file can be redistributed under the terms of the GNU Library General
+ * Public License
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <errno.h>
+
+#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/old_e2fsprogs/e2p/ostype.c b/e2fsprogs/old_e2fsprogs/e2p/ostype.c
new file mode 100644 (file)
index 0000000..1abe2ba
--- /dev/null
@@ -0,0 +1,74 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * getostype.c          - Get the Filesystem OS type
+ *
+ * Copyright (C) 2004,2005  Theodore Ts'o <tytso@mit.edu>
+ *
+ * This file can be redistributed under the terms of the GNU Library General
+ * Public License
+ */
+
+#include "e2p.h"
+#include <string.h>
+#include <stdlib.h>
+
+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/old_e2fsprogs/e2p/parse_num.c b/e2fsprogs/old_e2fsprogs/e2p/parse_num.c
new file mode 100644 (file)
index 0000000..6db076f
--- /dev/null
@@ -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 <tytso@mit.edu>
+ *
+ * This file can be redistributed under the terms of the GNU Library General
+ * Public License
+ */
+
+#include "e2p.h"
+
+#include <stdlib.h>
+
+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 <unistd.h>
+#include <stdio.h>
+
+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/old_e2fsprogs/e2p/pe.c b/e2fsprogs/old_e2fsprogs/e2p/pe.c
new file mode 100644 (file)
index 0000000..835274b
--- /dev/null
@@ -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 <card@masi.ibp.fr>
+ *                                 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 <stdio.h>
+
+#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/old_e2fsprogs/e2p/pf.c b/e2fsprogs/old_e2fsprogs/e2p/pf.c
new file mode 100644 (file)
index 0000000..02cbec7
--- /dev/null
@@ -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 <card@masi.ibp.fr>
+ *                           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 <stdio.h>
+
+#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/old_e2fsprogs/e2p/ps.c b/e2fsprogs/old_e2fsprogs/e2p/ps.c
new file mode 100644 (file)
index 0000000..a6b4099
--- /dev/null
@@ -0,0 +1,27 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * ps.c                        - Print filesystem state
+ *
+ * Copyright (C) 1993, 1994  Remy Card <card@masi.ibp.fr>
+ *                           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 <stdio.h>
+
+#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/old_e2fsprogs/e2p/uuid.c b/e2fsprogs/old_e2fsprogs/e2p/uuid.c
new file mode 100644 (file)
index 0000000..474d64a
--- /dev/null
@@ -0,0 +1,78 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * uuid.c -- utility routines for manipulating UUID's.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#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 "<none>";
+       e2p_uuid_to_str(uu, buf);
+       return buf;
+}
diff --git a/e2fsprogs/old_e2fsprogs/ext2fs/Kbuild b/e2fsprogs/old_e2fsprogs/ext2fs/Kbuild
new file mode 100644 (file)
index 0000000..185887a
--- /dev/null
@@ -0,0 +1,23 @@
+# 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.
+
+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/old_e2fsprogs/ext2fs/alloc.c b/e2fsprogs/old_e2fsprogs/ext2fs/alloc.c
new file mode 100644 (file)
index 0000000..590f23a
--- /dev/null
@@ -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 <stdio.h>
+#if HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#include <time.h>
+#include <string.h>
+#if HAVE_SYS_STAT_H
+#include <sys/stat.h>
+#endif
+#if HAVE_SYS_TYPES_H
+#include <sys/types.h>
+#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/old_e2fsprogs/ext2fs/alloc_sb.c b/e2fsprogs/old_e2fsprogs/ext2fs/alloc_sb.c
new file mode 100644 (file)
index 0000000..a7437c9
--- /dev/null
@@ -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 <stdio.h>
+#include <string.h>
+#if HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#include <fcntl.h>
+#include <time.h>
+#if HAVE_SYS_STAT_H
+#include <sys/stat.h>
+#endif
+#if HAVE_SYS_TYPES_H
+#include <sys/types.h>
+#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/old_e2fsprogs/ext2fs/alloc_stats.c b/e2fsprogs/old_e2fsprogs/ext2fs/alloc_stats.c
new file mode 100644 (file)
index 0000000..f3ab06a
--- /dev/null
@@ -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 <stdio.h>
+
+#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/old_e2fsprogs/ext2fs/alloc_tables.c b/e2fsprogs/old_e2fsprogs/ext2fs/alloc_tables.c
new file mode 100644 (file)
index 0000000..b2d786e
--- /dev/null
@@ -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 <stdio.h>
+#include <string.h>
+#if HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#include <fcntl.h>
+#include <time.h>
+#if HAVE_SYS_STAT_H
+#include <sys/stat.h>
+#endif
+#if HAVE_SYS_TYPES_H
+#include <sys/types.h>
+#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/old_e2fsprogs/ext2fs/badblocks.c b/e2fsprogs/old_e2fsprogs/ext2fs/badblocks.c
new file mode 100644 (file)
index 0000000..6e5cc10
--- /dev/null
@@ -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 <stdio.h>
+#include <string.h>
+#if HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#include <fcntl.h>
+#include <time.h>
+#if HAVE_SYS_STAT_H
+#include <sys/stat.h>
+#endif
+#if HAVE_SYS_TYPES_H
+#include <sys/types.h>
+#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/old_e2fsprogs/ext2fs/bb_compat.c b/e2fsprogs/old_e2fsprogs/ext2fs/bb_compat.c
new file mode 100644 (file)
index 0000000..419ac77
--- /dev/null
@@ -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 <stdio.h>
+#include <string.h>
+#if HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#include <fcntl.h>
+#include <time.h>
+#if HAVE_SYS_STAT_H
+#include <sys/stat.h>
+#endif
+#if HAVE_SYS_TYPES_H
+#include <sys/types.h>
+#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/old_e2fsprogs/ext2fs/bb_inode.c b/e2fsprogs/old_e2fsprogs/ext2fs/bb_inode.c
new file mode 100644 (file)
index 0000000..1deae54
--- /dev/null
@@ -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 <stdio.h>
+#include <string.h>
+#if HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#include <fcntl.h>
+#include <time.h>
+#if HAVE_SYS_STAT_H
+#include <sys/stat.h>
+#endif
+#if HAVE_SYS_TYPES_H
+#include <sys/types.h>
+#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(NULL);
+       if (!inode.i_ctime)
+               inode.i_ctime = time(NULL);
+       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/old_e2fsprogs/ext2fs/bitmaps.c b/e2fsprogs/old_e2fsprogs/ext2fs/bitmaps.c
new file mode 100644 (file)
index 0000000..637ed27
--- /dev/null
@@ -0,0 +1,211 @@
+/* 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 <stdio.h>
+#include <string.h>
+#if HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#include <fcntl.h>
+#include <time.h>
+#if HAVE_SYS_STAT_H
+#include <sys/stat.h>
+#endif
+#if HAVE_SYS_TYPES_H
+#include <sys/types.h>
+#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);
+}
+
+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/old_e2fsprogs/ext2fs/bitops.c b/e2fsprogs/old_e2fsprogs/ext2fs/bitops.c
new file mode 100644 (file)
index 0000000..9870611
--- /dev/null
@@ -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 <stdio.h>
+#if HAVE_SYS_TYPES_H
+#include <sys/types.h>
+#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/old_e2fsprogs/ext2fs/bitops.h b/e2fsprogs/old_e2fsprogs/ext2fs/bitops.h
new file mode 100644 (file)
index 0000000..b34bd98
--- /dev/null
@@ -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 <asm/bitops.h>, Copyright 1992,
+ * Linus Torvalds.
+ */
+
+#include <string.h>
+//#include <strings.h>
+
+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/old_e2fsprogs/ext2fs/block.c b/e2fsprogs/old_e2fsprogs/ext2fs/block.c
new file mode 100644 (file)
index 0000000..4980969
--- /dev/null
@@ -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 <stdio.h>
+#include <string.h>
+#if HAVE_UNISTD_H
+#include <unistd.h>
+#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/old_e2fsprogs/ext2fs/bmap.c b/e2fsprogs/old_e2fsprogs/ext2fs/bmap.c
new file mode 100644 (file)
index 0000000..b22fe3d
--- /dev/null
@@ -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 <stdio.h>
+#include <string.h>
+#if HAVE_UNISTD_H
+#include <unistd.h>
+#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/old_e2fsprogs/ext2fs/bmove.c b/e2fsprogs/old_e2fsprogs/ext2fs/bmove.c
new file mode 100644 (file)
index 0000000..635410d
--- /dev/null
@@ -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 <stdio.h>
+#include <string.h>
+#if HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#if HAVE_SYS_TYPES_H
+#include <sys/types.h>
+#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/old_e2fsprogs/ext2fs/brel.h b/e2fsprogs/old_e2fsprogs/ext2fs/brel.h
new file mode 100644 (file)
index 0000000..216fd13
--- /dev/null
@@ -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/old_e2fsprogs/ext2fs/brel_ma.c b/e2fsprogs/old_e2fsprogs/ext2fs/brel_ma.c
new file mode 100644 (file)
index 0000000..652a350
--- /dev/null
@@ -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 <fcntl.h>
+#include <stdio.h>
+#include <string.h>
+#if HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#if HAVE_ERRNO_H
+#include <errno.h>
+#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/old_e2fsprogs/ext2fs/check_desc.c b/e2fsprogs/old_e2fsprogs/ext2fs/check_desc.c
new file mode 100644 (file)
index 0000000..dd4b0e9
--- /dev/null
@@ -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 <stdio.h>
+#include <string.h>
+#if HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#include <fcntl.h>
+#include <time.h>
+#if HAVE_SYS_STAT_H
+#include <sys/stat.h>
+#endif
+#if HAVE_SYS_TYPES_H
+#include <sys/types.h>
+#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/old_e2fsprogs/ext2fs/closefs.c b/e2fsprogs/old_e2fsprogs/ext2fs/closefs.c
new file mode 100644 (file)
index 0000000..008d5f3
--- /dev/null
@@ -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 <stdio.h>
+#if HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#include <time.h>
+#include <string.h>
+
+#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/old_e2fsprogs/ext2fs/cmp_bitmaps.c b/e2fsprogs/old_e2fsprogs/ext2fs/cmp_bitmaps.c
new file mode 100644 (file)
index 0000000..05b8eb8
--- /dev/null
@@ -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 <stdio.h>
+#include <string.h>
+#if HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#include <fcntl.h>
+#include <time.h>
+#if HAVE_SYS_STAT_H
+#include <sys/stat.h>
+#endif
+#if HAVE_SYS_TYPES_H
+#include <sys/types.h>
+#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/old_e2fsprogs/ext2fs/dblist.c b/e2fsprogs/old_e2fsprogs/ext2fs/dblist.c
new file mode 100644 (file)
index 0000000..06ff6d8
--- /dev/null
@@ -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 <stdio.h>
+#if HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#include <string.h>
+#include <time.h>
+
+#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/old_e2fsprogs/ext2fs/dblist_dir.c b/e2fsprogs/old_e2fsprogs/ext2fs/dblist_dir.c
new file mode 100644 (file)
index 0000000..b239204
--- /dev/null
@@ -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 <stdio.h>
+#if HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#include <string.h>
+#include <time.h>
+
+#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/old_e2fsprogs/ext2fs/dir_iterate.c b/e2fsprogs/old_e2fsprogs/ext2fs/dir_iterate.c
new file mode 100644 (file)
index 0000000..b7d8735
--- /dev/null
@@ -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 <stdio.h>
+#include <string.h>
+#if HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#if HAVE_ERRNO_H
+#include <errno.h>
+#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/old_e2fsprogs/ext2fs/dirblock.c b/e2fsprogs/old_e2fsprogs/ext2fs/dirblock.c
new file mode 100644 (file)
index 0000000..5d3f6a1
--- /dev/null
@@ -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 <stdio.h>
+#if HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#include <string.h>
+#include <time.h>
+
+#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/old_e2fsprogs/ext2fs/dirhash.c b/e2fsprogs/old_e2fsprogs/ext2fs/dirhash.c
new file mode 100644 (file)
index 0000000..09e34be
--- /dev/null
@@ -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 <stdio.h>
+#include <string.h>
+
+#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 <jeremy@zip.com.au> 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/old_e2fsprogs/ext2fs/dupfs.c b/e2fsprogs/old_e2fsprogs/ext2fs/dupfs.c
new file mode 100644 (file)
index 0000000..203c29f
--- /dev/null
@@ -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 <stdio.h>
+#if HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#include <time.h>
+#include <string.h>
+
+#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/old_e2fsprogs/ext2fs/e2image.h b/e2fsprogs/old_e2fsprogs/ext2fs/e2image.h
new file mode 100644 (file)
index 0000000..8d38ecc
--- /dev/null
@@ -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/old_e2fsprogs/ext2fs/expanddir.c b/e2fsprogs/old_e2fsprogs/ext2fs/expanddir.c
new file mode 100644 (file)
index 0000000..8a29ae5
--- /dev/null
@@ -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 <stdio.h>
+#include <string.h>
+#if HAVE_UNISTD_H
+#include <unistd.h>
+#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/old_e2fsprogs/ext2fs/ext2_err.h b/e2fsprogs/old_e2fsprogs/ext2fs/ext2_err.h
new file mode 100644 (file)
index 0000000..ead3528
--- /dev/null
@@ -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/old_e2fsprogs/ext2fs/ext2_ext_attr.h b/e2fsprogs/old_e2fsprogs/ext2fs/ext2_ext_attr.h
new file mode 100644 (file)
index 0000000..cc91bb8
--- /dev/null
@@ -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, <a.gruenbacher@computer.org>
+*/
+
+/* 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<<EXT2_EXT_ATTR_PAD_BITS)
+#define EXT2_EXT_ATTR_ROUND            (EXT2_EXT_ATTR_PAD-1)
+#define EXT2_EXT_ATTR_LEN(name_len) \
+       (((name_len) + EXT2_EXT_ATTR_ROUND + \
+       sizeof(struct ext2_ext_attr_entry)) & ~EXT2_EXT_ATTR_ROUND)
+#define EXT2_EXT_ATTR_NEXT(entry) \
+       ( (struct ext2_ext_attr_entry *)( \
+         (char *)(entry) + EXT2_EXT_ATTR_LEN((entry)->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/old_e2fsprogs/ext2fs/ext2_fs.h b/e2fsprogs/old_e2fsprogs/ext2fs/ext2_fs.h
new file mode 100644 (file)
index 0000000..6f4f708
--- /dev/null
@@ -0,0 +1,569 @@
+/* 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 1
+
+#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
diff --git a/e2fsprogs/old_e2fsprogs/ext2fs/ext2_io.h b/e2fsprogs/old_e2fsprogs/ext2fs/ext2_io.h
new file mode 100644 (file)
index 0000000..1900a76
--- /dev/null
@@ -0,0 +1,112 @@
+/* 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 1
+
+/*
+ * 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
diff --git a/e2fsprogs/old_e2fsprogs/ext2fs/ext2_types.h b/e2fsprogs/old_e2fsprogs/ext2fs/ext2_types.h
new file mode 100644 (file)
index 0000000..2c1196b
--- /dev/null
@@ -0,0 +1,2 @@
+/* vi: set sw=4 ts=4: */
+#include <linux/types.h>
diff --git a/e2fsprogs/old_e2fsprogs/ext2fs/ext2fs.h b/e2fsprogs/old_e2fsprogs/ext2fs/ext2fs.h
new file mode 100644 (file)
index 0000000..9f77201
--- /dev/null
@@ -0,0 +1,922 @@
+/* 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 1
+
+
+#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 <sys/types.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#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
diff --git a/e2fsprogs/old_e2fsprogs/ext2fs/ext2fsP.h b/e2fsprogs/old_e2fsprogs/ext2fs/ext2fsP.h
new file mode 100644 (file)
index 0000000..908b5d9
--- /dev/null
@@ -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/old_e2fsprogs/ext2fs/ext2fs_inline.c b/e2fsprogs/old_e2fsprogs/ext2fs/ext2fs_inline.c
new file mode 100644 (file)
index 0000000..da1cf5b
--- /dev/null
@@ -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 <string.h>
+
+/*
+ *  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/old_e2fsprogs/ext2fs/ext_attr.c b/e2fsprogs/old_e2fsprogs/ext2fs/ext_attr.c
new file mode 100644 (file)
index 0000000..7ee41f2
--- /dev/null
@@ -0,0 +1,101 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * ext_attr.c --- extended attribute blocks
+ *
+ * Copyright (C) 2001 Andreas Gruenbacher, <a.gruenbacher@computer.org>
+ *
+ * Copyright (C) 2002 Theodore Ts'o.
+ *
+ * %Begin-Header%
+ * This file may be redistributed under the terms of the GNU Public
+ * License.
+ * %End-Header%
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <time.h>
+
+#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/old_e2fsprogs/ext2fs/fileio.c b/e2fsprogs/old_e2fsprogs/ext2fs/fileio.c
new file mode 100644 (file)
index 0000000..5a5ad3e
--- /dev/null
@@ -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 <stdio.h>
+#include <string.h>
+#if HAVE_UNISTD_H
+#include <unistd.h>
+#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/old_e2fsprogs/ext2fs/finddev.c b/e2fsprogs/old_e2fsprogs/ext2fs/finddev.c
new file mode 100644 (file)
index 0000000..5e2cce9
--- /dev/null
@@ -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 <stdio.h>
+#include <string.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#include <stdlib.h>
+#include <string.h>
+#ifdef HAVE_SYS_TYPES_H
+#include <sys/types.h>
+#endif
+#ifdef HAVE_SYS_STAT_H
+#include <sys/stat.h>
+#endif
+#include <dirent.h>
+#ifdef HAVE_ERRNO_H
+#include <errno.h>
+#endif
+#ifdef HAVE_SYS_MKDEV_H
+#include <sys/mkdev.h>
+#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/old_e2fsprogs/ext2fs/flushb.c b/e2fsprogs/old_e2fsprogs/ext2fs/flushb.c
new file mode 100644 (file)
index 0000000..e429826
--- /dev/null
@@ -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 <stdio.h>
+#if HAVE_ERRNO_H
+#include <errno.h>
+#endif
+#if HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#if HAVE_SYS_IOCTL_H
+#include <sys/ioctl.h>
+#endif
+#if HAVE_SYS_MOUNT_H
+#include <sys/param.h>
+#include <sys/mount.h>         /* 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/old_e2fsprogs/ext2fs/freefs.c b/e2fsprogs/old_e2fsprogs/ext2fs/freefs.c
new file mode 100644 (file)
index 0000000..65c4ee7
--- /dev/null
@@ -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 <stdio.h>
+#if HAVE_UNISTD_H
+#include <unistd.h>
+#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/old_e2fsprogs/ext2fs/gen_bitmap.c b/e2fsprogs/old_e2fsprogs/ext2fs/gen_bitmap.c
new file mode 100644 (file)
index 0000000..d0869c9
--- /dev/null
@@ -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 <stdio.h>
+#include <string.h>
+#if HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#include <fcntl.h>
+#include <time.h>
+#if HAVE_SYS_STAT_H
+#include <sys/stat.h>
+#endif
+#if HAVE_SYS_TYPES_H
+#include <sys/types.h>
+#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/old_e2fsprogs/ext2fs/get_pathname.c b/e2fsprogs/old_e2fsprogs/ext2fs/get_pathname.c
new file mode 100644 (file)
index 0000000..a98b2b9
--- /dev/null
@@ -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 <name>.  <dir> is the containing
+ *     directory inode, and <ino> is the inode number itself.  If
+ *     <ino> is zero, then ext2fs_get_pathname will return pathname
+ *     of the the directory <dir>.
+ *
+ */
+
+#include <stdio.h>
+#include <string.h>
+#if HAVE_UNISTD_H
+#include <unistd.h>
+#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/old_e2fsprogs/ext2fs/getsectsize.c b/e2fsprogs/old_e2fsprogs/ext2fs/getsectsize.c
new file mode 100644 (file)
index 0000000..163ec65
--- /dev/null
@@ -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 <stdio.h>
+#if HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#if HAVE_ERRNO_H
+#include <errno.h>
+#endif
+#include <fcntl.h>
+#ifdef HAVE_LINUX_FD_H
+#include <sys/ioctl.h>
+#include <linux/fd.h>
+#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/old_e2fsprogs/ext2fs/getsize.c b/e2fsprogs/old_e2fsprogs/ext2fs/getsize.c
new file mode 100644 (file)
index 0000000..ff11fe9
--- /dev/null
@@ -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 <stdio.h>
+#if HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#if HAVE_ERRNO_H
+#include <errno.h>
+#endif
+#include <fcntl.h>
+#ifdef HAVE_SYS_IOCTL_H
+#include <sys/ioctl.h>
+#endif
+#ifdef HAVE_LINUX_FD_H
+#include <linux/fd.h>
+#endif
+#ifdef HAVE_SYS_DISKLABEL_H
+#include <sys/disklabel.h>
+#endif
+#ifdef HAVE_SYS_DISK_H
+#ifdef HAVE_SYS_QUEUE_H
+#include <sys/queue.h> /* for LIST_HEAD */
+#endif
+#include <sys/disk.h>
+#endif
+#ifdef __linux__
+#include <sys/utsname.h>
+#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 <windows.h>
+#include <winioctl.h>
+
+#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/old_e2fsprogs/ext2fs/icount.c b/e2fsprogs/old_e2fsprogs/ext2fs/icount.c
new file mode 100644 (file)
index 0000000..7ab5f51
--- /dev/null
@@ -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 <unistd.h>
+#endif
+#include <string.h>
+#include <stdio.h>
+
+#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/old_e2fsprogs/ext2fs/imager.c b/e2fsprogs/old_e2fsprogs/ext2fs/imager.c
new file mode 100644 (file)
index 0000000..e82321e
--- /dev/null
@@ -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 <stdio.h>
+#include <string.h>
+#if HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#if HAVE_ERRNO_H
+#include <errno.h>
+#endif
+#include <fcntl.h>
+#include <time.h>
+#if HAVE_SYS_STAT_H
+#include <sys/stat.h>
+#endif
+#if HAVE_SYS_TYPES_H
+#include <sys/types.h>
+#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/old_e2fsprogs/ext2fs/ind_block.c b/e2fsprogs/old_e2fsprogs/ext2fs/ind_block.c
new file mode 100644 (file)
index 0000000..c86a1c5
--- /dev/null
@@ -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 <stdio.h>
+#include <string.h>
+#if HAVE_UNISTD_H
+#include <unistd.h>
+#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/old_e2fsprogs/ext2fs/initialize.c b/e2fsprogs/old_e2fsprogs/ext2fs/initialize.c
new file mode 100644 (file)
index 0000000..ef1d343
--- /dev/null
@@ -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 <stdio.h>
+#include <string.h>
+#if HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#include <fcntl.h>
+#include <time.h>
+#if HAVE_SYS_STAT_H
+#include <sys/stat.h>
+#endif
+#if HAVE_SYS_TYPES_H
+#include <sys/types.h>
+#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/old_e2fsprogs/ext2fs/inline.c b/e2fsprogs/old_e2fsprogs/ext2fs/inline.c
new file mode 100644 (file)
index 0000000..9b620a7
--- /dev/null
@@ -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 <stdio.h>
+#include <string.h>
+#if HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#include <fcntl.h>
+#include <time.h>
+#if HAVE_SYS_STAT_H
+#include <sys/stat.h>
+#endif
+#if HAVE_SYS_TYPES_H
+#include <sys/types.h>
+#endif
+
+#include "ext2_fs.h"
+#define INCLUDE_INLINE_FUNCS
+#include "ext2fs.h"
+
diff --git a/e2fsprogs/old_e2fsprogs/ext2fs/inode.c b/e2fsprogs/old_e2fsprogs/ext2fs/inode.c
new file mode 100644 (file)
index 0000000..5e0d081
--- /dev/null
@@ -0,0 +1,767 @@
+/* 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 <stdio.h>
+#include <string.h>
+#if HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#if HAVE_ERRNO_H
+#include <errno.h>
+#endif
+#if HAVE_SYS_STAT_H
+#include <sys/stat.h>
+#endif
+#if HAVE_SYS_TYPES_H
+#include <sys/types.h>
+#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);
+}
+
+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/old_e2fsprogs/ext2fs/inode_io.c b/e2fsprogs/old_e2fsprogs/ext2fs/inode_io.c
new file mode 100644 (file)
index 0000000..4bfa93a
--- /dev/null
@@ -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 <stdio.h>
+#include <string.h>
+#if HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#if HAVE_ERRNO_H
+#include <errno.h>
+#endif
+#include <time.h>
+
+#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/old_e2fsprogs/ext2fs/io_manager.c b/e2fsprogs/old_e2fsprogs/ext2fs/io_manager.c
new file mode 100644 (file)
index 0000000..b470386
--- /dev/null
@@ -0,0 +1,70 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * io_manager.c --- the I/O manager abstraction
+ */
+
+#include <stdio.h>
+#include <string.h>
+#if HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#include <fcntl.h>
+#include <time.h>
+#if HAVE_SYS_STAT_H
+#include <sys/stat.h>
+#endif
+#if HAVE_SYS_TYPES_H
+#include <sys/types.h>
+#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/old_e2fsprogs/ext2fs/irel.h b/e2fsprogs/old_e2fsprogs/ext2fs/irel.h
new file mode 100644 (file)
index 0000000..91d1d89
--- /dev/null
@@ -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/old_e2fsprogs/ext2fs/irel_ma.c b/e2fsprogs/old_e2fsprogs/ext2fs/irel_ma.c
new file mode 100644 (file)
index 0000000..c871b18
--- /dev/null
@@ -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 <fcntl.h>
+#include <stdio.h>
+#include <string.h>
+#if HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#if HAVE_ERRNO_H
+#include <errno.h>
+#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/old_e2fsprogs/ext2fs/ismounted.c b/e2fsprogs/old_e2fsprogs/ext2fs/ismounted.c
new file mode 100644 (file)
index 0000000..7f24f9b
--- /dev/null
@@ -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 <stdio.h>
+#if HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#if HAVE_ERRNO_H
+#include <errno.h>
+#endif
+#include <fcntl.h>
+#ifdef HAVE_LINUX_FD_H
+#include <linux/fd.h>
+#endif
+#ifdef HAVE_MNTENT_H
+#include <mntent.h>
+#endif
+#ifdef HAVE_GETMNTINFO
+#include <paths.h>
+#include <sys/param.h>
+#include <sys/mount.h>
+#endif /* HAVE_GETMNTINFO */
+#include <string.h>
+#include <sys/stat.h>
+
+#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 (LONE_CHAR(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_for_read("/proc/swaps")))
+               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, "<swap>", 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/old_e2fsprogs/ext2fs/jfs_dat.h b/e2fsprogs/old_e2fsprogs/ext2fs/jfs_dat.h
new file mode 100644 (file)
index 0000000..136635d
--- /dev/null
@@ -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/old_e2fsprogs/ext2fs/kernel-jbd.h b/e2fsprogs/old_e2fsprogs/ext2fs/kernel-jbd.h
new file mode 100644 (file)
index 0000000..853d97a
--- /dev/null
@@ -0,0 +1,235 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * linux/include/linux/jbd.h
+ *
+ * Written by Stephen C. Tweedie <sct@redhat.com>
+ *
+ * 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 1
+
+#include <sys/types.h>
+#include <linux/types.h>
+#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
diff --git a/e2fsprogs/old_e2fsprogs/ext2fs/kernel-list.h b/e2fsprogs/old_e2fsprogs/ext2fs/kernel-list.h
new file mode 100644 (file)
index 0000000..d80716a
--- /dev/null
@@ -0,0 +1,113 @@
+/* vi: set sw=4 ts=4: */
+#ifndef LINUX_LIST_H
+#define LINUX_LIST_H 1
+
+/*
+ * 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/old_e2fsprogs/ext2fs/link.c b/e2fsprogs/old_e2fsprogs/ext2fs/link.c
new file mode 100644 (file)
index 0000000..08b2e96
--- /dev/null
@@ -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 <stdio.h>
+#include <string.h>
+#if HAVE_UNISTD_H
+#include <unistd.h>
+#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/old_e2fsprogs/ext2fs/lookup.c b/e2fsprogs/old_e2fsprogs/ext2fs/lookup.c
new file mode 100644 (file)
index 0000000..31b30a1
--- /dev/null
@@ -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 <stdio.h>
+#include <string.h>
+#if HAVE_UNISTD_H
+#include <unistd.h>
+#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/old_e2fsprogs/ext2fs/mkdir.c b/e2fsprogs/old_e2fsprogs/ext2fs/mkdir.c
new file mode 100644 (file)
index 0000000..b63c5d7
--- /dev/null
@@ -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 <stdio.h>
+#include <string.h>
+#if HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#include <fcntl.h>
+#include <time.h>
+#if HAVE_SYS_STAT_H
+#include <sys/stat.h>
+#endif
+#if HAVE_SYS_TYPES_H
+#include <sys/types.h>
+#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/old_e2fsprogs/ext2fs/mkjournal.c b/e2fsprogs/old_e2fsprogs/ext2fs/mkjournal.c
new file mode 100644 (file)
index 0000000..af47aee
--- /dev/null
@@ -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 <stdio.h>
+#include <string.h>
+#if HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#if HAVE_ERRNO_H
+#include <errno.h>
+#endif
+#include <fcntl.h>
+#include <time.h>
+#if HAVE_SYS_STAT_H
+#include <sys/stat.h>
+#endif
+#if HAVE_SYS_TYPES_H
+#include <sys/types.h>
+#endif
+#if HAVE_SYS_IOCTL_H
+#include <sys/ioctl.h>
+#endif
+#if HAVE_NETINET_IN_H
+#include <netinet/in.h>
+#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(NULL);
+       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/old_e2fsprogs/ext2fs/namei.c b/e2fsprogs/old_e2fsprogs/ext2fs/namei.c
new file mode 100644 (file)
index 0000000..9889670
--- /dev/null
@@ -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 <stdio.h>
+#include <string.h>
+#if HAVE_UNISTD_H
+#include <unistd.h>
+#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/old_e2fsprogs/ext2fs/newdir.c b/e2fsprogs/old_e2fsprogs/ext2fs/newdir.c
new file mode 100644 (file)
index 0000000..9470e7f
--- /dev/null
@@ -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 <stdio.h>
+#include <string.h>
+#if HAVE_UNISTD_H
+#include <unistd.h>
+#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/old_e2fsprogs/ext2fs/openfs.c b/e2fsprogs/old_e2fsprogs/ext2fs/openfs.c
new file mode 100644 (file)
index 0000000..1b27119
--- /dev/null
@@ -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 <stdio.h>
+#include <string.h>
+#if HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#include <fcntl.h>
+#include <time.h>
+#if HAVE_SYS_STAT_H
+#include <sys/stat.h>
+#endif
+#if HAVE_SYS_TYPES_H
+#include <sys/types.h>
+#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/old_e2fsprogs/ext2fs/read_bb.c b/e2fsprogs/old_e2fsprogs/ext2fs/read_bb.c
new file mode 100644 (file)
index 0000000..4766157
--- /dev/null
@@ -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 <stdio.h>
+#include <string.h>
+#if HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#include <fcntl.h>
+#include <time.h>
+#if HAVE_SYS_STAT_H
+#include <sys/stat.h>
+#endif
+#if HAVE_SYS_TYPES_H
+#include <sys/types.h>
+#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/old_e2fsprogs/ext2fs/read_bb_file.c b/e2fsprogs/old_e2fsprogs/ext2fs/read_bb_file.c
new file mode 100644 (file)
index 0000000..831adcc
--- /dev/null
@@ -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 <stdio.h>
+#include <string.h>
+#if HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#include <fcntl.h>
+#include <time.h>
+#if HAVE_SYS_STAT_H
+#include <sys/stat.h>
+#endif
+#if HAVE_SYS_TYPES_H
+#include <sys/types.h>
+#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/old_e2fsprogs/ext2fs/res_gdt.c b/e2fsprogs/old_e2fsprogs/ext2fs/res_gdt.c
new file mode 100644 (file)
index 0000000..3c550d5
--- /dev/null
@@ -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 <stdio.h>
+#include <string.h>
+#include <time.h>
+#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(NULL);
+       }
+
+       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(NULL);
+               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/old_e2fsprogs/ext2fs/rs_bitmap.c b/e2fsprogs/old_e2fsprogs/ext2fs/rs_bitmap.c
new file mode 100644 (file)
index 0000000..e932b3c
--- /dev/null
@@ -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 <stdio.h>
+#include <string.h>
+#if HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#include <fcntl.h>
+#include <time.h>
+#ifdef HAVE_SYS_STAT_H
+#include <sys/stat.h>
+#endif
+#ifdef HAVE_SYS_TYPES_H
+#include <sys/types.h>
+#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/old_e2fsprogs/ext2fs/rw_bitmaps.c b/e2fsprogs/old_e2fsprogs/ext2fs/rw_bitmaps.c
new file mode 100644 (file)
index 0000000..a5782db
--- /dev/null
@@ -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 <stdio.h>
+#include <string.h>
+#if HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#include <fcntl.h>
+#include <time.h>
+#ifdef HAVE_SYS_STAT_H
+#include <sys/stat.h>
+#endif
+#ifdef HAVE_SYS_TYPES_H
+#include <sys/types.h>
+#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/old_e2fsprogs/ext2fs/sparse.c b/e2fsprogs/old_e2fsprogs/ext2fs/sparse.c
new file mode 100644 (file)
index 0000000..b3d3071
--- /dev/null
@@ -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 <stdio.h>
+
+#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/old_e2fsprogs/ext2fs/swapfs.c b/e2fsprogs/old_e2fsprogs/ext2fs/swapfs.c
new file mode 100644 (file)
index 0000000..2fca3cf
--- /dev/null
@@ -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 <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <time.h>
+
+#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/old_e2fsprogs/ext2fs/test_io.c b/e2fsprogs/old_e2fsprogs/ext2fs/test_io.c
new file mode 100644 (file)
index 0000000..3d40d9a
--- /dev/null
@@ -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 <stdio.h>
+#include <string.h>
+#if HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#include <fcntl.h>
+#include <time.h>
+#if HAVE_SYS_STAT_H
+#include <sys/stat.h>
+#endif
+#if HAVE_SYS_TYPES_H
+#include <sys/types.h>
+#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_for_write(value);
+       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/old_e2fsprogs/ext2fs/unix_io.c b/e2fsprogs/old_e2fsprogs/ext2fs/unix_io.c
new file mode 100644 (file)
index 0000000..474f073
--- /dev/null
@@ -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 <stdio.h>
+#include <string.h>
+#if HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#if HAVE_ERRNO_H
+#include <errno.h>
+#endif
+#include <fcntl.h>
+#include <time.h>
+#ifdef __linux__
+#include <sys/utsname.h>
+#endif
+#if HAVE_SYS_STAT_H
+#include <sys/stat.h>
+#endif
+#if HAVE_SYS_TYPES_H
+#include <sys/types.h>
+#endif
+#include <sys/resource.h>
+
+#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/old_e2fsprogs/ext2fs/unlink.c b/e2fsprogs/old_e2fsprogs/ext2fs/unlink.c
new file mode 100644 (file)
index 0000000..83ac271
--- /dev/null
@@ -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 <stdio.h>
+#include <string.h>
+#if HAVE_UNISTD_H
+#include <unistd.h>
+#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/old_e2fsprogs/ext2fs/valid_blk.c b/e2fsprogs/old_e2fsprogs/ext2fs/valid_blk.c
new file mode 100644 (file)
index 0000000..8ed77ae
--- /dev/null
@@ -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 <stdio.h>
+#if HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#include <string.h>
+#include <time.h>
+
+#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/old_e2fsprogs/ext2fs/version.c b/e2fsprogs/old_e2fsprogs/ext2fs/version.c
new file mode 100644 (file)
index 0000000..d2981e8
--- /dev/null
@@ -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 <unistd.h>
+#endif
+#include <string.h>
+#include <stdio.h>
+#include <ctype.h>
+
+#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/old_e2fsprogs/ext2fs/write_bb_file.c b/e2fsprogs/old_e2fsprogs/ext2fs/write_bb_file.c
new file mode 100644 (file)
index 0000000..5b19eef
--- /dev/null
@@ -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 <stdio.h>
+
+#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/old_e2fsprogs/fsck.c b/e2fsprogs/old_e2fsprogs/fsck.c
new file mode 100644 (file)
index 0000000..98e4e38
--- /dev/null
@@ -0,0 +1,1388 @@
+/* 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, <tytso@mit.edu>
+ *
+ * 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.
+ *
+ * Licensed under GPLv2, see file LICENSE in this tarball for details.
+ */
+
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <sys/stat.h>
+#include <limits.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <string.h>
+#include <time.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <paths.h>
+#include <unistd.h>
+#include <errno.h>
+#include <signal.h>
+
+#include "fsck.h"
+#include "blkid/blkid.h"
+
+#include "e2fsbb.h"
+
+#include "libbb.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 = xstrdup(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);
+}
+
+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_or_warn(filename, "r")) == NULL) {
+               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 <num_args; i++)
+               argv[argc++] = string_copy(args[i]);
+
+       if (progress && !progress_active()) {
+               if ((strcmp(type, "ext2") == 0) ||
+                   (strcmp(type, "ext3") == 0)) {
+                       char tmp[80];
+                       snprintf(tmp, 80, "-C%d", progress_fd);
+                       argv[argc++] = string_copy(tmp);
+                       inst->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]);
+               bb_putchar('\n');
+       }
+
+       /* 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_simple_perror_msg_and_die(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 <fsck.fs> 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 (LONE_CHAR(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 (LONE_CHAR(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
+                                && argv[i+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) MAIN_EXTERNALLY_VISIBLE;
+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/old_e2fsprogs/fsck.h b/e2fsprogs/old_e2fsprogs/fsck.h
new file mode 100644 (file)
index 0000000..2ca2af7
--- /dev/null
@@ -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/old_e2fsprogs/lsattr.c b/e2fsprogs/old_e2fsprogs/lsattr.c
new file mode 100644 (file)
index 0000000..294bf2f
--- /dev/null
@@ -0,0 +1,129 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * lsattr.c            - List file attributes on an ext2 file system
+ *
+ * Copyright (C) 1993, 1994  Remy Card <card@masi.ibp.fr>
+ *                           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 <sys/types.h>
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/param.h>
+#include <sys/stat.h>
+
+#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_e2flags(stdout, fsflags, PFOPT_LONG);
+               bb_putchar('\n');
+       } else {
+               print_e2flags(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);
+                               bb_putchar('\n');
+                       }
+               }
+       }
+
+       free(path);
+
+       return 0;
+}
+
+int lsattr_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int lsattr_main(int argc, char **argv)
+{
+       int i;
+
+       flags = getopt32(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/old_e2fsprogs/mke2fs.c b/e2fsprogs/old_e2fsprogs/mke2fs.c
new file mode 100644 (file)
index 0000000..a132743
--- /dev/null
@@ -0,0 +1,1335 @@
+/* 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.
+ *
+ * Licensed under GPLv2, see file LICENSE in this tarball for details.
+ */
+
+/* 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 <stdio.h>
+#include <string.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <time.h>
+#include <getopt.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <mntent.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+
+#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);
+}
+
+/*
+ * 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_for_read(bad_blocks_file);
+       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) {
+               bb_putchar('\n');
+               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=<stride length in blocks>\n"
+                       "\tresize=<resize maximum size in blocks>\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(&param, 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,
+                                           &param.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(&param),
+                                               &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(&param))
+                               param.s_blocks_count &= ~((sys_page_size /
+                                                          EXT2_BLOCK_SIZE(&param))-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, &sector_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, &param, blocksize, sector_size, &inode_ratio);
+       blocksize = EXT2_BLOCK_SIZE(&param);
+
+       if (extended_opts)
+               parse_extended_opts(&param, 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(&param) ||
+                   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);
+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, &param,
+                                  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/old_e2fsprogs/tune2fs.c b/e2fsprogs/old_e2fsprogs/tune2fs.c
new file mode 100644 (file)
index 0000000..1d39ed1
--- /dev/null
@@ -0,0 +1,710 @@
+/* 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 <card@masi.ibp.fr>
+ *                                 Laboratoire MASI, Institut Blaise Pascal
+ *                                 Universite Pierre et Marie Curie (Paris VI)
+ *
+ * Copyright 1995, 1996, 1997, 1998, 1999, 2000 by Theodore Ts'o.
+ *
+ * Licensed under GPLv2, see file LICENSE in this tarball for details.
+ */
+
+/*
+ * 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 <sys/types.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <getopt.h>
+
+#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 "libbb.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);
+}
+
+/*
+ * 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 = xgroup2gid(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 = xuname2uid(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]);
+}
+
+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) MAIN_EXTERNALLY_VISIBLE;
+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_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/old_e2fsprogs/util.c b/e2fsprogs/old_e2fsprogs/util.c
new file mode 100644 (file)
index 0000000..7ab6591
--- /dev/null
@@ -0,0 +1,264 @@
+/* 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.
+ *
+ * Licensed under GPLv2, see file LICENSE in this tarball for details.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <linux/major.h>
+#include <sys/stat.h>
+
+#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=<journal size in megabytes>\n"
+                       "\tdevice=<journal device>\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/old_e2fsprogs/util.h b/e2fsprogs/old_e2fsprogs/util.h
new file mode 100644 (file)
index 0000000..80d2417
--- /dev/null
@@ -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/old_e2fsprogs/uuid/Kbuild b/e2fsprogs/old_e2fsprogs/uuid/Kbuild
new file mode 100644 (file)
index 0000000..dde9818
--- /dev/null
@@ -0,0 +1,14 @@
+# 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.
+
+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/old_e2fsprogs/uuid/compare.c b/e2fsprogs/old_e2fsprogs/uuid/compare.c
new file mode 100644 (file)
index 0000000..348ea7c
--- /dev/null
@@ -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 <string.h>
+
+#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/old_e2fsprogs/uuid/gen_uuid.c b/e2fsprogs/old_e2fsprogs/uuid/gen_uuid.c
new file mode 100644 (file)
index 0000000..4310c17
--- /dev/null
@@ -0,0 +1,304 @@
+/* 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 <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/file.h>
+#include <sys/time.h>
+#ifdef HAVE_SYS_IOCTL_H
+#include <sys/ioctl.h>
+#endif
+#include <sys/socket.h>
+#ifdef HAVE_SYS_SOCKIO_H
+#include <sys/sockio.h>
+#endif
+#ifdef HAVE_NET_IF_H
+#include <net/if.h>
+#endif
+#ifdef HAVE_NETINET_IN_H
+#include <netinet/in.h>
+#endif
+#ifdef HAVE_NET_IF_DL_H
+#include <net/if_dl.h>
+#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;
+}
+
+/*
+ * 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_IFNAMSIZ(ifr.ifr_name, ifrp->ifr_name);
+#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/old_e2fsprogs/uuid/pack.c b/e2fsprogs/old_e2fsprogs/uuid/pack.c
new file mode 100644 (file)
index 0000000..217cfce
--- /dev/null
@@ -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 <string.h>
+#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/old_e2fsprogs/uuid/parse.c b/e2fsprogs/old_e2fsprogs/uuid/parse.c
new file mode 100644 (file)
index 0000000..9a3f9cb
--- /dev/null
@@ -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 <stdlib.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <string.h>
+
+#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/old_e2fsprogs/uuid/unpack.c b/e2fsprogs/old_e2fsprogs/uuid/unpack.c
new file mode 100644 (file)
index 0000000..95d3aab
--- /dev/null
@@ -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 <string.h>
+#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/old_e2fsprogs/uuid/unparse.c b/e2fsprogs/old_e2fsprogs/uuid/unparse.c
new file mode 100644 (file)
index 0000000..d2948fe
--- /dev/null
@@ -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 <stdio.h>
+
+#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/old_e2fsprogs/uuid/uuid.h b/e2fsprogs/old_e2fsprogs/uuid/uuid.h
new file mode 100644 (file)
index 0000000..7a97064
--- /dev/null
@@ -0,0 +1,103 @@
+/* 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 1
+
+#include <sys/types.h>
+#include <time.h>
+
+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 UNUSED_PARAM = {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/old_e2fsprogs/uuid/uuidP.h b/e2fsprogs/old_e2fsprogs/uuid/uuidP.h
new file mode 100644 (file)
index 0000000..87041ef
--- /dev/null
@@ -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 <inttypes.h>
+#include <sys/types.h>
+
+#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/old_e2fsprogs/uuid/uuid_time.c b/e2fsprogs/old_e2fsprogs/uuid/uuid_time.c
new file mode 100644 (file)
index 0000000..b6f73e6
--- /dev/null
@@ -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 <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <time.h>
+
+#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:
+               bb_putchar('\n');
+       }
+       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 (file)
index 0000000..7dbc9b6
--- /dev/null
@@ -0,0 +1,196 @@
+#
+# 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_LIBM
+       bool "Enable math functions (requires libm)"
+       default n
+       depends on AWK
+       help
+         Enable math functions of the Awk programming language.
+         NOTE: This will require libm to be present for linking.
+
+config CMP
+       bool "cmp"
+       default n
+       help
+         cmp is used to compare two files and returns the result
+         to standard output.
+
+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 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 <grin>. 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_MAX_LEN
+       int "Maximum screen width in vi"
+       range 256 16384
+       default 4096
+       depends on VI
+       help
+         Contrary to what you may think, this is not eating much.
+         Make it smaller than 4k only if you are very limited on memory.
+
+config FEATURE_VI_8BIT
+       bool "Allow vi to display 8-bit chars (otherwise shows dots)"
+       default y
+       depends on VI
+       help
+         If your terminal can display characters with high bit set,
+         you may want to enable this. Note: vi is not Unicode-capable.
+         If your terminal combines several 8-bit bytes into one character
+         (as in Unicode mode), this will not work properly.
+
+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.
+
+config FEATURE_ALLOW_EXEC
+       bool "Allow vi and awk to execute shell commands"
+       default y
+       depends on VI || AWK
+       help
+         Enables vi and awk features which allows user to execute
+         shell commands (using system() C call).
+
+endmenu
diff --git a/editors/Kbuild b/editors/Kbuild
new file mode 100644 (file)
index 0000000..76302aa
--- /dev/null
@@ -0,0 +1,14 @@
+# 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_AWK)       += awk.o
+lib-$(CONFIG_CMP)       += cmp.o
+lib-$(CONFIG_DIFF)      += diff.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 (file)
index 0000000..89ae503
--- /dev/null
@@ -0,0 +1,2930 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * awk implementation for busybox
+ *
+ * Copyright (C) 2002 by Dmitry Zakharov <dmit@crp.bank.gov.ua>
+ *
+ * Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
+ */
+
+#include "libbb.h"
+#include "xregex.h"
+#include <math.h>
+
+/* This is a NOEXEC applet. Be very careful! */
+
+
+#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 = var::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 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;
+       const char *programname;
+} chain;
+
+/* Function */
+typedef struct func_s {
+       unsigned nargs;
+       struct chain_s body;
+} func;
+
+/* I/O stream */
+typedef struct rstream_s {
+       FILE *F;
+       char *buffer;
+       int adv;
+       int size;
+       int pos;
+       smallint 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 nel;           /* num of elements */
+       unsigned csize;         /* current hash size */
+       unsigned nprime;        /* next hash size in PRIMES[] */
+       unsigned glen;          /* summary length of item names */
+       struct hash_item_s **items;
+} xhash;
+
+/* Tree node */
+typedef struct node_s {
+       uint32_t info;
+       unsigned 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,   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,   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 const char tokenlist[] ALIGN1 =
+       "\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,    OFMT,       FS,         OFS,
+       ORS,        RS,         RT,         FILENAME,
+       SUBSEP,     F0,         ARGIND,     ARGC,
+       ARGV,       ERRNO,      FNR,        NR,
+       NF,         IGNORECASE, ENVIRON,    NUM_INTERNAL_VARS
+};
+
+static const char vNames[] ALIGN1 =
+       "CONVFMT\0" "OFMT\0"    "FS\0*"     "OFS\0"
+       "ORS\0"     "RS\0*"     "RT\0"      "FILENAME\0"
+       "SUBSEP\0"  "$\0*"      "ARGIND\0"  "ARGC\0"
+       "ARGV\0"    "ERRNO\0"   "FNR\0"     "NR\0"
+       "NF\0*"     "IGNORECASE\0*" "ENVIRON\0" "\0";
+
+static const char vValues[] ALIGN1 =
+       "%.6g\0"    "%.6g\0"    " \0"       " \0"
+       "\n\0"      "\n\0"      "\0"        "\0"
+       "\034\0"    "\0"        "\377";
+
+/* hash size may grow to these values */
+#define FIRST_PRIME 61
+static const uint16_t PRIMES[] ALIGN2 = { 251, 1021, 4093, 16381, 65521 };
+
+
+/* Globals. Split in two parts so that first one is addressed
+ * with (mostly short) negative offsets.
+ * NB: it's unsafe to put members of type "double"
+ * into globals2 (gcc may fail to align them).
+ */
+struct globals {
+       double t_double;
+       chain beginseq, mainseq, endseq;
+       chain *seq;
+       node *break_ptr, *continue_ptr;
+       rstream *iF;
+       xhash *vhash, *ahash, *fdhash, *fnhash;
+       const char *g_progname;
+       int g_lineno;
+       int nfields;
+       int maxfields; /* used in fsrealloc() only */
+       var *Fields;
+       nvblock *g_cb;
+       char *g_pos;
+       char *g_buf;
+       smallint icase;
+       smallint exiting;
+       smallint nextrec;
+       smallint nextfile;
+       smallint is_f0_split;
+};
+struct globals2 {
+       uint32_t t_info; /* often used */
+       uint32_t t_tclass;
+       char *t_string;
+       int t_lineno;
+       int t_rollback;
+
+       var *intvar[NUM_INTERNAL_VARS]; /* often used */
+
+       /* former statics from various functions */
+       char *split_f0__fstrings;
+
+       uint32_t next_token__save_tclass;
+       uint32_t next_token__save_info;
+       uint32_t next_token__ltclass;
+       smallint next_token__concat_inserted;
+
+       smallint next_input_file__files_happen;
+       rstream next_input_file__rsm;
+
+       var *evaluate__fnargs;
+       unsigned evaluate__seed;
+       regex_t evaluate__sreg;
+
+       var ptest__v;
+
+       tsplitter exec_builtin__tspl;
+
+       /* biggest and least used members go last */
+       tsplitter fsplitter, rsplitter;
+};
+#define G1 (ptr_to_globals[-1])
+#define G (*(struct globals2 *)ptr_to_globals)
+/* For debug. nm --size-sort awk.o | grep -vi ' [tr] ' */
+/*char G1size[sizeof(G1)]; - 0x74 */
+/*char Gsize[sizeof(G)]; - 0x1c4 */
+/* Trying to keep most of members accessible with short offsets: */
+/*char Gofs_seed[offsetof(struct globals2, evaluate__seed)]; - 0x90 */
+#define t_double     (G1.t_double    )
+#define beginseq     (G1.beginseq    )
+#define mainseq      (G1.mainseq     )
+#define endseq       (G1.endseq      )
+#define seq          (G1.seq         )
+#define break_ptr    (G1.break_ptr   )
+#define continue_ptr (G1.continue_ptr)
+#define iF           (G1.iF          )
+#define vhash        (G1.vhash       )
+#define ahash        (G1.ahash       )
+#define fdhash       (G1.fdhash      )
+#define fnhash       (G1.fnhash      )
+#define g_progname   (G1.g_progname  )
+#define g_lineno     (G1.g_lineno    )
+#define nfields      (G1.nfields     )
+#define maxfields    (G1.maxfields   )
+#define Fields       (G1.Fields      )
+#define g_cb         (G1.g_cb        )
+#define g_pos        (G1.g_pos       )
+#define g_buf        (G1.g_buf       )
+#define icase        (G1.icase       )
+#define exiting      (G1.exiting     )
+#define nextrec      (G1.nextrec     )
+#define nextfile     (G1.nextfile    )
+#define is_f0_split  (G1.is_f0_split )
+#define t_info       (G.t_info      )
+#define t_tclass     (G.t_tclass    )
+#define t_string     (G.t_string    )
+#define t_lineno     (G.t_lineno    )
+#define t_rollback   (G.t_rollback  )
+#define intvar       (G.intvar      )
+#define fsplitter    (G.fsplitter   )
+#define rsplitter    (G.rsplitter   )
+#define INIT_G() do { \
+       SET_PTR_TO_GLOBALS(xzalloc(sizeof(G1) + sizeof(G)) + sizeof(G1)); \
+       G.next_token__ltclass = TC_OPTERM; \
+       G.evaluate__seed = 1; \
+} while (0)
+
+
+/* function prototypes */
+static void handle_special(var *);
+static node *parse_expr(uint32_t);
+static void chain_group(void);
+static var *evaluate(node *, var *);
+static rstream *next_input_file(void);
+static int fmt_num(char *, int, const char *, double, int);
+static int awk_exit(int) NORETURN;
+
+/* ---- error handling ---- */
+
+static const char EMSG_INTERNAL_ERROR[] ALIGN1 = "Internal error";
+static const char EMSG_UNEXP_EOS[] ALIGN1 = "Unexpected end of string";
+static const char EMSG_UNEXP_TOKEN[] ALIGN1 = "Unexpected token";
+static const char EMSG_DIV_BY_ZERO[] ALIGN1 = "Division by zero";
+static const char EMSG_INV_FMT[] ALIGN1 = "Invalid format specifier";
+static const char EMSG_TOO_FEW_ARGS[] ALIGN1 = "Too few arguments for builtin";
+static const char EMSG_NOT_ARRAY[] ALIGN1 = "Not an array";
+static const char EMSG_POSSIBLE_ERROR[] ALIGN1 = "Possible syntax error";
+static const char EMSG_UNDEF_FUNC[] ALIGN1 = "Call to undefined function";
+#if !ENABLE_FEATURE_AWK_LIBM
+static const char EMSG_NO_MATH[] ALIGN1 = "Math support is not compiled in";
+#endif
+
+static void zero_out_var(var * vp)
+{
+       memset(vp, 0, sizeof(*vp));
+}
+
+static void syntax_error(const char *const message) NORETURN;
+static void syntax_error(const char *const message)
+{
+       bb_error_msg_and_die("%s:%i: %s", g_progname, g_lineno, message);
+}
+
+/* ---- hash stuff ---- */
+
+static unsigned hashidx(const char *name)
+{
+       unsigned idx = 0;
+
+       while (*name) idx = *name++ + (idx << 6) - idx;
+       return idx;
+}
+
+/* create new hash */
+static xhash *hash_init(void)
+{
+       xhash *newhash;
+
+       newhash = xzalloc(sizeof(xhash));
+       newhash->csize = FIRST_PRIME;
+       newhash->items = 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 newsize, i, idx;
+       hash_item **newitems, *hi, *thi;
+
+       if (hash->nprime == ARRAY_SIZE(PRIMES))
+               return;
+
+       newsize = PRIMES[hash->nprime++];
+       newitems = xzalloc(newsize * sizeof(hash_item *));
+
+       for (i = 0; i < hash->csize; 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 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(*hi) + l);
+               strcpy(hi->name, name);
+
+               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 (1) {
+               if (*p == '\\' && p[1] == '\n') {
+                       p++;
+                       t_lineno++;
+               } else if (*p != ' ' && *p != '\t') {
+                       break;
+               }
+               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 ALWAYS_INLINE int isalnum_(int c)
+{
+       return (isalnum(c) || c == '_');
+}
+
+static double my_strtod(char **pp)
+{
+#if ENABLE_DESKTOP
+       if ((*pp)[0] == '0'
+        && ((((*pp)[1] | 0x20) == 'x') || isdigit((*pp)[1]))
+       ) {
+               return strtoull(*pp, pp, 0);
+       }
+#endif
+       return strtod(*pp, pp);
+}
+
+/* -------- 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 i;
+       hash_item *hi, *thi;
+
+       for (i = 0; i < array->csize; i++) {
+               hi = array->items[i];
+               while (hi) {
+                       thi = hi;
+                       hi = hi->next;
+                       free(thi->data.v.string);
+                       free(thi);
+               }
+               array->items[i] = NULL;
+       }
+       array->glen = array->nel = 0;
+}
+
+/* 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)
+{
+       char sidx[sizeof(int)*3 + 1];
+       var *v;
+
+       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 const char *getvar_s(var *v)
+{
+       /* if v is numeric and has no cached string, convert it to string */
+       if ((v->type & (VF_NUMBER | VF_CACHED)) == VF_NUMBER) {
+               fmt_num(g_buf, MAXVARFMT, getvar_s(intvar[CONVFMT]), v->number, TRUE);
+               v->string = xstrdup(g_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 = my_strtod(&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;
+}
+
+/* Used for operands of bitwise ops */
+static unsigned long getvar_i_int(var *v)
+{
+       double d = getvar_i(v);
+
+       /* Casting doubles to longs is undefined for values outside
+        * of target type range. Try to widen it as much as possible */
+       if (d >= 0)
+               return (unsigned long)d;
+       /* Why? Think about d == -4294967295.0 (assuming 32bit longs) */
+       return - (long) (unsigned long) (-d);
+}
+
+static var *copyvar(var *dest, const var *src)
+{
+       if (dest != src) {
+               clrvar(dest);
+               dest->type |= (src->type & ~(VF_DONTTOUCH | VF_FSTR));
+               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;
+       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 (g_cb) {
+               pb = g_cb;
+               if ((g_cb->pos - g_cb->nv) + n <= g_cb->size) break;
+               g_cb = g_cb->next;
+       }
+
+       if (!g_cb) {
+               size = (n <= MINNVBLOCK) ? MINNVBLOCK : n;
+               g_cb = xzalloc(sizeof(nvblock) + size * sizeof(var));
+               g_cb->size = size;
+               g_cb->pos = g_cb->nv;
+               g_cb->prev = pb;
+               /*g_cb->next = NULL; - xzalloc did it */
+               if (pb) pb->next = g_cb;
+       }
+
+       v = r = g_cb->pos;
+       g_cb->pos += n;
+
+       while (v < g_cb->pos) {
+               v->type = 0;
+               v->string = NULL;
+               v++;
+       }
+
+       return r;
+}
+
+static void nvfree(var *v)
+{
+       var *p;
+
+       if (v < g_cb->nv || v >= g_cb->pos)
+               syntax_error(EMSG_INTERNAL_ERROR);
+
+       for (p = v; p < g_cb->pos; p++) {
+               if ((p->type & (VF_ARRAY | VF_CHILD)) == VF_ARRAY) {
+                       clear_array(iamarray(p));
+                       free(p->x.array->items);
+                       free(p->x.array);
+               }
+               if (p->type & VF_WALK)
+                       free(p->x.walker);
+
+               clrvar(p);
+       }
+
+       g_cb->pos = v;
+       while (g_cb->prev && g_cb->pos == g_cb->nv) {
+               g_cb = g_cb->prev;
+       }
+}
+
+/* ------- awk program text parsing ------- */
+
+/* Parse next token pointed by global pos, place results into global ttt.
+ * If token isn't expected, give away. Return token class
+ */
+static uint32_t next_token(uint32_t expected)
+{
+#define concat_inserted (G.next_token__concat_inserted)
+#define save_tclass     (G.next_token__save_tclass)
+#define save_info       (G.next_token__save_info)
+/* Initialized to TC_OPTERM: */
+#define ltclass         (G.next_token__ltclass)
+
+       char *p, *pp, *s;
+       const char *tl;
+       uint32_t tc;
+       const uint32_t *ti;
+       int l;
+
+       if (t_rollback) {
+               t_rollback = FALSE;
+
+       } else if (concat_inserted) {
+               concat_inserted = FALSE;
+               t_tclass = save_tclass;
+               t_info = save_info;
+
+       } else {
+               p = g_pos;
+ readnext:
+               skip_spaces(&p);
+               g_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);
+                               *s = *p++;
+                               if (*s++ == '\\') {
+                                       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_double = my_strtod(&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;
+                                       }
+                               }
+                       }
+               }
+               g_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;
+#undef concat_inserted
+#undef save_tclass
+#undef save_info
+#undef ltclass
+}
+
+static void rollback_token(void)
+{
+       t_rollback = TRUE;
+}
+
+static node *new_node(uint32_t info)
+{
+       node *n;
+
+       n = xzalloc(sizeof(node));
+       n->info = info;
+       n->lineno = g_lineno;
+       return n;
+}
+
+static node *mk_re_node(const 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;
+                                       v = hash_search(ahash, t_string);
+                                       if (v != 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_double);
+                                       else
+                                               setvar_s(v, t_string);
+                                       break;
+
+                               case TC_REGEXP:
+                                       mk_re_node(t_string, cn, 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 != g_progname) {
+               seq->programname = g_progname;
+               n = chain_node(OC_NEWSOURCE);
+               n->l.s = xstrdup(g_progname);
+       }
+
+       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;
+
+       g_pos = p;
+       t_lineno = 1;
+       while ((tclass = next_token(TC_EOF | TC_OPSEQ | TC_GRPSTART |
+                       TC_OPTERM | TC_BEGIN | TC_END | TC_FUNCDECL)) != TC_EOF) {
+
+               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);
+                       g_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(const 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); // TODO: nuke ire, use re+1?
+       }
+       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)
+{
+       int cflags;
+       var *v;
+       const char *s;
+
+       if ((op->info & OPCLSMASK) == OC_REGEXP) {
+               return icase ? op->r.ire : op->l.re;
+       }
+       v = nvalloc(1);
+       s = getvar_s(evaluate(op, v));
+
+       cflags = icase ? REG_EXTENDED | REG_ICASE : REG_EXTENDED;
+       /* Testcase where REG_EXTENDED fails (unpaired '{'):
+        * echo Hi | awk 'gsub("@(samp|code|file)\{","");'
+        * gawk 3.1.5 eats this. We revert to ~REG_EXTENDED
+        * (maybe gsub is not supposed to use REG_EXTENDED?).
+        */
+       if (regcomp(preg, s, cflags)) {
+               cflags &= ~REG_EXTENDED;
+               xregcomp(preg, s, cflags);
+       }
+       nvfree(v);
+       return preg;
+}
+
+/* gradually increasing buffer */
+static void qrealloc(char **b, int n, int *size)
+{
+       if (!*b || n >= *size) {
+               *size = n + (n>>1) + 80;
+               *b = xrealloc(*b, *size);
+       }
+}
+
+/* resize field storage space */
+static void fsrealloc(int size)
+{
+       int i;
+
+       if (size >= maxfields) {
+               i = maxfields;
+               maxfields = size + 16;
+               Fields = xrealloc(Fields, maxfields * sizeof(var));
+               for (; i < maxfields; i++) {
+                       Fields[i].type = VF_SPECIAL;
+                       Fields[i].string = NULL;
+               }
+       }
+
+       if (size < nfields) {
+               for (i = size; i < nfields; i++) {
+                       clrvar(Fields + i);
+               }
+       }
+       nfields = size;
+}
+
+static int awk_split(const char *s, node *spl, char **slist)
+{
+       int l, n = 0;
+       char c[4];
+       char *s1;
+       regmatch_t pmatch[2]; // TODO: why [2]? [1] is enough...
+
+       /* in worst case, each char would be a separate field */
+       *slist = s1 = xzalloc(strlen(s) * 2 + 3);
+       strcpy(s1, s);
+
+       c[0] = c[1] = (char)spl->info;
+       c[2] = c[3] = '\0';
+       if (*getvar_s(intvar[RS]) == '\0')
+               c[2] = '\n';
+
+       if ((spl->info & OPCLSMASK) == OC_REGEXP) {  /* regex split */
+               if (!*s)
+                       return n; /* "": zero fields */
+               n++; /* at least one field will be there */
+               do {
+                       l = strcspn(s, c+2); /* len till next NUL or \n */
+                       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++;
+                               }
+                               n++; /* we saw yet another delimiter */
+                       } else {
+                               pmatch[0].rm_eo = l;
+                               if (s[l])
+                                       pmatch[0].rm_eo++;
+                       }
+                       memcpy(s1, s, l);
+                       /* make sure we remove *all* of the separator chars */
+                       do {
+                               s1[l] = '\0';
+                       } while (++l < pmatch[0].rm_eo);
+                       nextword(&s1);
+                       s += pmatch[0].rm_eo;
+               } while (*s);
+               return n;
+       }
+       if (c[0] == '\0') {  /* null split */
+               while (*s) {
+                       *s1++ = *s++;
+                       *s1++ = '\0';
+                       n++;
+               }
+               return n;
+       }
+       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++;
+               }
+               return n;
+       }
+       /* 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; */
+#define fstrings (G.split_f0__fstrings)
+
+       int i, n;
+       char *s;
+
+       if (is_f0_split)
+               return;
+
+       is_f0_split = TRUE;
+       free(fstrings);
+       fsrealloc(0);
+       n = awk_split(getvar_s(intvar[F0]), &fsplitter.n, &fstrings);
+       fsrealloc(n);
+       s = fstrings;
+       for (i = 0; i < n; i++) {
+               Fields[i].string = nextword(&s);
+               Fields[i].type |= (VF_FSTR | VF_USER | VF_DIRTY);
+       }
+
+       /* set NF manually to avoid side effects */
+       clrvar(intvar[NF]);
+       intvar[NF]->type = VF_NUMBER | VF_SPECIAL;
+       intvar[NF]->number = nfields;
+#undef fstrings
+}
+
+/* perform additional actions when some internal variables changed */
+static void handle_special(var *v)
+{
+       int n;
+       char *b;
+       const char *sep, *s;
+       int sl, l, len, i, bsize;
+
+       if (!(v->type & VF_SPECIAL))
+               return;
+
+       if (v == intvar[NF]) {
+               n = (int)getvar_i(v);
+               fsrealloc(n);
+
+               /* recalculate $0 */
+               sep = getvar_s(intvar[OFS]);
+               sl = strlen(sep);
+               b = NULL;
+               len = 0;
+               for (i = 0; i < n; i++) {
+                       s = getvar_s(&Fields[i]);
+                       l = strlen(s);
+                       if (b) {
+                               memcpy(b+len, sep, sl);
+                               len += sl;
+                       }
+                       qrealloc(&b, len+l+sl, &bsize);
+                       memcpy(b+len, s, l);
+                       len += l;
+               }
+               if (b)
+                       b[len] = '\0';
+               setvar_p(intvar[F0], b);
+               is_f0_split = TRUE;
+
+       } else if (v == intvar[F0]) {
+               is_f0_split = FALSE;
+
+       } else if (v == intvar[FS]) {
+               mk_splitter(getvar_s(v), &fsplitter);
+
+       } else if (v == intvar[RS]) {
+               mk_splitter(getvar_s(v), &rsplitter);
+
+       } else if (v == intvar[IGNORECASE]) {
+               icase = istrue(v);
+
+       } else {                                /* $n */
+               n = getvar_i(intvar[NF]);
+               setvar_i(intvar[NF], n > 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;
+       unsigned i;
+
+       if (v->type & VF_WALK)
+               free(v->x.walker);
+
+       v->type |= VF_WALK;
+       w = v->x.walker = xzalloc(2 + 2*sizeof(char *) + array->glen);
+       w[0] = w[1] = (char *)(w + 2);
+       for (i = 0; i < array->csize; 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[0])
+               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)
+{
+       /* ptest__v is "static": to save stack space? */
+       return istrue(evaluate(pattern, &G.ptest__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(intvar[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(intvar[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 (c && *++s);
+               if (strchr("diouxX", c)) {
+                       r = snprintf(b, size, format, (int)n);
+               } else if (strchr("eEfgG", c)) {
+                       r = snprintf(b, size, format, n);
+               } else {
+                       syntax_error(EMSG_INV_FMT);
+               }
+       }
+       return r;
+}
+
+/* formatted output into an allocated buffer, return ptr to buffer */
+static char *awk_printf(node *n)
+{
+       char *b = NULL;
+       char *fmt, *s, *f;
+       const char *s1;
+       int i, j, incr, bsize;
+       char c, c1;
+       var *v, *arg;
+
+       v = nvalloc(1);
+       fmt = f = xstrdup(getvar_s(evaluate(nextarg(&n), v)));
+
+       i = 0;
+       while (*f) {
+               s = f;
+               while (*f && (*f != '%' || *(++f) == '%'))
+                       f++;
+               while (*f && !isalpha(*f)) {
+                       if (*f == '*')
+                               syntax_error("%*x formats are not supported");
+                       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, const char *repl, int nm, var *src, var *dest, int ex)
+{
+       char *ds = NULL;
+       const char *s;
+       const char *sp;
+       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 = intvar[F0];
+       if (!dest) dest = intvar[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) {
+                       ds[di] = *sp++;
+                       if (!ds[di++]) 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)
+{
+#define tspl (G.exec_builtin__tspl)
+
+       int (*to_xxx)(int);
+       var *tv;
+       node *an[4];
+       var *av[4];
+       const char *as[4];
+       regmatch_t pmatch[2];
+       regex_t sreg, *re;
+       node *spl;
+       uint32_t isr, info;
+       int nargs;
+       time_t tt;
+       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 ((uint32_t)nargs < (info >> 30))
+               syntax_error(EMSG_TOO_FEW_ARGS);
+
+       switch (info & OPNMASK) {
+
+       case B_a2:
+#if ENABLE_FEATURE_AWK_LIBM
+               setvar_i(res, atan2(getvar_i(av[0]), getvar_i(av[1])));
+#else
+               syntax_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 = xstrndup(as[0]+i, n);
+               setvar_p(res, s);
+               break;
+
+       /* Bitwise ops must assume that operands are unsigned. GNU Awk 3.1.5:
+        * awk '{ print or(-1,1) }' gives "4.29497e+09", not "-2.xxxe+09" */
+       case B_an:
+               setvar_i(res, getvar_i_int(av[0]) & getvar_i_int(av[1]));
+               break;
+
+       case B_co:
+               setvar_i(res, ~getvar_i_int(av[0]));
+               break;
+
+       case B_ls:
+               setvar_i(res, getvar_i_int(av[0]) << getvar_i_int(av[1]));
+               break;
+
+       case B_or:
+               setvar_i(res, getvar_i_int(av[0]) | getvar_i_int(av[1]));
+               break;
+
+       case B_rs:
+               setvar_i(res, getvar_i_int(av[0]) >> getvar_i_int(av[1]));
+               break;
+
+       case B_xo:
+               setvar_i(res, getvar_i_int(av[0]) ^ getvar_i_int(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(g_buf, MAXVARFMT,
+                       ((nargs > 0) ? as[0] : "%a %b %d %H:%M:%S %Z %Y"),
+                       localtime(&tt));
+               g_buf[i] = '\0';
+               setvar_s(res, g_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;
+#undef tspl
+}
+
+/*
+ * 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 */
+#define fnargs (G.evaluate__fnargs)
+/* seed is initialized to 1 */
+#define seed   (G.evaluate__seed)
+#define        sreg   (G.evaluate__sreg)
+
+       node *op1;
+       var *v1;
+       union {
+               var *v;
+               const char *s;
+               double d;
+               int i;
+       } L, R;
+       uint32_t opinfo;
+       int 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 = (opinfo & OPNMASK);
+               g_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 == '|') {
+                                               X.rsm->F = popen(R.s, "w");
+                                               if (X.rsm->F == 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(intvar[F0]), X.F);
+                               } else {
+                                       while (op1) {
+                                               L.v = evaluate(nextarg(&op1), v1);
+                                               if (L.v->type & VF_NUMBER) {
+                                                       fmt_num(g_buf, MAXVARFMT, getvar_s(intvar[OFMT]),
+                                                                       getvar_i(L.v), TRUE);
+                                                       fputs(g_buf, X.F);
+                                               } else {
+                                                       fputs(getvar_s(L.v), X.F);
+                                               }
+
+                                               if (op1) fputs(getvar_s(intvar[OFS]), X.F);
+                                       }
+                               }
+                               fputs(getvar_s(intvar[ORS]), X.F);
+
+                       } else {        /* OC_PRINTF */
+                               L.s = awk_printf(op1);
+                               fputs(L.s, X.F);
+                               free((char*)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 {
+                               syntax_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 ):
+                       g_progname = 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 == intvar[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(intvar[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)
+                               syntax_error(EMSG_POSSIBLE_ERROR);
+                       res = evaluate(istrue(L.v) ? op->r.n->l.n : op->r.n->r.n, res);
+                       break;
+
+               case XC( OC_FUNC ):
+                       if (!op->r.f->body.first)
+                               syntax_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 = g_progname;
+                       res = evaluate(op->r.f->body.first, res);
+                       g_progname = 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_for_read(L.s);         /* not xfopen! */
+                                       }
+                               }
+                       } else {
+                               if (!iF) iF = next_input_file();
+                               X.rsm = iF;
+                       }
+
+                       if (!X.rsm->F) {
+                               setvar_i(intvar[ERRNO], errno);
+                               setvar_i(res, -1);
+                               break;
+                       }
+
+                       if (!op->r.n)
+                               R.v = intvar[F0];
+
+                       L.i = awk_getline(X.rsm, R.v);
+                       if (L.i > 0) {
+                               if (!op1) {
+                                       incvar(intvar[FNR]);
+                                       incvar(intvar[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;
+#if ENABLE_FEATURE_AWK_LIBM
+                       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:
+                               syntax_error(EMSG_NO_MATH);
+                               break;
+#endif
+                       case F_sr:
+                               R.d = (double)seed;
+                               seed = op1 ? (unsigned)L.d : (unsigned)time(NULL);
+                               srand(seed);
+                               break;
+
+                       case F_ti:
+                               R.d = time(NULL);
+                               break;
+
+                       case F_le:
+                               if (!op1)
+                                       L.s = getvar_s(intvar[F0]);
+                               R.d = strlen(L.s);
+                               break;
+
+                       case F_sy:
+                               fflush(NULL);
+                               R.d = (ENABLE_FEATURE_ALLOW_EXEC && 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(intvar[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 = intvar[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 = xmalloc(opn);
+                       strcpy(X.s, L.s);
+                       if ((opinfo & OPCLSMASK) == OC_COMMA) {
+                               L.s = getvar_s(intvar[SUBSEP]);
+                               X.s = 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) syntax_error(EMSG_DIV_BY_ZERO);
+                               L.d /= R.d;
+                               break;
+                       case '&':
+#if ENABLE_FEATURE_AWK_LIBM
+                               L.d = pow(L.d, R.d);
+#else
+                               syntax_error(EMSG_NO_MATH);
+#endif
+                               break;
+                       case '%':
+                               if (R.d == 0) syntax_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:
+                       syntax_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;
+#undef fnargs
+#undef seed
+#undef sreg
+}
+
+
+/* -------- main & co. -------- */
+
+static int awk_exit(int r)
+{
+       var tv;
+       unsigned i;
+       hash_item *hi;
+
+       zero_out_var(&tv);
+
+       if (!exiting) {
+               exiting = TRUE;
+               nextrec = FALSE;
+               evaluate(endseq.first, &tv);
+       }
+
+       /* waiting for children */
+       for (i = 0; i < fdhash->csize; 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)
+{
+#define rsm          (G.next_input_file__rsm)
+#define files_happen (G.next_input_file__files_happen)
+
+       FILE *F = NULL;
+       const char *fname, *ind;
+
+       if (rsm.F) fclose(rsm.F);
+       rsm.F = NULL;
+       rsm.pos = rsm.adv = 0;
+
+       do {
+               if (getvar_i(intvar[ARGIND])+1 >= getvar_i(intvar[ARGC])) {
+                       if (files_happen)
+                               return NULL;
+                       fname = "-";
+                       F = stdin;
+               } else {
+                       ind = getvar_s(incvar(intvar[ARGIND]));
+                       fname = getvar_s(findvar(iamarray(intvar[ARGV]), ind));
+                       if (fname && *fname && !is_assignment(fname))
+                               F = xfopen_stdin(fname);
+               }
+       } while (!F);
+
+       files_happen = TRUE;
+       setvar_s(intvar[FILENAME], fname);
+       rsm.F = F;
+       return &rsm;
+#undef rsm
+#undef files_happen
+}
+
+int awk_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int awk_main(int argc, char **argv)
+{
+       unsigned opt;
+       char *opt_F, *opt_W;
+       llist_t *list_v = NULL;
+       llist_t *list_f = NULL;
+       int i, j;
+       var *v;
+       var tv;
+       char **envp;
+       char *vnames = (char *)vNames; /* cheat */
+       char *vvalues = (char *)vValues;
+
+       INIT_G();
+
+       /* Undo busybox.c, or else strtod may eat ','! This breaks parsing:
+        * $1,$2 == '$1,' '$2', NOT '$1' ',' '$2' */
+       if (ENABLE_LOCALE_SUPPORT)
+               setlocale(LC_NUMERIC, "C");
+
+       zero_out_var(&tv);
+
+       /* allocate global buffer */
+       g_buf = xmalloc(MAXVARFMT + 1);
+
+       vhash = hash_init();
+       ahash = hash_init();
+       fdhash = hash_init();
+       fnhash = hash_init();
+
+       /* initialize variables */
+       for (i = 0; *vnames; i++) {
+               intvar[i] = v = newvar(nextword(&vnames));
+               if (*vvalues != '\377')
+                       setvar_s(v, nextword(&vvalues));
+               else
+                       setvar_i(v, 0);
+
+               if (*vnames == '*') {
+                       v->type |= VF_SPECIAL;
+                       vnames++;
+               }
+       }
+
+       handle_special(intvar[FS]);
+       handle_special(intvar[RS]);
+
+       newfile("/dev/stdin")->F = stdin;
+       newfile("/dev/stdout")->F = stdout;
+       newfile("/dev/stderr")->F = stderr;
+
+       /* Huh, people report that sometimes environ is NULL. Oh well. */
+       if (environ) for (envp = environ; *envp; envp++) {
+               /* environ is writable, thus we don't strdup it needlessly */
+               char *s = *envp;
+               char *s1 = strchr(s, '=');
+               if (s1) {
+                       *s1 = '\0';
+                       /* Both findvar and setvar_u take const char*
+                        * as 2nd arg -> environment is not trashed */
+                       setvar_u(findvar(iamarray(intvar[ENVIRON]), s), s1 + 1);
+                       *s1 = '=';
+               }
+       }
+       opt_complementary = "v::f::"; /* -v and -f can occur multiple times */
+       opt = getopt32(argv, "F:v:f:W:", &opt_F, &list_v, &list_f, &opt_W);
+       argv += optind;
+       argc -= optind;
+       if (opt & 0x1)
+               setvar_s(intvar[FS], opt_F); // -F
+       while (list_v) { /* -v */
+               if (!is_assignment(llist_pop(&list_v)))
+                       bb_show_usage();
+       }
+       if (list_f) { /* -f */
+               do {
+                       char *s = NULL;
+                       FILE *from_file;
+
+                       g_progname = llist_pop(&list_f);
+                       from_file = xfopen_stdin(g_progname);
+                       /* one byte is reserved for some trick in next_token */
+                       for (i = j = 1; j > 0; i += j) {
+                               s = xrealloc(s, i + 4096);
+                               j = fread(s + i, 1, 4094, from_file);
+                       }
+                       s[i] = '\0';
+                       fclose(from_file);
+                       parse_program(s + 1);
+                       free(s);
+               } while (list_f);
+               argc++;
+       } else { // no -f: take program from 1st parameter
+               if (!argc)
+                       bb_show_usage();
+               g_progname = "cmd. line";
+               parse_program(*argv++);
+       }
+       if (opt & 0x8) // -W
+               bb_error_msg("warning: unrecognized option '-W %s' ignored", opt_W);
+
+       /* fill in ARGV array */
+       setvar_i(intvar[ARGC], argc);
+       setari_u(intvar[ARGV], 0, "awk");
+       i = 0;
+       while (*argv)
+               setari_u(intvar[ARGV], ++i, *argv++);
+
+       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(intvar[FNR], 0);
+
+               while ((i = awk_getline(iF, intvar[F0])) > 0) {
+                       nextrec = FALSE;
+                       incvar(intvar[NR]);
+                       incvar(intvar[FNR]);
+                       evaluate(mainseq.first, &tv);
+
+                       if (nextfile)
+                               break;
+               }
+
+               if (i < 0)
+                       syntax_error(strerror(errno));
+
+               iF = next_input_file();
+       }
+
+       awk_exit(EXIT_SUCCESS);
+       /*return 0;*/
+}
diff --git a/editors/cmp.c b/editors/cmp.c
new file mode 100644 (file)
index 0000000..2e98e6e
--- /dev/null
@@ -0,0 +1,135 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Mini cmp implementation for busybox
+ *
+ * Copyright (C) 2000,2001 by Matt Kraai <kraai@alumni.carnegiemellon.edu>
+ *
+ * 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 "libbb.h"
+
+static const char fmt_eof[] ALIGN1 = "cmp: EOF on %s\n";
+static const char fmt_differ[] ALIGN1 = "%s %s differ: char %"OFF_FMT"d, line %d\n";
+// This fmt_l_opt uses gnu-isms.  SUSv3 would be "%.0s%.0s%"OFF_FMT"d %o %o\n"
+static const char fmt_l_opt[] ALIGN1 = "%.0s%.0s%"OFF_FMT"d %3o %3o\n";
+
+static const char opt_chars[] ALIGN1 = "sl";
+#define CMP_OPT_s (1<<0)
+#define CMP_OPT_l (1<<1)
+
+int cmp_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int cmp_main(int argc UNUSED_PARAM, char **argv)
+{
+       FILE *fp1, *fp2, *outfile = stdout;
+       const char *filename1, *filename2 = "-";
+       USE_DESKTOP(off_t skip1 = 0, skip2 = 0;)
+       off_t char_pos = 0;
+       int line_pos = 1; /* Hopefully won't overflow... */
+       const char *fmt;
+       int c1, c2;
+       unsigned opt;
+       int retval = 0;
+
+       xfunc_error_retval = 2; /* 1 is returned if files are different. */
+
+       opt_complementary = "-1"
+                       USE_DESKTOP(":?4")
+                       SKIP_DESKTOP(":?2")
+                       ":l--s:s--l";
+       opt = getopt32(argv, opt_chars);
+       argv += optind;
+
+       filename1 = *argv;
+       fp1 = xfopen_stdin(filename1);
+
+       if (*++argv) {
+               filename2 = *argv;
+#if ENABLE_DESKTOP
+               if (*++argv) {
+                       skip1 = XATOOFF(*argv);
+                       if (*++argv) {
+                               skip2 = XATOOFF(*argv);
+                       }
+               }
+#endif
+       }
+
+       fp2 = xfopen_stdin(filename2);
+       if (fp1 == fp2) {               /* Paranoia 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;
+
+#if ENABLE_DESKTOP
+       while (skip1) { getc(fp1); skip1--; }
+       while (skip2) { getc(fp2); skip2--; }
+#endif
+       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/editors/diff.c b/editors/diff.c
new file mode 100644 (file)
index 0000000..7fce70d
--- /dev/null
@@ -0,0 +1,1342 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Mini diff implementation for busybox, adapted from OpenBSD diff.
+ *
+ * Copyright (C) 2006 by Robert Sullivan <cogito.ergo.cogito@hotmail.com>
+ * Copyright (c) 2003 Todd C. Miller <Todd.Miller@courtesan.com>
+ *
+ * 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 "libbb.h"
+
+// #define FSIZE_MAX 32768
+
+/* NOINLINEs added to prevent gcc from merging too much into diffreg()
+ * (it bites more than it can (efficiently) chew). */
+
+/*
+ * Output flags
+ */
+enum {
+       /* Print a header/footer between files */
+       /* D_HEADER = 1, - unused */
+       /* Treat file as empty (/dev/null) */
+       D_EMPTY1 = 2 * ENABLE_FEATURE_DIFF_DIR,
+       D_EMPTY2 = 4 * ENABLE_FEATURE_DIFF_DIR,
+};
+
+/*
+ * 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_ISDIR1 - path1 a dir, path2 a file
+ * D_ISDIR2 - 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) - unused */
+#define D_ISDIR1        (1 << 4)
+#define D_ISDIR2        (1 << 5)
+#define D_ERROR         (1 << 6)
+#define D_SKIPPED1      (1 << 7)
+#define D_SKIPPED2      (1 << 8)
+
+/* Command line options */
+#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)
+
+
+struct cand {
+       int x;
+       int y;
+       int pred;
+};
+
+struct line {
+       int serial;
+       int value;
+};
+
+/*
+ * 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 */
+};
+
+
+#define g_read_buf bb_common_bufsiz1
+
+struct globals {
+       bool anychange;
+       smallint exit_status;
+       int opt_U_context;
+       size_t max_context;     /* size of context_vec_start */
+       USE_FEATURE_DIFF_DIR(int dl_count;)
+       USE_FEATURE_DIFF_DIR(char **dl;)
+       char *opt_S_start;
+       const char *label1;
+       const char *label2;
+       int *J;                 /* will be overlaid on class */
+       int clen;
+       int pref, suff;         /* length of prefix and suffix */
+       int nlen[2];
+       int slen[2];
+       int clistlen;           /* the length of clist */
+       struct cand *clist;     /* merely a free storage pot for candidates */
+       long *ixnew;            /* will be overlaid on nfile[1] */
+       long *ixold;            /* will be overlaid on klist */
+       struct line *nfile[2];
+       struct line *sfile[2];  /* shortened by pruning common prefix/suffix */
+       struct context_vec *context_vec_start;
+       struct context_vec *context_vec_end;
+       struct context_vec *context_vec_ptr;
+       char *tempname1, *tempname2;
+       struct stat stb1, stb2;
+};
+#define G (*ptr_to_globals)
+#define anychange          (G.anychange         )
+#define exit_status        (G.exit_status       )
+#define opt_U_context      (G.opt_U_context     )
+#define max_context        (G.max_context       )
+#define dl_count           (G.dl_count          )
+#define dl                 (G.dl                )
+#define opt_S_start        (G.opt_S_start       )
+#define label1             (G.label1            )
+#define label2             (G.label2            )
+#define J                  (G.J                 )
+#define clen               (G.clen              )
+#define pref               (G.pref              )
+#define suff               (G.suff              )
+#define nlen               (G.nlen              )
+#define slen               (G.slen              )
+#define clistlen           (G.clistlen          )
+#define clist              (G.clist             )
+#define ixnew              (G.ixnew             )
+#define ixold              (G.ixold             )
+#define nfile              (G.nfile             )
+#define sfile              (G.sfile             )
+#define context_vec_start  (G.context_vec_start )
+#define context_vec_end    (G.context_vec_end   )
+#define context_vec_ptr    (G.context_vec_ptr   )
+#define stb1               (G.stb1              )
+#define stb2               (G.stb2              )
+#define tempname1          (G.tempname1         )
+#define tempname2          (G.tempname2         )
+#define INIT_G() do { \
+       SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
+       opt_U_context = 3; \
+       max_context = 64; \
+} while (0)
+
+
+#if ENABLE_FEATURE_DIFF_DIR
+static void print_only(const char *path, const char *entry)
+{
+       printf("Only in %s: %s\n", path, entry);
+}
+#endif
+
+
+static void print_status(int val, char *_path1, char *_path2)
+{
+       /*const char *const _entry = entry ? entry : "";*/
+       /*char *const _path1 = entry ? concat_path_file(path1, _entry) : path1;*/
+       /*char *const _path2 = entry ? concat_path_file(path2, _entry) : path2;*/
+
+       switch (val) {
+/*     case D_ONLY:
+               print_only(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 (option_mask32 & FLAG_q)
+                       printf("Files %s and %s differ\n", _path1, _path2);
+               break;
+       case D_SAME:
+               if (option_mask32 & FLAG_s)
+                       printf("Files %s and %s are identical\n", _path1, _path2);
+               break;
+       case D_ISDIR1:
+               printf("File %s is a %s while file %s is a %s\n",
+                          _path1, "directory", _path2, "regular file");
+               break;
+       case D_ISDIR2:
+               printf("File %s is a %s while file %s is a %s\n",
+                          _path1, "regular file", _path2, "directory");
+               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);
+       }
+*/
+}
+
+
+/* Read line, return its nonzero hash. Return 0 if EOF.
+ *
+ * Hash function taken from Robert Sedgewick, Algorithms in C, 3d ed., p 578.
+ */
+static ALWAYS_INLINE int fiddle_sum(int sum, int t)
+{
+       return sum * 127 + t;
+}
+static int readhash(FILE *fp)
+{
+       int i, t, space;
+       int sum;
+
+       sum = 1;
+       space = 0;
+       i = 0;
+       if (!(option_mask32 & (FLAG_b | FLAG_w))) {
+               while ((t = getc(fp)) != '\n') {
+                       if (t == EOF) {
+                               if (i == 0)
+                                       return 0;
+                               break;
+                       }
+                       sum = fiddle_sum(sum, t);
+                       i = 1;
+               }
+       } else {
+               while (1) {
+                       switch (t = getc(fp)) {
+                       case '\t':
+                       case '\r':
+                       case '\v':
+                       case '\f':
+                       case ' ':
+                               space = 1;
+                               continue;
+                       default:
+                               if (space && !(option_mask32 & FLAG_w)) {
+                                       i = 1;
+                                       space = 0;
+                               }
+                               sum = fiddle_sum(sum, t);
+                               i = 1;
+                               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);
+}
+
+
+/* Our diff implementation is using seek.
+ * When we meet non-seekable file, we must make a temp copy.
+ */
+static char *make_temp(FILE *f, struct stat *sb)
+{
+       char *name;
+       int fd;
+
+       if (S_ISREG(sb->st_mode) || S_ISBLK(sb->st_mode))
+               return NULL;
+       name = xstrdup("/tmp/difXXXXXX");
+       fd = mkstemp(name);
+       if (fd < 0)
+               bb_perror_msg_and_die("mkstemp");
+       if (bb_copyfd_eof(fileno(f), fd) < 0) {
+ clean_up:
+               unlink(name);
+               xfunc_die(); /* error message is printed by bb_copyfd_eof */
+       }
+       fstat(fd, sb);
+       close(fd);
+       if (freopen(name, "r+", f) == NULL) {
+               bb_perror_msg("freopen");
+               goto clean_up;
+       }
+       return name;
+}
+
+
+/*
+ * Check to see if the given files differ.
+ * Returns 0 if they are the same, 1 if different, and -1 on error.
+ */
+static NOINLINE int files_differ(FILE *f1, FILE *f2)
+{
+       size_t i, j;
+
+       /* Prevent making copies for "/dev/null" (too common) */
+       /* Deal with input from pipes etc */
+       tempname1 = make_temp(f1, &stb1);
+       tempname2 = make_temp(f2, &stb2);
+       if (stb1.st_size != stb2.st_size) {
+               return 1;
+       }
+       while (1) {
+               i = fread(g_read_buf,                    1, COMMON_BUFSIZE/2, f1);
+               j = fread(g_read_buf + COMMON_BUFSIZE/2, 1, COMMON_BUFSIZE/2, f2);
+               if (i != j)
+                       return 1;
+               if (i == 0)
+                       return (ferror(f1) || ferror(f2)) ? -1 : 0;
+               if (memcmp(g_read_buf,
+                          g_read_buf + COMMON_BUFSIZE/2, i) != 0)
+                       return 1;
+       }
+}
+
+
+static void prepare(int i, FILE *fp /*, off_t filesize*/)
+{
+       struct line *p;
+       int h;
+       size_t j, sz;
+
+       rewind(fp);
+
+       /*sz = (filesize <= FSIZE_MAX ? filesize : FSIZE_MAX) / 25;*/
+       /*if (sz < 100)*/
+       sz = 100;
+
+       p = xmalloc((sz + 3) * sizeof(p[0]));
+       j = 0;
+       while ((h = readhash(fp)) != 0) { /* while not EOF */
+               if (j == sz) {
+                       sz = sz * 3 / 2;
+                       p = xrealloc(p, (sz + 3) * sizeof(p[0]));
+               }
+               p[++j].value = h;
+       }
+       nlen[i] = j;
+       nfile[i] = p;
+}
+
+
+static void prune(void)
+{
+       int i, j;
+
+       for (pref = 0; pref < nlen[0] && pref < nlen[1] &&
+               nfile[0][pref + 1].value == nfile[1][pref + 1].value; pref++)
+               continue;
+       for (suff = 0; suff < nlen[0] - pref && suff < nlen[1] - pref &&
+               nfile[0][nlen[0] - suff].value == nfile[1][nlen[1] - suff].value;
+               suff++)
+               continue;
+       for (j = 0; j < 2; j++) {
+               sfile[j] = nfile[j] + pref;
+               slen[j] = nlen[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;
+
+       if (n == 0)
+               return 0;
+       x = 1;
+       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 =
+               (option_mask32 & 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 <= nlen[0]; i++)
+               J[i] = i <= pref ? i : i > nlen[0] - suff ? i + nlen[1] - nlen[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 NOINLINE 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 <= nlen[0]; i++) {
+               if (J[i] == 0) {
+                       ixold[i] = ctold += skipline(f1);
+                       continue;
+               }
+               while (j < J[i]) {
+                       ixnew[j] = ctnew += skipline(f2);
+                       j++;
+               }
+               if (option_mask32 & (FLAG_b | FLAG_w | FLAG_i)) {
+                       while (1) {
+                               c = getc(f1);
+                               d = getc(f2);
+                               /*
+                                * GNU diff ignores a missing newline
+                                * in one file if bflag || wflag.
+                                */
+                               if ((option_mask32 & (FLAG_b | FLAG_w))
+                                && ((c == EOF && d == '\n') || (c == '\n' && d == EOF))
+                               ) {
+                                       break;
+                               }
+                               ctold++;
+                               ctnew++;
+                               if ((option_mask32 & FLAG_b) && isspace(c) && isspace(d)) {
+                                       do {
+                                               if (c == '\n')
+                                                       break;
+                                               ctold++;
+                                               c = getc(f1);
+                                       } while (isspace(c));
+                                       do {
+                                               if (d == '\n')
+                                                       break;
+                                               ctnew++;
+                                               d = getc(f2);
+                                       } while (isspace(d));
+                               } else if (option_mask32 & 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++;
+                               c = getc(f1);
+                               d = getc(f2);
+                               if (c != d) {
+                                       J[i] = 0;
+                                       if (c != '\n' && c != EOF)
+                                               ctold += skipline(f1);
+/* was buggy? "if (d != '\n' && c != EOF)" */
+                                       if (d != '\n' && d != EOF)
+                                               ctnew += skipline(f2);
+                                       break;
+                               }
+                               if (c == '\n' || c == EOF)
+                                       break;
+                       }
+               }
+               ixold[i] = ctold;
+               ixnew[j] = ctnew;
+               j++;
+       }
+       for (; j <= nlen[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 void fetch(long *f, int a, int b, FILE *lb, int ch)
+{
+       int i, j, c, lastc, col, nc;
+
+       if (a > b)
+               return;
+       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 (option_mask32 & FLAG_T)
+                               putchar('\t');
+               }
+               col = 0;
+               for (j = 0, lastc = '\0'; j < nc; j++, lastc = c) {
+                       c = getc(lb);
+                       if (c == EOF) {
+                               printf("\n\\ No newline at end of file\n");
+                               return;
+                       }
+                       if (c == '\t' && (option_mask32 & FLAG_t)) {
+                               do {
+                                       putchar(' ');
+                               } while (++col & 7);
+                       } else {
+                               putchar(c);
+                               col++;
+                       }
+               }
+       }
+}
+
+
+#if ENABLE_FEATURE_DIFF_BINARY
+static int asciifile(FILE *f)
+{
+       int i, cnt;
+
+       if (option_mask32 & FLAG_a)
+               return 1;
+       rewind(f);
+       cnt = fread(g_read_buf, 1, COMMON_BUFSIZE, f);
+       for (i = 0; i < cnt; i++) {
+               if (!isprint(g_read_buf[i])
+                && !isspace(g_read_buf[i])
+               ) {
+                       return 0;
+               }
+       }
+       return 1;
+}
+#else
+#define asciifile(f) 1
+#endif
+
+
+/* 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 - opt_U_context);
+       upb = MIN(nlen[0], context_vec_ptr->b + opt_U_context);
+       lowc = MAX(1, cvp->c - opt_U_context);
+       upd = MIN(nlen[1], context_vec_ptr->d + opt_U_context);
+
+       printf("@@ -");
+       uni_range(lowa, upb);
+       printf(" +");
+       uni_range(lowc, upd);
+       printf(" @@\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 0
+               switch (ch) {
+               case 'c':
+// fetch() seeks!
+                       fetch(ixold, lowa, a - 1, f1, ' ');
+                       fetch(ixold, a, b, f1, '-');
+                       fetch(ixnew, c, d, f2, '+');
+                       break;
+               case 'd':
+                       fetch(ixold, lowa, a - 1, f1, ' ');
+                       fetch(ixold, a, b, f1, '-');
+                       break;
+               case 'a':
+                       fetch(ixnew, lowc, c - 1, f2, ' ');
+                       fetch(ixnew, c, d, f2, '+');
+                       break;
+               }
+#else
+               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, '+');
+#endif
+               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 (label1)
+               printf("--- %s\n", label1);
+       else
+               printf("--- %s\t%s", file1, ctime(&stb1.st_mtime));
+       if (label2)
+               printf("+++ %s\n", label2);
+       else
+               printf("+++ %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 than 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(const char *file1, FILE *f1, const char *file2, FILE *f2,
+                       int a, int b, int c, int d)
+{
+       if ((a > b && c > d) || (option_mask32 & FLAG_q)) {
+               anychange = 1;
+               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);
+       } else if (a > context_vec_ptr->b + (2 * opt_U_context) + 1
+               && c > context_vec_ptr->d + (2 * opt_U_context) + 1
+       ) {
+               /*
+                * If this change is more than 'context' lines from the
+                * previous change, dump the record and reset it.
+                */
+// dump_unified_vec() seeks!
+               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;
+       anychange = 1;
+}
+
+
+static void output(const char *file1, FILE *f1, const 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 = nlen[0];
+       J[0] = 0;
+       J[m + 1] = nlen[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() seeks!
+               change(file1, f1, file2, f2, i0, i1, j00, j01);
+       }
+       if (m == 0) {
+// change() seeks!
+               change(file1, f1, file2, f2, 1, 0, 1, nlen[1]);
+       }
+       if (anychange != 0 && !(option_mask32 & FLAG_q)) {
+// dump_unified_vec() seeks!
+               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 in 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.
+ */
+/* NB: files can be not REGular. The only sure thing that they
+ * are not both DIRectories. */
+static unsigned diffreg(const char *file1, const char *file2, int flags)
+{
+       int *member;     /* will be overlaid on nfile[1] */
+       int *class;      /* will be overlaid on nfile[0] */
+       int *klist;      /* will be overlaid on nfile[0] after class */
+       FILE *f1;
+       FILE *f2;
+       unsigned rval;
+       int i;
+
+       anychange = 0;
+       context_vec_ptr = context_vec_start - 1;
+       tempname1 = tempname2 = NULL;
+
+       /* Is any of them a directory? Then it's simple */
+       if (S_ISDIR(stb1.st_mode) != S_ISDIR(stb2.st_mode))
+               return (S_ISDIR(stb1.st_mode) ? D_ISDIR1 : D_ISDIR2);
+
+       /* None of them are directories */
+       rval = D_SAME;
+
+       if (flags & D_EMPTY1)
+               /* can't be stdin, but xfopen_stdin() is smaller code */
+               file1 = bb_dev_null;
+       f1 = xfopen_stdin(file1);
+       if (flags & D_EMPTY2)
+               file2 = bb_dev_null;
+       f2 = xfopen_stdin(file2);
+
+       /* NB: if D_EMPTY1/2 is set, other file is always a regular file,
+        * not pipe/fifo/chardev/etc - D_EMPTY is used by "diff -r" only,
+        * and it never diffs non-ordinary files in subdirs. */
+       if (!(flags & (D_EMPTY1 | D_EMPTY2))) {
+               /* Quick check whether they are different */
+               /* NB: copies non-REG files to tempfiles and fills tempname1/2 */
+               i = files_differ(f1, f2);
+               if (i != 1) { /* not different? */
+                       if (i != 0) /* error? */
+                               exit_status |= 2;
+                       goto closem;
+               }
+       }
+
+       if (!asciifile(f1) || !asciifile(f2)) {
+               rval = D_BINARY;
+               exit_status |= 1;
+               goto closem;
+       }
+
+// Rewind inside!
+       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 *) nfile[1];
+       equiv(sfile[0], slen[0], sfile[1], slen[1], member);
+//TODO: xrealloc_vector?
+       member = xrealloc(member, (slen[1] + 2) * sizeof(int));
+
+       class = (int *) nfile[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, (nlen[0] + 2) * sizeof(int));
+       unravel(klist[i]);
+       free(clist);
+       free(klist);
+
+       ixold = xrealloc(ixold, (nlen[0] + 2) * sizeof(long));
+       ixnew = xrealloc(ixnew, (nlen[1] + 2) * sizeof(long));
+// Rewind inside!
+       check(f1, f2);
+// Rewind inside!
+       output(file1, f1, file2, f2);
+
+ closem:
+       if (anychange) {
+               exit_status |= 1;
+               if (rval == D_SAME)
+                       rval = D_DIFFER;
+       }
+       fclose_if_not_stdin(f1);
+       fclose_if_not_stdin(f2);
+       if (tempname1) {
+               unlink(tempname1);
+               free(tempname1);
+       }
+       if (tempname2) {
+               unlink(tempname2);
+               free(tempname2);
+       }
+       return rval;
+}
+
+
+#if ENABLE_FEATURE_DIFF_DIR
+static void do_diff(char *dir1, char *path1, char *dir2, char *path2)
+{
+       int flags = 0; /*D_HEADER;*/
+       int val;
+       char *fullpath1 = NULL; /* if -N */
+       char *fullpath2 = NULL;
+
+       if (path1)
+               fullpath1 = concat_path_file(dir1, path1);
+       if (path2)
+               fullpath2 = concat_path_file(dir2, path2);
+
+       if (!fullpath1 || stat(fullpath1, &stb1) != 0) {
+               flags |= D_EMPTY1;
+               memset(&stb1, 0, sizeof(stb1));
+               if (path2) {
+                       free(fullpath1);
+                       fullpath1 = concat_path_file(dir1, path2);
+               }
+       }
+       if (!fullpath2 || stat(fullpath2, &stb2) != 0) {
+               flags |= D_EMPTY2;
+               memset(&stb2, 0, sizeof(stb2));
+               stb2.st_mode = stb1.st_mode;
+               if (path1) {
+                       free(fullpath2);
+                       fullpath2 = concat_path_file(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);
+               goto ret;
+       }
+
+       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 {
+               /* Both files are either REGular or DIRectories */
+               val = diffreg(fullpath1, fullpath2, flags);
+       }
+
+       print_status(val, fullpath1, fullpath2 /*, NULL*/);
+ ret:
+       free(fullpath1);
+       free(fullpath2);
+}
+#endif
+
+
+#if ENABLE_FEATURE_DIFF_DIR
+/* This function adds a filename to dl, the directory listing. */
+static int FAST_FUNC add_to_dirlist(const char *filename,
+               struct stat *sb UNUSED_PARAM,
+               void *userdata,
+               int depth UNUSED_PARAM)
+{
+       dl = xrealloc_vector(dl, 5, dl_count);
+       dl[dl_count] = xstrdup(filename + (int)(ptrdiff_t)userdata);
+       dl_count++;
+       return TRUE;
+}
+
+
+/* This returns a sorted directory listing. */
+static char **get_recursive_dirlist(char *path)
+{
+       dl_count = 0;
+       dl = xzalloc(sizeof(dl[0]));
+
+       /* We need to trim root directory prefix.
+        * Using void *userdata to specify its length,
+        * add_to_dirlist will remove it. */
+       if (option_mask32 & FLAG_r) {
+               recursive_action(path, ACTION_RECURSE|ACTION_FOLLOWLINKS,
+                                       add_to_dirlist, /* file_action */
+                                       NULL, /* dir_action */
+                                       (void*)(ptrdiff_t)(strlen(path) + 1),
+                                       0);
+       } else {
+               DIR *dp;
+               struct dirent *ep;
+
+               dp = warn_opendir(path);
+               while ((ep = readdir(dp))) {
+                       if (!strcmp(ep->d_name, "..") || LONE_CHAR(ep->d_name, '.'))
+                               continue;
+                       add_to_dirlist(ep->d_name, NULL, (void*)(int)0, 0);
+               }
+               closedir(dp);
+       }
+
+       /* Sort dl alphabetically. */
+       qsort_string_vector(dl, dl_count);
+
+       dl[dl_count] = NULL;
+       return dl;
+}
+
+
+static void diffdir(char *p1, char *p2)
+{
+       char **dirlist1, **dirlist2;
+       char *dp1, *dp2;
+       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_recursive_dirlist(p1);
+       dirlist2 = get_recursive_dirlist(p2);
+
+       /* If -S was set, find the starting point. */
+       if (opt_S_start) {
+               while (*dirlist1 != NULL && strcmp(*dirlist1, opt_S_start) < 0)
+                       dirlist1++;
+               while (*dirlist2 != NULL && strcmp(*dirlist2, opt_S_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 (option_mask32 & FLAG_N)
+                               do_diff(p1, dp1, p2, NULL);
+                       else
+                               print_only(p1, dp1);
+                       dirlist1++;
+               } else {
+                       if (option_mask32 & FLAG_N)
+                               do_diff(p1, NULL, p2, dp2);
+                       else
+                               print_only(p2, dp2);
+                       dirlist2++;
+               }
+       }
+}
+#endif
+
+
+int diff_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int diff_main(int argc UNUSED_PARAM, char **argv)
+{
+       int gotstdin = 0;
+       char *f1, *f2;
+       llist_t *L_arg = NULL;
+
+       INIT_G();
+
+       /* exactly 2 params; collect multiple -L <label>; -U N */
+       opt_complementary = "=2:L::U+";
+       getopt32(argv, "abdiL:NqrsS:tTU:wu"
+                       "p" /* ignored (for compatibility) */,
+                       &L_arg, &opt_S_start, &opt_U_context);
+       /*argc -= optind;*/
+       argv += optind;
+       while (L_arg) {
+               if (label1 && label2)
+                       bb_show_usage();
+               if (label1) /* then label2 is NULL */
+                       label2 = label1;
+               label1 = llist_pop(&L_arg);
+       }
+
+       /*
+        * Do sanity checks, fill in stb1 and stb2 and call the appropriate
+        * driver routine.  Both drivers use the contents of stb1 and stb2.
+        */
+       f1 = argv[0];
+       f2 = argv[1];
+       if (LONE_DASH(f1)) {
+               fstat(STDIN_FILENO, &stb1);
+               gotstdin++;
+       } else
+               xstat(f1, &stb1);
+       if (LONE_DASH(f2)) {
+               fstat(STDIN_FILENO, &stb2);
+               gotstdin++;
+       } else
+               xstat(f2, &stb2);
+
+       if (gotstdin && (S_ISDIR(stb1.st_mode) || S_ISDIR(stb2.st_mode)))
+               bb_error_msg_and_die("can't compare stdin to a directory");
+
+       if (S_ISDIR(stb1.st_mode) && S_ISDIR(stb2.st_mode)) {
+#if ENABLE_FEATURE_DIFF_DIR
+               diffdir(f1, f2);
+               return exit_status;
+#else
+               bb_error_msg_and_die("no support for directory comparison");
+#endif
+       }
+
+       if (S_ISDIR(stb1.st_mode)) { /* "diff dir file" */
+               /* NB: "diff dir      dir2/dir3/file" must become
+                *     "diff dir/file dir2/dir3/file" */
+               char *slash = strrchr(f2, '/');
+               f1 = concat_path_file(f1, slash ? slash + 1 : f2);
+               xstat(f1, &stb1);
+       }
+       if (S_ISDIR(stb2.st_mode)) {
+               char *slash = strrchr(f1, '/');
+               f2 = concat_path_file(f2, slash ? slash + 1 : f1);
+               xstat(f2, &stb2);
+       }
+
+       /* diffreg can get non-regular files here,
+        * they are not both DIRestories */
+       print_status((gotstdin > 1 ? D_SAME : diffreg(f1, f2, 0)),
+                       f1, f2 /*, NULL*/);
+       return exit_status;
+}
diff --git a/editors/ed.c b/editors/ed.c
new file mode 100644 (file)
index 0000000..9084a17
--- /dev/null
@@ -0,0 +1,1049 @@
+/* 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 "libbb.h"
+
+typedef struct LINE {
+       struct LINE *next;
+       struct LINE *prev;
+       int len;
+       char data[1];
+} LINE;
+
+
+#define searchString bb_common_bufsiz1
+
+enum {
+       USERSIZE = sizeof(searchString) > 1024 ? 1024
+                : sizeof(searchString) - 1, /* max line length typed in by user */
+       INITBUF_SIZE = 1024, /* initial buffer size */
+};
+
+struct globals {
+       int curNum;
+       int lastNum;
+       int bufUsed;
+       int bufSize;
+       LINE *curLine;
+       char *bufBase;
+       char *bufPtr;
+       char *fileName;
+       LINE lines;
+       smallint dirty;
+       int marks[26];
+};
+#define G (*ptr_to_globals)
+#define curLine            (G.curLine           )
+#define bufBase            (G.bufBase           )
+#define bufPtr             (G.bufPtr            )
+#define fileName           (G.fileName          )
+#define curNum             (G.curNum            )
+#define lastNum            (G.lastNum           )
+#define bufUsed            (G.bufUsed           )
+#define bufSize            (G.bufSize           )
+#define dirty              (G.dirty             )
+#define lines              (G.lines             )
+#define marks              (G.marks             )
+#define INIT_G() do { \
+       SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
+} while (0)
+
+
+static void doCommands(void);
+static void subCommand(const char *cmd, int num1, int num2);
+static int getNum(const char **retcp, smallint *retHaveNum, int *retNum);
+static int setCurNum(int num);
+static void addLines(int num);
+static int insertLine(int num, const char *data, int len);
+static void 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);
+
+
+static int bad_nums(int num1, int num2, const char *for_what)
+{
+       if ((num1 < 1) || (num2 > lastNum) || (num1 > num2)) {
+               bb_error_msg("bad line range for %s", for_what);
+               return 1;
+       }
+       return 0;
+}
+
+
+static char *skip_blank(const char *cp)
+{
+       while (isblank(*cp))
+               cp++;
+       return (char *)cp;
+}
+
+
+int ed_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int ed_main(int argc UNUSED_PARAM, char **argv)
+{
+       INIT_G();
+
+       bufSize = INITBUF_SIZE;
+       bufBase = xmalloc(bufSize);
+       bufPtr = bufBase;
+       lines.next = &lines;
+       lines.prev = &lines;
+
+       if (argv[1]) {
+               fileName = xstrdup(argv[1]);
+               if (!readLines(fileName, 1)) {
+                       return EXIT_SUCCESS;
+               }
+               if (lastNum)
+                       setCurNum(1);
+               dirty = FALSE;
+       }
+
+       doCommands();
+       return EXIT_SUCCESS;
+}
+
+/*
+ * Read commands until we are told to stop.
+ */
+static void doCommands(void)
+{
+       const char *cp;
+       char *endbuf, buf[USERSIZE];
+       int len, num1, num2;
+       smallint have1, have2;
+
+       while (TRUE) {
+               /* Returns:
+                * -1 on read errors or EOF, or on bare Ctrl-D.
+                * 0  on ctrl-C,
+                * >0 length of input string, including terminating '\n'
+                */
+               len = read_line_input(": ", buf, sizeof(buf), NULL);
+               if (len <= 0)
+                       return;
+               endbuf = &buf[len - 1];
+               while ((endbuf > buf) && isblank(endbuf[-1]))
+                       endbuf--;
+               *endbuf = '\0';
+
+               cp = skip_blank(buf);
+               have1 = FALSE;
+               have2 = FALSE;
+
+               if ((curNum == 0) && (lastNum > 0)) {
+                       curNum = 1;
+                       curLine = lines.next;
+               }
+
+               if (!getNum(&cp, &have1, &num1))
+                       continue;
+
+               cp = skip_blank(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;
+                       }
+                       cp = skip_blank(cp);
+                       if (*cp == '\0') {
+                               if (fileName)
+                                       printf("\"%s\"\n", fileName);
+                               else
+                                       printf("No file name\n");
+                               break;
+                       }
+                       free(fileName);
+                       fileName = xstrdup(cp);
+                       break;
+
+               case 'i':
+                       addLines(num1);
+                       break;
+
+               case 'k':
+                       cp = skip_blank(cp);
+                       if ((*cp < 'a') || (*cp > 'z') || 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':
+                       cp = skip_blank(cp);
+                       if (have1 || *cp) {
+                               bb_error_msg("bad quit command");
+                               break;
+                       }
+                       if (!dirty)
+                               return;
+                       len = read_line_input("Really quit? ", buf, 16, NULL);
+                       /* read error/EOF - no way to continue */
+                       if (len < 0)
+                               return;
+                       cp = skip_blank(buf);
+                       if ((*cp | 0x20) == 'y') /* Y or y */
+                               return;
+                       break;
+
+               case 'r':
+                       if (*cp && !isblank(*cp)) {
+                               bb_error_msg("bad read command");
+                               break;
+                       }
+                       cp = skip_blank(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 = xstrdup(cp);
+                       break;
+
+               case 's':
+                       subCommand(cp, num1, num2);
+                       break;
+
+               case 'w':
+                       if (*cp && !isblank(*cp)) {
+                               bb_error_msg("bad write command");
+                               break;
+                       }
+                       cp = skip_blank(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 (bad_nums(num1, num2, "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 = (char*)"";
+
+       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 = 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 (1) {
+               /* Returns:
+                * -1 on read errors or EOF, or on bare Ctrl-D.
+                * 0  on ctrl-C,
+                * >0 length of input string, including terminating '\n'
+                */
+               len = read_line_input("", buf, sizeof(buf), NULL);
+               if (len <= 0) {
+                       /* Previously, ctrl-C was exiting to shell.
+                        * Now we exit to ed prompt. Is in important? */
+                       return;
+               }
+               if ((buf[0] == '.') && (buf[1] == '\n') && (buf[2] == '\0'))
+                       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, smallint *retHaveNum, int *retNum)
+{
+       const char *cp;
+       char *endStr, str[USERSIZE];
+       int value, num;
+       smallint haveNum, minus;
+
+       cp = *retcp;
+       value = 0;
+       haveNum = FALSE;
+       minus = 0;
+
+       while (TRUE) {
+               cp = skip_blank(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 += (minus ? -num : num);
+
+               cp = skip_blank(cp);
+
+               switch (*cp) {
+                       case '-':
+                               minus = 1;
+                               cp++;
+                               break;
+
+                       case '+':
+                               minus = 0;
+                               cp++;
+                               break;
+
+                       default:
+                               *retcp = cp;
+                               *retHaveNum = haveNum;
+                               *retNum = value;
+                               return TRUE;
+               }
+       }
+}
+
+
+/*
+ * 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 = safe_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 (bad_nums(num1, num2, "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 (full_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 (bad_nums(num1, num2, "print"))
+               return FALSE;
+
+       lp = findLine(num1);
+       if (lp == NULL)
+               return FALSE;
+
+       while (num1 <= num2) {
+               if (!expandFlag) {
+                       write(STDOUT_FILENO, 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 = (unsigned char) *cp++;
+                       fputc_printable(ch | PRINTABLE_META, 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 void deleteLines(int num1, int num2)
+{
+       LINE *lp, *nlp, *plp;
+       int count;
+
+       if (bad_nums(num1, num2, "delete"))
+               return;
+
+       lp = findLine(num1);
+       if (lp == NULL)
+               return;
+
+       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;
+               free(lp);
+               lp = nlp;
+       }
+
+       dirty = TRUE;
+}
+
+
+/*
+ * Search for a line which contains the specified string.
+ * If the string is "", 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 (bad_nums(num1, num2, "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 (file)
index 0000000..e8482a7
--- /dev/null
@@ -0,0 +1,254 @@
+/* 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 patch (not just one hunk) will fail.
+ *   - Reject file isnt saved
+ */
+
+#include "libbb.h"
+
+static unsigned copy_lines(FILE *src_stream, FILE *dst_stream, unsigned lines_count)
+{
+       while (src_stream && lines_count) {
+               char *line;
+               line = xmalloc_fgets(src_stream);
+               if (line == NULL) {
+                       break;
+               }
+               if (fputs(line, dst_stream) == EOF) {
+                       bb_perror_msg_and_die("error writing to new file");
+               }
+               free(line);
+               lines_count--;
+       }
+       return lines_count;
+}
+
+/* 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
+ * NB: frees 1st argument!
+ */
+static char *extract_filename(char *line, int patch_level, const char *pat)
+{
+       char *temp = NULL, *filename_start_ptr = line + 4;
+
+       if (strncmp(line, pat, 4) == 0) {
+               /* Terminate string at end of source filename */
+               line[strcspn(line, "\t\n\r")] = '\0';
+
+               /* Skip over (patch_level) number of leading directories */
+               while (patch_level--) {
+                       temp = strchr(filename_start_ptr, '/');
+                       if (!temp)
+                               break;
+                       filename_start_ptr = temp + 1;
+               }
+               temp = xstrdup(filename_start_ptr);
+       }
+       free(line);
+       return temp;
+}
+
+int patch_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int patch_main(int argc UNUSED_PARAM, char **argv)
+{
+       struct stat saved_stat;
+       char *patch_line;
+       FILE *patch_file;
+       int patch_level;
+       int ret = 0;
+       char plus = '+';
+
+       xfunc_error_retval = 2;
+       {
+               const char *p = "-1";
+               const char *i = "-"; /* compat */
+               if (getopt32(argv, "p:i:R", &p, &i) & 4)
+                       plus = '-';
+               patch_level = xatoi(p); /* can be negative! */
+               patch_file = xfopen_stdin(i);
+       }
+
+       patch_line = xmalloc_fgetline(patch_file);
+       while (patch_line) {
+               FILE *src_stream;
+               FILE *dst_stream;
+               //char *old_filename;
+               char *new_filename;
+               char *backup_filename;
+               unsigned src_cur_line = 1;
+               unsigned dst_cur_line = 0;
+               unsigned dst_beg_line;
+               unsigned bad_hunk_count = 0;
+               unsigned hunk_count = 0;
+               smallint copy_trailing_lines_flag = 0;
+
+               /* Skip everything upto the "---" marker
+                * No need to parse the lines "Only in <dir>", and "diff <args>"
+                */
+               do {
+                       /* Extract the filename used before the patch was generated */
+                       new_filename = extract_filename(patch_line, patch_level, "--- ");
+                       // was old_filename above
+                       patch_line = xmalloc_fgetline(patch_file);
+                       if (!patch_line) goto quit;
+               } while (!new_filename);
+               free(new_filename); // "source" filename is irrelevant
+
+               new_filename = extract_filename(patch_line, patch_level, "+++ ");
+               if (!new_filename) {
+                       bb_error_msg_and_die("invalid patch");
+               }
+
+               /* Get access rights from the file to be patched */
+               if (stat(new_filename, &saved_stat) != 0) {
+                       char *slash = strrchr(new_filename, '/');
+                       if (slash) {
+                               /* Create leading directories */
+                               *slash = '\0';
+                               bb_make_directory(new_filename, -1, FILEUTILS_RECUR);
+                               *slash = '/';
+                       }
+                       backup_filename = NULL;
+                       src_stream = NULL;
+                       saved_stat.st_mode = 0644;
+               } else {
+                       backup_filename = xasprintf("%s.orig", new_filename);
+                       xrename(new_filename, backup_filename);
+                       src_stream = xfopen_for_read(backup_filename);
+               }
+               dst_stream = xfopen_for_write(new_filename);
+               fchmod(fileno(dst_stream), saved_stat.st_mode);
+
+               printf("patching file %s\n", new_filename);
+
+               /* Handle all hunks for this file */
+               patch_line = xmalloc_fgets(patch_file);
+               while (patch_line) {
+                       unsigned count;
+                       unsigned src_beg_line;
+                       unsigned hunk_offset_start;
+                       unsigned src_last_line = 1;
+                       unsigned dst_last_line = 1;
+
+                       if ((sscanf(patch_line, "@@ -%d,%d +%d,%d", &src_beg_line, &src_last_line, &dst_beg_line, &dst_last_line) < 3)
+                        && (sscanf(patch_line, "@@ -%d +%d,%d", &src_beg_line, &dst_beg_line, &dst_last_line) < 2)
+                       ) {
+                               /* No more hunks for this file */
+                               break;
+                       }
+                       if (plus != '+') {
+                               /* reverse patch */
+                               unsigned tmp = src_last_line;
+                               src_last_line = dst_last_line;
+                               dst_last_line = tmp;
+                               tmp = src_beg_line;
+                               src_beg_line = dst_beg_line;
+                               dst_beg_line = tmp;
+                       }
+                       hunk_count++;
+
+                       if (src_beg_line && dst_beg_line) {
+                               /* Copy unmodified lines upto start of hunk */
+                               /* src_beg_line will be 0 if it's a new file */
+                               count = src_beg_line - src_cur_line;
+                               if (copy_lines(src_stream, dst_stream, count)) {
+                                       bb_error_msg_and_die("bad src file");
+                               }
+                               src_cur_line += count;
+                               dst_cur_line += count;
+                               copy_trailing_lines_flag = 1;
+                       }
+                       src_last_line += hunk_offset_start = src_cur_line;
+                       dst_last_line += dst_cur_line;
+
+                       while (1) {
+                               free(patch_line);
+                               patch_line = xmalloc_fgets(patch_file);
+                               if (patch_line == NULL)
+                                       break; /* EOF */
+                               if ((*patch_line != '-') && (*patch_line != '+')
+                                && (*patch_line != ' ')
+                               ) {
+                                       break; /* End of hunk */
+                               }
+                               if (*patch_line != plus) { /* '-' or ' ' */
+                                       char *src_line = NULL;
+                                       if (src_cur_line == src_last_line)
+                                               break;
+                                       if (src_stream) {
+                                               src_line = xmalloc_fgets(src_stream);
+                                               if (src_line) {
+                                                       int diff = strcmp(src_line, patch_line + 1);
+                                                       src_cur_line++;
+                                                       free(src_line);
+                                                       if (diff)
+                                                               src_line = NULL;
+                                               }
+                                       }
+                                       if (!src_line) {
+                                               bb_error_msg("hunk #%u FAILED at %u", hunk_count, hunk_offset_start);
+                                               bad_hunk_count++;
+                                               break;
+                                       }
+                                       if (*patch_line != ' ') { /* '-' */
+                                               continue;
+                                       }
+                               }
+                               if (dst_cur_line == dst_last_line)
+                                       break;
+                               fputs(patch_line + 1, dst_stream);
+                               dst_cur_line++;
+                       } /* end of while loop handling one hunk */
+               } /* end of while loop handling one file */
+
+               /* Cleanup last patched file */
+               if (copy_trailing_lines_flag) {
+                       copy_lines(src_stream, dst_stream, (unsigned)(-1));
+               }
+               if (src_stream) {
+                       fclose(src_stream);
+               }
+               fclose(dst_stream);
+               if (bad_hunk_count) {
+                       ret = 1;
+                       bb_error_msg("%u out of %u hunk FAILED", bad_hunk_count, hunk_count);
+               } else {
+                       /* It worked, we can remove the backup */
+                       if (backup_filename) {
+                               unlink(backup_filename);
+                       }
+                       if ((dst_cur_line == 0) || (dst_beg_line == 0)) {
+                               /* The new patched file is empty, remove it */
+                               xunlink(new_filename);
+                               // /* old_filename and new_filename may be the same file */
+                               // unlink(old_filename);
+                       }
+               }
+               free(backup_filename);
+               //free(old_filename);
+               free(new_filename);
+       } /* end of "while there are patch lines" */
+ quit:
+       /* 0 = SUCCESS
+        * 1 = Some hunks failed
+        * 2 = More serious problems (exited earlier)
+        */
+       return ret;
+}
diff --git a/editors/sed.c b/editors/sed.c
new file mode 100644 (file)
index 0000000..eb31f7d
--- /dev/null
@@ -0,0 +1,1349 @@
+/* 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 <markw@codepoet.org>
+ * Copyright (C) 2002  Matt Kraai
+ * Copyright (C) 2003 by Glenn McGrath
+ * Copyright (C) 2003,2004 by Rob Landley <rob@landley.net>
+ *
+ * MAINTAINER: Rob Landley <rob@landley.net>
+ *
+ * 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
+  (G.sed_cmd_head/G.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 "libbb.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 */
+       struct sed_cmd_s *next; /* Next command (linked list, NULL terminated) */
+
+       /* 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 *sw_file;          /* File (sw) command writes to, -1 for none. */
+       char *string;           /* Data string for (saicytb) commands. */
+
+       unsigned which_match;   /* (s) Which match to replace (0 for all) */
+
+       /* Bitfields (gcc won't group them if we don't) */
+       unsigned invert:1;      /* the '!' after the address */
+       unsigned in_match:1;    /* Next line also included in match? */
+       unsigned sub_p:1;       /* (s) print option */
+
+       char sw_last_char;      /* Last line written by (sw) had no '\n' */
+
+       /* GENERAL FIELDS */
+       char cmd;               /* The command char: abcdDgGhHilnNpPqrstwxy:={} */
+} sed_cmd_t;
+
+static const char semicolon_whitespace[] ALIGN1 = "; \n\r\t\v";
+
+struct 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;
+};
+#define G (*(struct globals*)&bb_common_bufsiz1)
+void BUG_sed_globals_too_big(void);
+#define INIT_G() do { \
+       if (sizeof(struct globals) > COMMON_BUFSIZE) \
+               BUG_sed_globals_too_big(); \
+       G.sed_cmd_tail = &G.sed_cmd_head; \
+} while (0)
+
+
+#if ENABLE_FEATURE_CLEAN_UP
+static void sed_free_and_close_stuff(void)
+{
+       sed_cmd_t *sed_cmd = G.sed_cmd_head.next;
+
+       llist_free(G.append_head, free);
+
+       while (sed_cmd) {
+               sed_cmd_t *sed_cmd_next = sed_cmd->next;
+
+               if (sed_cmd->sw_file)
+                       xprint_and_close_file(sed_cmd->sw_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;
+       }
+
+       free(G.hold_space);
+
+       while (G.current_input_file < G.input_file_count)
+               fclose(G.input_file_list[G.current_input_file++]);
+}
+#else
+void sed_free_and_close_stuff(void);
+#endif
+
+/* If something bad happens during -i operation, delete temp file */
+
+static void cleanup_outname(void)
+{
+       if (G.outname) unlink(G.outname);
+}
+
+/* strcpy, replacing "\from" with 'to'. If to is NUL, replacing "\any" with 'any' */
+
+static void parse_escapes(char *dest, const 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;
+                       }
+                       *dest++ = string[i++];
+               }
+               /* TODO: is it safe wrt a string with trailing '\\' ? */
+               *dest++ = string[i++];
+       }
+       *dest = '\0';
+}
+
+static char *copy_parsing_escapes(const char *string, int len)
+{
+       char *dest = xmalloc(len + 1);
+
+       parse_escapes(dest, string, len, 'n', '\n');
+       /* GNU sed also recognizes \t */
+       parse_escapes(dest, dest, strlen(dest), 't', '\t');
+       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, const 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(const char *cmdstr, char **match, char **replace)
+{
+       const 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(const char *my_str, int *linenum, regex_t ** regex)
+{
+       const char *pos = my_str;
+
+       if (isdigit(*my_str)) {
+               *linenum = strtol(my_str, (char**)&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, G.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,*/ const 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, const char *substr)
+{
+       int cflags = G.regex_type;
+       char *match;
+       int idx;
+
+       /*
+        * 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. */
+                               const char *pos = substr + idx;
+/* FIXME: error check? */
+                               sed_cmd->which_match = (unsigned)strtol(substr+idx, (char**) &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 = xmalloc(sizeof(regex_t));
+               xregcomp(sed_cmd->sub_match, match, cflags);
+       }
+       free(match);
+
+       return idx;
+}
+
+/*
+ *  Process the commands arguments
+ */
+static const char *parse_cmd_args(sed_cmd_t *sed_cmd, const 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);
+               /* "\anychar" -> "anychar" */
+               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->sw_file = xfopen_for_write(sed_cmd->string);
+                       sed_cmd->sw_last_char = '\n';
+               }
+       /* 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(const char *cmdstr)
+{
+       sed_cmd_t *sed_cmd;
+       int temp;
+
+       /* Append this line to any unfinished line from last time. */
+       if (G.add_cmd_line) {
+               char *tp = xasprintf("%s\n%s", G.add_cmd_line, cmdstr);
+               free(G.add_cmd_line);
+               cmdstr = G.add_cmd_line = tp;
+       }
+
+       /* If this line ends with backslash, request next line. */
+       temp = strlen(cmdstr);
+       if (temp && cmdstr[--temp] == '\\') {
+               if (!G.add_cmd_line)
+                       G.add_cmd_line = xstrdup(cmdstr);
+               G.add_cmd_line[temp] = '\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')
+                               G.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 */
+               G.sed_cmd_tail->next = sed_cmd;
+               G.sed_cmd_tail = G.sed_cmd_tail->next;
+       }
+
+       /* If we glued multiple lines together, free the memory. */
+       free(G.add_cmd_line);
+       G.add_cmd_line = NULL;
+}
+
+/* Append to a string, reallocating memory as necessary. */
+
+#define PIPE_GROW 64
+
+static void pipe_putc(char c)
+{
+       if (G.pipeline.idx == G.pipeline.len) {
+               G.pipeline.buf = xrealloc(G.pipeline.buf,
+                               G.pipeline.len + PIPE_GROW);
+               G.pipeline.len += PIPE_GROW;
+       }
+       G.pipeline.buf[G.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] == '\\') {
+                       unsigned backref = replace[++i] - '0';
+                       if (backref <= 9) {
+                               /* print out the text held in G.regmatch[backref] */
+                               if (G.regmatch[backref].rm_so != -1) {
+                                       j = G.regmatch[backref].rm_so;
+                                       while (j < G.regmatch[backref].rm_eo)
+                                               pipe_putc(line[j++]);
+                               }
+                               continue;
+                       }
+                       /* I _think_ it is impossible to get '\' to be
+                        * the last char in replace string. Thus we dont check
+                        * for replace[i] == NUL. (counterexample anyone?) */
+                       /* if we find a backslash escaped character, print the character */
+                       pipe_putc(replace[i]);
+                       continue;
+               }
+               /* if we find an unescaped '&' print out the whole matched text. */
+               if (replace[i] == '&') {
+                       j = G.regmatch[0].rm_so;
+                       while (j < G.regmatch[0].rm_eo)
+                               pipe_putc(line[j++]);
+                       continue;
+               }
+               /* Otherwise just output the character. */
+               pipe_putc(replace[i]);
+       }
+}
+
+static int do_subst_command(sed_cmd_t *sed_cmd, char **line)
+{
+       char *oldline = *line;
+       int altered = 0;
+       unsigned match_count = 0;
+       regex_t *current_regex;
+
+       /* Handle empty regex. */
+       if (sed_cmd->sub_match == NULL) {
+               current_regex = G.previous_regex_ptr;
+               if (!current_regex)
+                       bb_error_msg_and_die("no previous regexp");
+       } else
+               G.previous_regex_ptr = current_regex = sed_cmd->sub_match;
+
+       /* Find the first match */
+       if (REG_NOMATCH == regexec(current_regex, oldline, 10, G.regmatch, 0))
+               return 0;
+
+       /* Initialize temporary output buffer. */
+       G.pipeline.buf = xmalloc(PIPE_GROW);
+       G.pipeline.len = PIPE_GROW;
+       G.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 (!G.regmatch[0].rm_so && !G.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 < G.regmatch[0].rm_eo; i++)
+                               pipe_putc(*oldline++);
+                       continue;
+               }
+
+               /* print everything before the match */
+               for (i = 0; i < G.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 += G.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, G.regmatch, 0) != REG_NOMATCH));
+
+       /* Copy rest of string into output pipeline */
+
+       while (*oldline)
+               pipe_putc(*oldline++);
+       pipe_putc(0);
+
+       free(*line);
+       *line = G.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 = G.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(&G.append_head, xstrdup(s));
+}
+
+static void flush_append(void)
+{
+       char *data;
+
+       /* Output appended lines. */
+       while ((data = (char *)llist_pop(&G.append_head))) {
+               fprintf(G.nonstdout, "%s\n", data);
+               free(data);
+       }
+}
+
+static void add_input_file(FILE *file)
+{
+       G.input_file_list = xrealloc_vector(G.input_file_list, 2, G.input_file_count);
+       G.input_file_list[G.input_file_count++] = file;
+}
+
+/* Get next line of input from G.input_file_list, flushing append buffer and
+ * noting if we ran out of files without a newline on the last line we read.
+ */
+enum {
+       NO_EOL_CHAR = 1,
+       LAST_IS_NUL = 2,
+};
+static char *get_next_line(char *gets_char)
+{
+       char *temp = NULL;
+       int len;
+       char gc;
+
+       flush_append();
+
+       /* will be returned if last line in the file
+        * doesn't end with either '\n' or '\0' */
+       gc = NO_EOL_CHAR;
+       while (G.current_input_file < G.input_file_count) {
+               FILE *fp = G.input_file_list[G.current_input_file];
+               /* Read line up to a newline or NUL byte, inclusive,
+                * return malloc'ed char[]. length of the chunk read
+                * is stored in len. NULL if EOF/error */
+               temp = bb_get_chunk_from_file(fp, &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';
+                               gc = c;
+                               if (c == '\0') {
+                                       int ch = fgetc(fp);
+                                       if (ch != EOF)
+                                               ungetc(ch, fp);
+                                       else
+                                               gc = LAST_IS_NUL;
+                               }
+                       }
+                       /* else we put NO_EOL_CHAR into *gets_char */
+                       break;
+
+               /* NB: I had the idea of peeking next file(s) and returning
+                * NO_EOL_CHAR only if it is the *last* non-empty
+                * input file. But there is a case where this won't work:
+                * file1: "a woo\nb woo"
+                * file2: "c no\nd no"
+                * sed -ne 's/woo/bang/p' input1 input2 => "a bang\nb bang"
+                * (note: *no* newline after "b bang"!) */
+               }
+               /* Close this file and advance to next one */
+               fclose(fp);
+               G.current_input_file++;
+       }
+       *gets_char = gc;
+       return temp;
+}
+
+/* Output line of text. */
+/* Note:
+ * The tricks with NO_EOL_CHAR 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 void puts_maybe_newline(char *s, FILE *file, char *last_puts_char, char last_gets_char)
+{
+       char lpc = *last_puts_char;
+
+       /* Need to insert a '\n' between two files because first file's
+        * last line wasn't terminated? */
+       if (lpc != '\n' && lpc != '\0') {
+               fputc('\n', file);
+               lpc = '\n';
+       }
+       fputs(s, file);
+
+       /* 'x' - just something which is not '\n', '\0' or NO_EOL_CHAR */
+       if (s[0])
+               lpc = 'x';
+
+       /* had trailing '\0' and it was last char of file? */
+       if (last_gets_char == LAST_IS_NUL) {
+               fputc('\0', file);
+               lpc = 'x'; /* */
+       } else
+       /* had trailing '\n' or '\0'? */
+       if (last_gets_char != NO_EOL_CHAR) {
+               fputc(last_gets_char, file);
+               lpc = last_gets_char;
+       }
+
+       if (ferror(file)) {
+               xfunc_error_retval = 4;  /* It's what gnu sed exits with... */
+               bb_error_msg_and_die(bb_msg_write_error);
+       }
+       *last_puts_char = lpc;
+}
+
+#define sed_puts(s, n) (puts_maybe_newline(s, G.nonstdout, &last_puts_char, n))
+
+static int beg_match(sed_cmd_t *sed_cmd, const char *pattern_space)
+{
+       int retval = sed_cmd->beg_match && !regexec(sed_cmd->beg_match, pattern_space, 0, NULL, 0);
+       if (retval)
+               G.previous_regex_ptr = sed_cmd->beg_match;
+       return retval;
+}
+
+/* Process all the lines in all the files */
+
+static void process_files(void)
+{
+       char *pattern_space, *next_line;
+       int linenum = 0;
+       char last_puts_char = '\n';
+       char last_gets_char, next_gets_char;
+       sed_cmd_t *sed_cmd;
+       int substituted;
+
+       /* Prime the pump */
+       next_line = get_next_line(&next_gets_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_gets_char = next_gets_char;
+
+       /* Read one line in advance so we can act on the last line,
+        * the '$' address */
+       next_line = get_next_line(&next_gets_char);
+       linenum++;
+ restart:
+       /* for every line, go through all the commands */
+       for (sed_cmd = G.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? */
+                       || (beg_match(sed_cmd, pattern_space))
+                       /* 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->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) {
+                               G.previous_regex_ptr = sed_cmd->beg_match;
+                       }
+
+                       /* actual sedding */
+                       switch (sed_cmd->cmd) {
+
+                       /* Print line number */
+                       case '=':
+                               fprintf(G.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';
+                                       /* TODO: explain why '\n' below */
+                                       sed_puts(pattern_space, '\n');
+                                       *tmp = '\n';
+                                       break;
+                               }
+                               /* Fall Through */
+                       }
+
+                       /* Write the current pattern space to output */
+                       case 'p':
+                               /* NB: we print this _before_ the last line
+                                * (of current file) is printed. Even if
+                                * that line is nonterminated, we print
+                                * '\n' here (gnu sed does the same) */
+                               sed_puts(pattern_space, '\n');
+                               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_gets_char);
+                               /* handle w option */
+                               if (sed_cmd->sw_file)
+                                       puts_maybe_newline(
+                                               pattern_space, sed_cmd->sw_file,
+                                               &sed_cmd->sw_last_char, last_gets_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, '\n');
+                               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, NO_EOL_CHAR);
+                               goto discard_line;
+
+                       /* Read file, append contents to output */
+                       case 'r':
+                       {
+                               FILE *rfile;
+
+                               rfile = fopen_for_read(sed_cmd->string);
+                               if (rfile) {
+                                       char *line;
+
+                                       while ((line = xmalloc_fgetline(rfile))
+                                                       != NULL)
+                                               append(line);
+                                       xprint_and_close_file(rfile);
+                               }
+
+                               break;
+                       }
+
+                       /* Write pattern space to file. */
+                       case 'w':
+                               puts_maybe_newline(
+                                       pattern_space, sed_cmd->sw_file,
+                                       &sed_cmd->sw_last_char, last_gets_char);
+                               break;
+
+                       /* Read next line from input */
+                       case 'n':
+                               if (!G.be_quiet)
+                                       sed_puts(pattern_space, last_gets_char);
+                               if (next_line) {
+                                       free(pattern_space);
+                                       pattern_space = next_line;
+                                       last_gets_char = next_gets_char;
+                                       next_line = get_next_line(&next_gets_char);
+                                       substituted = 0;
+                                       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_gets_char = next_gets_char;
+                               next_line = get_next_line(&next_gets_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(G.hold_space ? G.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 (G.hold_space)
+                                       hold_space_size = strlen(G.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 (G.hold_space)
+                                       strcat(pattern_space, G.hold_space);
+                               last_gets_char = '\n';
+
+                               break;
+                       }
+                       case 'h':       /* Replace hold space with pattern space */
+                               free(G.hold_space);
+                               G.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 (G.hold_space)
+                                       hold_space_size += strlen(G.hold_space);
+                               if (pattern_space)
+                                       pattern_space_size = strlen(pattern_space);
+                               G.hold_space = xrealloc(G.hold_space,
+                                               hold_space_size + pattern_space_size);
+
+                               if (hold_space_size == 2)
+                                       *G.hold_space = 0;
+                               strcat(G.hold_space, "\n");
+                               if (pattern_space)
+                                       strcat(G.hold_space, pattern_space);
+
+                               break;
+                       }
+                       case 'x': /* Exchange hold and pattern space */
+                       {
+                               char *tmp = pattern_space;
+                               pattern_space = G.hold_space ? : xzalloc(1);
+                               last_gets_char = '\n';
+                               G.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 (!G.be_quiet)
+               sed_puts(pattern_space, last_gets_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.
+ * However, newline can be escaped: 's/e/z\<newline>z/'
+ * We check for this.
+ */
+
+static void add_cmd_block(char *cmdstr)
+{
+       char *sv, *eol;
+
+       cmdstr = sv = xstrdup(cmdstr);
+       do {
+               eol = strchr(cmdstr, '\n');
+ next:
+               if (eol) {
+                       /* Count preceding slashes */
+                       int slashes = 0;
+                       char *sl = eol;
+
+                       while (sl != cmdstr && *--sl == '\\')
+                               slashes++;
+                       /* Odd number of preceding slashes - newline is escaped */
+                       if (slashes & 1) {
+                               overlapping_strcpy(eol - 1, eol);
+                               eol = strchr(eol, '\n');
+                               goto next;
+                       }
+                       *eol = '\0';
+               }
+               add_cmd(cmdstr);
+               cmdstr = eol + 1;
+       } while (eol);
+       free(sv);
+}
+
+int sed_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int sed_main(int argc UNUSED_PARAM, char **argv)
+{
+       enum {
+               OPT_in_place = 1 << 0,
+       };
+       unsigned opt;
+       llist_t *opt_e, *opt_f;
+       int status = EXIT_SUCCESS;
+
+       INIT_G();
+
+       /* 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 (argv[1] && !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(argv, "irne:f:", &opt_e, &opt_f,
+                           &G.be_quiet); /* counter for -n */
+       //argc -= optind;
+       argv += optind;
+       if (opt & OPT_in_place) { // -i
+               atexit(cleanup_outname);
+       }
+       if (opt & 0x2) G.regex_type |= REG_EXTENDED; // -r
+       //if (opt & 0x4) G.be_quiet++; // -n
+       while (opt_e) { // -e
+               add_cmd_block(llist_pop(&opt_e));
+       }
+       while (opt_f) { // -f
+               char *line;
+               FILE *cmdfile;
+               cmdfile = xfopen_for_read(llist_pop(&opt_f));
+               while ((line = xmalloc_fgetline(cmdfile)) != NULL) {
+                       add_cmd(line);
+                       free(line);
+               }
+               fclose(cmdfile);
+       }
+       /* if we didn't get a pattern from -e or -f, use argv[0] */
+       if (!(opt & 0x18)) {
+               if (!*argv)
+                       bb_show_usage();
+               add_cmd_block(*argv++);
+       }
+       /* Flush any unfinished commands. */
+       add_cmd("");
+
+       /* By default, we write to stdout */
+       G.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; argv[i]; i++) {
+                       struct stat statbuf;
+                       int nonstdoutfd;
+
+                       if (LONE_DASH(argv[i]) && !(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;
+                       }
+
+                       G.outname = xasprintf("%sXXXXXX", argv[i]);
+                       nonstdoutfd = mkstemp(G.outname);
+                       if (-1 == nonstdoutfd)
+                               bb_perror_msg_and_die("cannot create temp file %s", G.outname);
+                       G.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(G.nonstdout);
+
+                       G.nonstdout = stdout;
+                       /* unlink(argv[i]); */
+                       xrename(G.outname, argv[i]);
+                       free(G.outname);
+                       G.outname = NULL;
+               }
+               if (G.input_file_count > G.current_input_file)
+                       process_files();
+       }
+
+       return status;
+}
diff --git a/editors/sed1line.txt b/editors/sed1line.txt
new file mode 100644 (file)
index 0000000..11a2e36
--- /dev/null
@@ -0,0 +1,425 @@
+http://www.student.northpark.edu/pemente/sed/sed1line.txt
+-------------------------------------------------------------------------
+HANDY ONE-LINERS FOR SED (Unix stream editor)               Apr. 26, 2004
+compiled by Eric Pement - pemente[at]northpark[dot]edu        version 5.4
+Latest version of this file is usually at:
+   http://sed.sourceforge.net/sed1line.txt
+   http://www.student.northpark.edu/pemente/sed/sed1line.txt
+This file is also available in Portuguese at:
+   http://www.lrv.ufsc.br/wmaker/sed_ptBR.html
+
+FILE SPACING:
+
+ # double space a file
+ sed G
+
+ # double space a file which already has blank lines in it. Output file
+ # should contain no more than one blank line between lines of text.
+ sed '/^$/d;G'
+
+ # triple space a file
+ sed 'G;G'
+
+ # undo double-spacing (assumes even-numbered lines are always blank)
+ sed 'n;d'
+
+ # insert a blank line above every line which matches "regex"
+ sed '/regex/{x;p;x;}'
+
+ # insert a blank line below every line which matches "regex"
+ sed '/regex/G'
+
+ # insert a blank line above and below every line which matches "regex"
+ sed '/regex/{x;p;x;G;}'
+
+NUMBERING:
+
+ # number each line of a file (simple left alignment). Using a tab (see
+ # note on '\t' at end of file) instead of space will preserve margins.
+ sed = filename | sed 'N;s/\n/\t/'
+
+ # number each line of a file (number on left, right-aligned)
+ sed = filename | sed 'N; s/^/     /; s/ *\(.\{6,\}\)\n/\1  /'
+
+ # number each line of file, but only print numbers if line is not blank
+ sed '/./=' filename | sed '/./N; s/\n/ /'
+
+ # count lines (emulates "wc -l")
+ sed -n '$='
+
+TEXT CONVERSION AND SUBSTITUTION:
+
+ # IN UNIX ENVIRONMENT: convert DOS newlines (CR/LF) to Unix format
+ sed 's/.$//'               # assumes that all lines end with CR/LF
+ sed 's/^M$//'              # in bash/tcsh, press Ctrl-V then Ctrl-M
+ sed 's/\x0D$//'            # gsed 3.02.80, but top script is easier
+
+ # IN UNIX ENVIRONMENT: convert Unix newlines (LF) to DOS format
+ sed "s/$/`echo -e \\\r`/"            # command line under ksh
+ sed 's/$'"/`echo \\\r`/"             # command line under bash
+ sed "s/$/`echo \\\r`/"               # command line under zsh
+ sed 's/$/\r/'                        # gsed 3.02.80
+
+ # IN DOS ENVIRONMENT: convert Unix newlines (LF) to DOS format
+ sed "s/$//"                          # method 1
+ sed -n p                             # method 2
+
+ # IN DOS ENVIRONMENT: convert DOS newlines (CR/LF) to Unix format
+ # Can only be done with UnxUtils sed, version 4.0.7 or higher.
+ # Cannot be done with other DOS versions of sed. Use "tr" instead.
+ sed "s/\r//" infile >outfile         # UnxUtils sed v4.0.7 or higher
+ tr -d \r <infile >outfile            # GNU tr version 1.22 or higher
+
+ # delete leading whitespace (spaces, tabs) from front of each line
+ # aligns all text flush left
+ sed 's/^[ \t]*//'                    # see note on '\t' at end of file
+
+ # delete trailing whitespace (spaces, tabs) from end of each line
+ sed 's/[ \t]*$//'                    # see note on '\t' at end of file
+
+ # delete BOTH leading and trailing whitespace from each line
+ sed 's/^[ \t]*//;s/[ \t]*$//'
+
+ # insert 5 blank spaces at beginning of each line (make page offset)
+ sed 's/^/     /'
+
+ # align all text flush right on a 79-column width
+ sed -e :a -e 's/^.\{1,78\}$/ &/;ta'  # set at 78 plus 1 space
+
+ # center all text in the middle of 79-column width. In method 1,
+ # spaces at the beginning of the line are significant, and trailing
+ # spaces are appended at the end of the line. In method 2, spaces at
+ # the beginning of the line are discarded in centering the line, and
+ # no trailing spaces appear at the end of lines.
+ sed  -e :a -e 's/^.\{1,77\}$/ & /;ta'                     # method 1
+ sed  -e :a -e 's/^.\{1,77\}$/ &/;ta' -e 's/\( *\)\1/\1/'  # method 2
+
+ # substitute (find and replace) "foo" with "bar" on each line
+ sed 's/foo/bar/'             # replaces only 1st instance in a line
+ sed 's/foo/bar/4'            # replaces only 4th instance in a line
+ sed 's/foo/bar/g'            # replaces ALL instances in a line
+ sed 's/\(.*\)foo\(.*foo\)/\1bar\2/' # replace the next-to-last case
+ sed 's/\(.*\)foo/\1bar/'            # replace only the last case
+
+ # substitute "foo" with "bar" ONLY for lines which contain "baz"
+ sed '/baz/s/foo/bar/g'
+
+ # substitute "foo" with "bar" EXCEPT for lines which contain "baz"
+ sed '/baz/!s/foo/bar/g'
+
+ # change "scarlet" or "ruby" or "puce" to "red"
+ sed 's/scarlet/red/g;s/ruby/red/g;s/puce/red/g'   # most seds
+ gsed 's/scarlet\|ruby\|puce/red/g'                # GNU sed only
+
+ # reverse order of lines (emulates "tac")
+ # bug/feature in HHsed v1.5 causes blank lines to be deleted
+ sed '1!G;h;$!d'               # method 1
+ sed -n '1!G;h;$p'             # method 2
+
+ # reverse each character on the line (emulates "rev")
+ sed '/\n/!G;s/\(.\)\(.*\n\)/&\2\1/;//D;s/.//'
+
+ # join pairs of lines side-by-side (like "paste")
+ sed '$!N;s/\n/ /'
+
+ # if a line ends with a backslash, append the next line to it
+ sed -e :a -e '/\\$/N; s/\\\n//; ta'
+
+ # if a line begins with an equal sign, append it to the previous line
+ # and replace the "=" with a single space
+ sed -e :a -e '$!N;s/\n=/ /;ta' -e 'P;D'
+
+ # add commas to numeric strings, changing "1234567" to "1,234,567"
+ gsed ':a;s/\B[0-9]\{3\}\>/,&/;ta'                     # GNU sed
+ sed -e :a -e 's/\(.*[0-9]\)\([0-9]\{3\}\)/\1,\2/;ta'  # other seds
+
+ # add commas to numbers with decimal points and minus signs (GNU sed)
+ gsed ':a;s/\(^\|[^0-9.]\)\([0-9]\+\)\([0-9]\{3\}\)/\1\2,\3/g;ta'
+
+ # add a blank line every 5 lines (after lines 5, 10, 15, 20, etc.)
+ gsed '0~5G'                  # GNU sed only
+ sed 'n;n;n;n;G;'             # other seds
+
+SELECTIVE PRINTING OF CERTAIN LINES:
+
+ # print first 10 lines of file (emulates behavior of "head")
+ sed 10q
+
+ # print first line of file (emulates "head -1")
+ sed q
+
+ # print the last 10 lines of a file (emulates "tail")
+ sed -e :a -e '$q;N;11,$D;ba'
+
+ # print the last 2 lines of a file (emulates "tail -2")
+ sed '$!N;$!D'
+
+ # print the last line of a file (emulates "tail -1")
+ sed '$!d'                    # method 1
+ sed -n '$p'                  # method 2
+
+ # print only lines which match regular expression (emulates "grep")
+ sed -n '/regexp/p'           # method 1
+ sed '/regexp/!d'             # method 2
+
+ # print only lines which do NOT match regexp (emulates "grep -v")
+ sed -n '/regexp/!p'          # method 1, corresponds to above
+ sed '/regexp/d'              # method 2, simpler syntax
+
+ # print the line immediately before a regexp, but not the line
+ # containing the regexp
+ sed -n '/regexp/{g;1!p;};h'
+
+ # print the line immediately after a regexp, but not the line
+ # containing the regexp
+ sed -n '/regexp/{n;p;}'
+
+ # print 1 line of context before and after regexp, with line number
+ # indicating where the regexp occurred (similar to "grep -A1 -B1")
+ sed -n -e '/regexp/{=;x;1!p;g;$!N;p;D;}' -e h
+
+ # grep for AAA and BBB and CCC (in any order)
+ sed '/AAA/!d; /BBB/!d; /CCC/!d'
+
+ # grep for AAA and BBB and CCC (in that order)
+ sed '/AAA.*BBB.*CCC/!d'
+
+ # grep for AAA or BBB or CCC (emulates "egrep")
+ sed -e '/AAA/b' -e '/BBB/b' -e '/CCC/b' -e d    # most seds
+ gsed '/AAA\|BBB\|CCC/!d'                        # GNU sed only
+
+ # print paragraph if it contains AAA (blank lines separate paragraphs)
+ # HHsed v1.5 must insert a 'G;' after 'x;' in the next 3 scripts below
+ sed -e '/./{H;$!d;}' -e 'x;/AAA/!d;'
+
+ # print paragraph if it contains AAA and BBB and CCC (in any order)
+ sed -e '/./{H;$!d;}' -e 'x;/AAA/!d;/BBB/!d;/CCC/!d'
+
+ # print paragraph if it contains AAA or BBB or CCC
+ sed -e '/./{H;$!d;}' -e 'x;/AAA/b' -e '/BBB/b' -e '/CCC/b' -e d
+ gsed '/./{H;$!d;};x;/AAA\|BBB\|CCC/b;d'         # GNU sed only
+
+ # print only lines of 65 characters or longer
+ sed -n '/^.\{65\}/p'
+
+ # print only lines of less than 65 characters
+ sed -n '/^.\{65\}/!p'        # method 1, corresponds to above
+ sed '/^.\{65\}/d'            # method 2, simpler syntax
+
+ # print section of file from regular expression to end of file
+ sed -n '/regexp/,$p'
+
+ # print section of file based on line numbers (lines 8-12, inclusive)
+ sed -n '8,12p'               # method 1
+ sed '8,12!d'                 # method 2
+
+ # print line number 52
+ sed -n '52p'                 # method 1
+ sed '52!d'                   # method 2
+ sed '52q;d'                  # method 3, efficient on large files
+
+ # beginning at line 3, print every 7th line
+ gsed -n '3~7p'               # GNU sed only
+ sed -n '3,${p;n;n;n;n;n;n;}' # other seds
+
+ # print section of file between two regular expressions (inclusive)
+ sed -n '/Iowa/,/Montana/p'             # case sensitive
+
+SELECTIVE DELETION OF CERTAIN LINES:
+
+ # print all of file EXCEPT section between 2 regular expressions
+ sed '/Iowa/,/Montana/d'
+
+ # delete duplicate, consecutive lines from a file (emulates "uniq").
+ # First line in a set of duplicate lines is kept, rest are deleted.
+ sed '$!N; /^\(.*\)\n\1$/!P; D'
+
+ # delete duplicate, nonconsecutive lines from a file. Beware not to
+ # overflow the buffer size of the hold space, or else use GNU sed.
+ sed -n 'G; s/\n/&&/; /^\([ -~]*\n\).*\n\1/d; s/\n//; h; P'
+
+ # delete all lines except duplicate lines (emulates "uniq -d").
+ sed '$!N; s/^\(.*\)\n\1$/\1/; t; D'
+
+ # delete the first 10 lines of a file
+ sed '1,10d'
+
+ # delete the last line of a file
+ sed '$d'
+
+ # delete the last 2 lines of a file
+ sed 'N;$!P;$!D;$d'
+
+ # delete the last 10 lines of a file
+ sed -e :a -e '$d;N;2,10ba' -e 'P;D'   # method 1
+ sed -n -e :a -e '1,10!{P;N;D;};N;ba'  # method 2
+
+ # delete every 8th line
+ gsed '0~8d'                           # GNU sed only
+ sed 'n;n;n;n;n;n;n;d;'                # other seds
+
+ # delete ALL blank lines from a file (same as "grep '.' ")
+ sed '/^$/d'                           # method 1
+ sed '/./!d'                           # method 2
+
+ # delete all CONSECUTIVE blank lines from file except the first; also
+ # deletes all blank lines from top and end of file (emulates "cat -s")
+ sed '/./,/^$/!d'          # method 1, allows 0 blanks at top, 1 at EOF
+ sed '/^$/N;/\n$/D'        # method 2, allows 1 blank at top, 0 at EOF
+
+ # delete all CONSECUTIVE blank lines from file except the first 2:
+ sed '/^$/N;/\n$/N;//D'
+
+ # delete all leading blank lines at top of file
+ sed '/./,$!d'
+
+ # delete all trailing blank lines at end of file
+ sed -e :a -e '/^\n*$/{$d;N;ba' -e '}'  # works on all seds
+ sed -e :a -e '/^\n*$/N;/\n$/ba'        # ditto, except for gsed 3.02*
+
+ # delete the last line of each paragraph
+ sed -n '/^$/{p;h;};/./{x;/./p;}'
+
+SPECIAL APPLICATIONS:
+
+ # remove nroff overstrikes (char, backspace) from man pages. The 'echo'
+ # command may need an -e switch if you use Unix System V or bash shell.
+ sed "s/.`echo \\\b`//g"    # double quotes required for Unix environment
+ sed 's/.^H//g'             # in bash/tcsh, press Ctrl-V and then Ctrl-H
+ sed 's/.\x08//g'           # hex expression for sed v1.5
+
+ # get Usenet/e-mail message header
+ sed '/^$/q'                # deletes everything after first blank line
+
+ # get Usenet/e-mail message body
+ sed '1,/^$/d'              # deletes everything up to first blank line
+
+ # get Subject header, but remove initial "Subject: " portion
+ sed '/^Subject: */!d; s///;q'
+
+ # get return address header
+ sed '/^Reply-To:/q; /^From:/h; /./d;g;q'
+
+ # parse out the address proper. Pulls out the e-mail address by itself
+ # from the 1-line return address header (see preceding script)
+ sed 's/ *(.*)//; s/>.*//; s/.*[:<] *//'
+
+ # add a leading angle bracket and space to each line (quote a message)
+ sed 's/^/> /'
+
+ # delete leading angle bracket & space from each line (unquote a message)
+ sed 's/^> //'
+
+ # remove most HTML tags (accommodates multiple-line tags)
+ sed -e :a -e 's/<[^>]*>//g;/</N;//ba'
+
+ # extract multi-part uuencoded binaries, removing extraneous header
+ # info, so that only the uuencoded portion remains. Files passed to
+ # sed must be passed in the proper order. Version 1 can be entered
+ # from the command line; version 2 can be made into an executable
+ # Unix shell script. (Modified from a script by Rahul Dhesi.)
+ sed '/^end/,/^begin/d' file1 file2 ... fileX | uudecode   # vers. 1
+ sed '/^end/,/^begin/d' "$@" | uudecode                    # vers. 2
+
+ # zip up each .TXT file individually, deleting the source file and
+ # setting the name of each .ZIP file to the basename of the .TXT file
+ # (under DOS: the "dir /b" switch returns bare filenames in all caps).
+ echo @echo off >zipup.bat
+ dir /b *.txt | sed "s/^\(.*\)\.TXT/pkzip -mo \1 \1.TXT/" >>zipup.bat
+
+TYPICAL USE: Sed takes one or more editing commands and applies all of
+them, in sequence, to each line of input. After all the commands have
+been applied to the first input line, that line is output and a second
+input line is taken for processing, and the cycle repeats. The
+preceding examples assume that input comes from the standard input
+device (i.e, the console, normally this will be piped input). One or
+more filenames can be appended to the command line if the input does
+not come from stdin. Output is sent to stdout (the screen). Thus:
+
+ cat filename | sed '10q'        # uses piped input
+ sed '10q' filename              # same effect, avoids a useless "cat"
+ sed '10q' filename > newfile    # redirects output to disk
+
+For additional syntax instructions, including the way to apply editing
+commands from a disk file instead of the command line, consult "sed &
+awk, 2nd Edition," by Dale Dougherty and Arnold Robbins (O'Reilly,
+1997; http://www.ora.com), "UNIX Text Processing," by Dale Dougherty
+and Tim O'Reilly (Hayden Books, 1987) or the tutorials by Mike Arst
+distributed in U-SEDIT2.ZIP (many sites). To fully exploit the power
+of sed, one must understand "regular expressions." For this, see
+"Mastering Regular Expressions" by Jeffrey Friedl (O'Reilly, 1997).
+The manual ("man") pages on Unix systems may be helpful (try "man
+sed", "man regexp", or the subsection on regular expressions in "man
+ed"), but man pages are notoriously difficult. They are not written to
+teach sed use or regexps to first-time users, but as a reference text
+for those already acquainted with these tools.
+
+QUOTING SYNTAX: The preceding examples use single quotes ('...')
+instead of double quotes ("...") to enclose editing commands, since
+sed is typically used on a Unix platform. Single quotes prevent the
+Unix shell from intrepreting the dollar sign ($) and backquotes
+(`...`), which are expanded by the shell if they are enclosed in
+double quotes. Users of the "csh" shell and derivatives will also need
+to quote the exclamation mark (!) with the backslash (i.e., \!) to
+properly run the examples listed above, even within single quotes.
+Versions of sed written for DOS invariably require double quotes
+("...") instead of single quotes to enclose editing commands.
+
+USE OF '\t' IN SED SCRIPTS: For clarity in documentation, we have used
+the expression '\t' to indicate a tab character (0x09) in the scripts.
+However, most versions of sed do not recognize the '\t' abbreviation,
+so when typing these scripts from the command line, you should press
+the TAB key instead. '\t' is supported as a regular expression
+metacharacter in awk, perl, and HHsed, sedmod, and GNU sed v3.02.80.
+
+VERSIONS OF SED: Versions of sed do differ, and some slight syntax
+variation is to be expected. In particular, most do not support the
+use of labels (:name) or branch instructions (b,t) within editing
+commands, except at the end of those commands. We have used the syntax
+which will be portable to most users of sed, even though the popular
+GNU versions of sed allow a more succinct syntax. When the reader sees
+a fairly long command such as this:
+
+   sed -e '/AAA/b' -e '/BBB/b' -e '/CCC/b' -e d
+
+it is heartening to know that GNU sed will let you reduce it to:
+
+   sed '/AAA/b;/BBB/b;/CCC/b;d'      # or even
+   sed '/AAA\|BBB\|CCC/b;d'
+
+In addition, remember that while many versions of sed accept a command
+like "/one/ s/RE1/RE2/", some do NOT allow "/one/! s/RE1/RE2/", which
+contains space before the 's'. Omit the space when typing the command.
+
+OPTIMIZING FOR SPEED: If execution speed needs to be increased (due to
+large input files or slow processors or hard disks), substitution will
+be executed more quickly if the "find" expression is specified before
+giving the "s/.../.../" instruction. Thus:
+
+   sed 's/foo/bar/g' filename         # standard replace command
+   sed '/foo/ s/foo/bar/g' filename   # executes more quickly
+   sed '/foo/ s//bar/g' filename      # shorthand sed syntax
+
+On line selection or deletion in which you only need to output lines
+from the first part of the file, a "quit" command (q) in the script
+will drastically reduce processing time for large files. Thus:
+
+   sed -n '45,50p' filename           # print line nos. 45-50 of a file
+   sed -n '51q;45,50p' filename       # same, but executes much faster
+
+If you have any additional scripts to contribute or if you find errors
+in this document, please send e-mail to the compiler. Indicate the
+version of sed you used, the operating system it was compiled for, and
+the nature of the problem. Various scripts in this file were written
+or contributed by:
+
+ Al Aab <af137@freenet.toronto.on.ca>   # "seders" list moderator
+ Edgar Allen <era@sky.net>              # various
+ Yiorgos Adamopoulos <adamo@softlab.ece.ntua.gr>
+ Dale Dougherty <dale@songline.com>     # author of "sed & awk"
+ Carlos Duarte <cdua@algos.inesc.pt>    # author of "do it with sed"
+ Eric Pement <pemente@northpark.edu>    # author of this document
+ Ken Pizzini <ken@halcyon.com>          # author of GNU sed v3.02
+ S.G. Ravenhall <stew.ravenhall@totalise.co.uk> # great de-html script
+ Greg Ubben <gsu@romulus.ncsc.mil>      # many contributions & much help
+-------------------------------------------------------------------------
diff --git a/editors/sed_summary.htm b/editors/sed_summary.htm
new file mode 100644 (file)
index 0000000..34e72b0
--- /dev/null
@@ -0,0 +1,223 @@
+<html>
+
+<head><title>Command Summary for sed (sed & awk, Second Edition)</title>
+</head>
+
+<body>
+
+<h2>Command Summary for sed</h2>
+
+<dl>
+
+<dt><b>: </b> <b> :</b><em>label</em></dt>
+<dd>Label a line in the script for the transfer of control by
+<b>b</b> or <b>t</b>.
+<em>label</em> may contain up to seven characters.
+(The POSIX standard says that an implementation can allow longer
+labels if it wishes to. GNU sed allows labels to be of any length.)
+</p></dd>
+
+
+<dt><b>=</b> [<em>address</em>]<b>=</b></dt>
+<dd>Write to standard output the line number of addressed line.</p></dd>
+
+
+<dt><b>a</b> [<em>address</em>]<b>a\</b></dt>
+<dd><em>text</em></p>
+
+<p>Append <em>text</em>
+following each line matched by <em>address</em>.  If
+<em>text</em> goes over more than one line, newlines
+must be "hidden" by preceding them with a backslash.  The
+<em>text</em> will be terminated by the first
+newline that is not hidden in this way.  The
+<em>text</em> is not available in the pattern space
+and subsequent commands cannot be applied to it.  The results of this
+command are sent to standard output when the list of editing commands
+is finished, regardless of what happens to the current line in the
+pattern space.</p></dd>
+
+
+<dt><b>b</b> [<em>address1</em>[,<em>address2</em>]]<b>b</b>[<em>label</em>]</dt>
+<dd>Transfer control unconditionally (branch) to
+<b>:</b><em>label</em> elsewhere in
+script.  That is, the command following the
+<em>label</em> is the next command applied to the
+current line.  If no <em>label</em> is specified,
+control falls through to the end of the script, so no more commands
+are applied to the current line.</p></dd>
+
+
+<dt><b>c</b> [<em>address1</em>[,<em>address2</em>]]<b>c\</b></dt>
+<dd><em>text</em></p>
+
+<p>Replace (change) the lines selected by the address with
+<em>text</em>.  When a range of lines is specified,
+all lines as a group are replaced by a single copy of
+<em>text</em>.  The newline following each line of
+<em>text</em> must be escaped by a backslash, except
+the last line.  The contents of the pattern space are, in effect,
+deleted and no subsequent editing commands can be applied to it (or to
+<em>text</em>).</p></dd>
+
+
+<dt><b>d</b> [<em>address1</em>[,<em>address2</em>]]<b>d</b></dt>
+<dd>Delete line(s) from pattern space.  Thus, the line is not passed to standard
+output. A new line of input is read and editing resumes with first
+command in script.</p></dd>
+
+
+<dt><b>D</b> [<em>address1</em>[,<em>address2</em>]]<b>D</b></dt>
+<dd>Delete first part (up to embedded newline) of multiline pattern space created
+by <b>N</b> command and resume editing with first command in
+script.  If this command empties the pattern space, then a new line
+of input is read, as if the <b>d</b> command had been executed.</p></dd>
+
+
+<dt><b>g</b> [<em>address1</em>[,<em>address2</em>]]<b>g</b></dt>
+<dd>Copy (get) contents of hold space (see <b>h</b> or
+<b>H</b> command) into the pattern space, wiping out
+previous contents.</p></dd>
+
+
+<dt><b>G</b> [<em>address1</em>[,<em>address2</em>]]<b>G</b></dt>
+<dd>Append newline followed by contents of hold space (see
+<b>h</b> or <b>H</b> command) to contents of
+the pattern space.  If hold space is empty, a newline is still
+appended to the pattern space.</p></dd>
+
+
+<dt><b>h</b> [<em>address1</em>[,<em>address2</em>]]<b>h</b></dt>
+<dd>Copy pattern space into hold space, a special temporary buffer.
+Previous contents of hold space are wiped out.</p></dd>
+
+
+<dt><b>H</b> [<em>address1</em>[,<em>address2</em>]]<b>H</b></dt>
+<dd>Append newline and contents of pattern space to contents of the hold
+space.  Even if hold space is empty, this command still appends the
+newline first.</p></dd>
+
+
+<dt><b>i</b> [<em>address1</em>]<b>i\</b></dt>
+<dd><em>text</em></p>
+
+<p>Insert <em>text</em> before each line matched by
+<em>address</em>. (See <b>a</b> for
+details on <em>text</em>.)</p></dd>
+
+
+<dt><b>l</b> [<em>address1</em>[,<em>address2</em>]]<b>l</b></dt>
+<dd>List the contents of the pattern space, showing nonprinting characters
+as ASCII codes.  Long lines are wrapped.</p></dd>
+
+
+<dt><b>n</b> [<em>address1</em>[,<em>address2</em>]]<b>n</b></dt>
+<dd>Read next line of input into pattern space.  Current line is sent to
+standard output.  New line becomes current line and increments line
+counter.  Control passes to command following <b>n</b>
+instead of resuming at the top of the script.</p></dd>
+
+
+<dt><b>N</b> [<em>address1</em>[,<em>address2</em>]]<b>N</b></dt>
+<dd>Append next input line to contents of pattern space; the new line is
+separated from the previous contents of the pattern space by a newline.
+(This command is designed to allow pattern matches across two
+lines.  Using \n to match the embedded newline, you can match
+patterns across multiple lines.)</p></dd>
+
+
+<dt><b>p</b> [<em>address1</em>[,<em>address2</em>]]<b>p</b></dt>
+<dd>Print the addressed line(s).  Note that this can result in duplicate
+output unless default output is suppressed by using "#n" or
+the <span class="option">-n</span>
+
+command-line option.  Typically used before commands that change flow
+control (<b>d</b>, <b>n</b>,
+<b>b</b>) and might prevent the current line from being
+output.</p></dd>
+
+
+<dt><b>P</b> [<em>address1</em>[,<em>address2</em>]]<b>P</b></dt>
+<dd>Print first part (up to embedded newline) of multiline pattern space
+created by <b>N</b> command.  Same as <b>p</b>
+if <b>N</b> has not been applied to a line.</p></dd>
+
+
+<dt><b>q</b> [<em>address</em>]<b>q</b></dt>
+<dd>Quit when <em>address</em> is encountered.  The
+addressed line is first written to output (if default output is not
+suppressed), along with any text appended to it by previous
+<b>a</b> or <b>r</b> commands.</p></dd>
+
+
+<dt><b>r</b> [<em>address</em>]<b>r</b> <em>file</em></dt>
+<dd>Read contents of <em>file</em> and append after the
+contents of the pattern space.  Exactly one space must be put between
+<b>r</b> and the filename.</p></dd>
+
+
+<dt><b>s</b> [<em>address1</em>[,<em>address2</em>]]<b>s</b>/<em>pattern</em>/<em>replacement</em>/[<em>flags</em>]</dt>
+<dd>Substitute <em>replacement</em> for
+<em>pattern</em> on each addressed line.  If pattern
+addresses are used, the pattern <b>//</b> represents the
+last pattern address specified.  The following flags can be specified:</p>
+
+       <dl>
+
+       <dt><b>n</b></dt>
+       <dd>Replace <em>n</em>th instance of
+       /<em>pattern</em>/ on each addressed line.
+       <em>n</em> is any number in the range 1 to 512, and
+       the default is 1.</p></dd>
+
+       <dt><b>g</b></dt>
+       <dd>Replace all instances of /<em>pattern</em>/ on each
+       addressed line, not just the first instance.</p></dd>
+
+       <dt><b>I</b></dt>
+       <dd>Matching is case-insensitive.<p></p></dd>
+
+       <dt><b>p</b></dt>
+       <dd>Print the line if a successful substitution is done.  If several
+       successful substitutions are done, multiple copies of the line will be
+       printed.</p></dd>
+
+       <dt><b>w</b> <em>file</em></dt>
+       <dd>Write the line to <em>file</em> if a replacement
+       was done.  A maximum of 10 different <em>files</em> can be opened.</p></dd>
+
+       </dl>
+
+</dd>
+
+
+<dt><b>t</b> [<em>address1</em>[,<em>address2</em>]]<b>t </b>[<em>label</em>]</dt>
+<dd>Test if successful substitutions have been made on addressed lines,
+and if so, branch to line marked by :<em>label</em>.
+(See <b>b</b> and <b>:</b>.)  If label is not
+specified, control falls through to bottom of script.</p></dd>
+
+
+<dt><b>w</b> [<em>address1</em>[,<em>address2</em>]]<b>w</b> <em>file</em></dt>
+<dd>Append contents of pattern space to <em>file</em>.
+This action occurs when the command is encountered rather than when
+the pattern space is output.  Exactly one space must separate the
+<b>w</b> and the filename.  A maximum of 10 different
+files can be opened in a script.  This command will create the file if
+it does not exist; if the file exists, its contents will be
+overwritten each time the script is executed.  Multiple write commands
+that direct output to the same file append to the end of the file.</p></dd>
+
+
+<dt><b>x</b> [<em>address1</em>[,<em>address2</em>]]<b>x</b></dt>
+<dd>Exchange contents of the pattern space with the contents of the hold
+space.</p></dd>
+
+
+<dt><b>y</b> [<em>address1</em>[,<em>address2</em>]]<b>y</b>/<em>abc</em>/<em>xyz</em>/</dt>
+<dd>Transform each character by position in string
+<em>abc</em> to its equivalent in string
+<em>xyz</em>.</p></dd>
+
+
+</dl>
diff --git a/editors/vi.c b/editors/vi.c
new file mode 100644 (file)
index 0000000..0497bc2
--- /dev/null
@@ -0,0 +1,3983 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * tiny vi.c: A small 'vi' clone
+ * Copyright (C) 2000, 2001 Sterling Huxley <sterling@europa.com>
+ *
+ * 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 "libbb.h"
+
+/* the CRASHME code is unmaintained, and doesn't currently build */
+#define ENABLE_FEATURE_VI_CRASHME 0
+
+
+#if ENABLE_LOCALE_SUPPORT
+
+#if ENABLE_FEATURE_VI_8BIT
+#define Isprint(c) isprint(c)
+#else
+#define Isprint(c) (isprint(c) && (unsigned char)(c) < 0x7f)
+#endif
+
+#else
+
+/* 0x9b is Meta-ESC */
+#if ENABLE_FEATURE_VI_8BIT
+#define Isprint(c) ((unsigned char)(c) >= ' ' && (c) != 0x7f && (unsigned char)(c) != 0x9b)
+#else
+#define Isprint(c) ((unsigned char)(c) >= ' ' && (unsigned char)(c) < 0x7f)
+#endif
+
+#endif
+
+
+enum {
+       MAX_TABSTOP = 32, // sanity limit
+       // User input len. Need not be extra big.
+       // Lines in file being edited *can* be bigger than this.
+       MAX_INPUT_LEN = 128,
+       // Sanity limits. We have only one buffer of this size.
+       MAX_SCR_COLS = CONFIG_FEATURE_VI_MAX_LEN,
+       MAX_SCR_ROWS = CONFIG_FEATURE_VI_MAX_LEN,
+};
+
+/* vt102 typical ESC sequence */
+/* terminal standout start/normal ESC sequence */
+static const char SOs[] ALIGN1 = "\033[7m";
+static const char SOn[] ALIGN1 = "\033[0m";
+/* terminal bell sequence */
+static const char bell[] ALIGN1 = "\007";
+/* Clear-end-of-line and Clear-end-of-screen ESC sequence */
+static const char Ceol[] ALIGN1 = "\033[0K";
+static const char Ceos[] ALIGN1 = "\033[0J";
+/* Cursor motion arbitrary destination ESC sequence */
+static const char CMrc[] ALIGN1 = "\033[%d;%dH";
+/* Cursor motion up and down ESC sequence */
+static const char CMup[] ALIGN1 = "\033[A";
+static const char CMdown[] ALIGN1 = "\n";
+
+#if ENABLE_FEATURE_VI_DOT_CMD || ENABLE_FEATURE_VI_YANKMARK
+// cmds modifying text[]
+// vda: removed "aAiIs" as they switch us into insert mode
+// and remembering input for replay after them makes no sense
+static const char modifying_cmds[] = "cCdDJoOpPrRxX<>~";
+#endif
+
+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"
+};
+
+
+/* vi.c expects chars to be unsigned. */
+/* busybox build system provides that, but it's better */
+/* to audit and fix the source */
+
+struct globals {
+       /* many references - keep near the top of globals */
+       char *text, *end;       // pointers to the user data in memory
+       char *dot;              // where all the action takes place
+       int text_size;          // size of the allocated buffer
+
+       /* the rest */
+       smallint 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)
+
+#if ENABLE_FEATURE_VI_READONLY
+       smallint readonly_mode;
+#define SET_READONLY_FILE(flags)        ((flags) |= 0x01)
+#define SET_READONLY_MODE(flags)        ((flags) |= 0x02)
+#define UNSET_READONLY_FILE(flags)      ((flags) &= 0xfe)
+#else
+#define SET_READONLY_FILE(flags)        ((void)0)
+#define SET_READONLY_MODE(flags)        ((void)0)
+#define UNSET_READONLY_FILE(flags)      ((void)0)
+#endif
+
+       smallint editing;        // >0 while we are editing a file
+                                // [code audit says "can be 0, 1 or 2 only"]
+       smallint cmd_mode;       // 0=command  1=insert 2=replace
+       int file_modified;       // buffer contents changed (counter, not flag!)
+       int last_file_modified;  // = -1;
+       int fn_start;            // index of first cmd line file name
+       int save_argc;           // how many file names on cmd line
+       int cmdcnt;              // repetition count
+       unsigned rows, columns;  // the terminal screen is this size
+       int crow, ccol;          // cursor is on Crow x Ccol
+       int offset;              // chars scrolled off the screen to the left
+       int have_status_msg;     // is default edit status needed?
+                                // [don't make smallint!]
+       int last_status_cksum;   // hash of current status line
+       char *current_filename;
+       char *screenbegin;       // index into text[], of top line on the screen
+       char *screen;            // pointer to the virtual screen buffer
+       int screensize;          //            and its size
+       int tabstop;
+       int last_forward_char;   // last char searched for with 'f' (int because of Unicode)
+       char erase_char;         // the users erase character
+       char last_input_char;    // last char read from user
+
+       smalluint chars_to_parse;
+#if ENABLE_FEATURE_VI_DOT_CMD
+       smallint adding2q;       // are we currently adding user input to q
+       int lmc_len;             // length of last_modifying_cmd
+       char *ioq, *ioq_start;   // pointer to string for get_one_char to "read"
+#endif
+#if ENABLE_FEATURE_VI_OPTIMIZE_CURSOR
+       int last_row;            // where the cursor was last moved to
+#endif
+#if ENABLE_FEATURE_VI_USE_SIGNALS || ENABLE_FEATURE_VI_CRASHME
+       int my_pid;
+#endif
+#if ENABLE_FEATURE_VI_SEARCH
+       char *last_search_pattern; // last pattern from a '/' or '?' search
+#endif
+
+       /* former statics */
+#if ENABLE_FEATURE_VI_YANKMARK
+       char *edit_file__cur_line;
+#endif
+       int refresh__old_offset;
+       int format_edit_status__tot;
+
+       /* a few references only */
+#if ENABLE_FEATURE_VI_YANKMARK
+       int YDreg, Ureg;        // default delete register and orig line for "U"
+       char *reg[28];          // named register a-z, "D", and "U" 0-25,26,27
+       char *mark[28];         // user marks points somewhere in text[]-  a-z and previous context ''
+       char *context_start, *context_end;
+#endif
+#if ENABLE_FEATURE_VI_USE_SIGNALS
+       sigjmp_buf restart;     // catch_sig()
+#endif
+       struct termios term_orig, term_vi; // remember what the cooked mode was
+#if ENABLE_FEATURE_VI_COLON
+       char *initial_cmds[3];  // currently 2 entries, NULL terminated
+#endif
+       // Should be just enough to hold a key sequence,
+       // but CRASHME mode uses it as generated command buffer too
+#if ENABLE_FEATURE_VI_CRASHME
+       char readbuffer[128];
+#else
+       char readbuffer[KEYCODE_BUFFER_SIZE];
+#endif
+#define STATUS_BUFFER_LEN  200
+       char status_buffer[STATUS_BUFFER_LEN]; // messages to the user
+#if ENABLE_FEATURE_VI_DOT_CMD
+       char last_modifying_cmd[MAX_INPUT_LEN]; // last modifying cmd for "."
+#endif
+       char get_input_line__buf[MAX_INPUT_LEN]; /* former static */
+
+       char scr_out_buf[MAX_SCR_COLS + MAX_TABSTOP * 2];
+};
+#define G (*ptr_to_globals)
+#define text           (G.text          )
+#define text_size      (G.text_size     )
+#define end            (G.end           )
+#define dot            (G.dot           )
+#define reg            (G.reg           )
+
+#define vi_setops               (G.vi_setops          )
+#define editing                 (G.editing            )
+#define cmd_mode                (G.cmd_mode           )
+#define file_modified           (G.file_modified      )
+#define last_file_modified      (G.last_file_modified )
+#define fn_start                (G.fn_start           )
+#define save_argc               (G.save_argc          )
+#define cmdcnt                  (G.cmdcnt             )
+#define rows                    (G.rows               )
+#define columns                 (G.columns            )
+#define crow                    (G.crow               )
+#define ccol                    (G.ccol               )
+#define offset                  (G.offset             )
+#define status_buffer           (G.status_buffer      )
+#define have_status_msg         (G.have_status_msg    )
+#define last_status_cksum       (G.last_status_cksum  )
+#define current_filename        (G.current_filename   )
+#define screen                  (G.screen             )
+#define screensize              (G.screensize         )
+#define screenbegin             (G.screenbegin        )
+#define tabstop                 (G.tabstop            )
+#define last_forward_char       (G.last_forward_char  )
+#define erase_char              (G.erase_char         )
+#define last_input_char         (G.last_input_char    )
+#define chars_to_parse          (G.chars_to_parse     )
+#if ENABLE_FEATURE_VI_READONLY
+#define readonly_mode           (G.readonly_mode      )
+#else
+#define readonly_mode           0
+#endif
+#define adding2q                (G.adding2q           )
+#define lmc_len                 (G.lmc_len            )
+#define ioq                     (G.ioq                )
+#define ioq_start               (G.ioq_start          )
+#define last_row                (G.last_row           )
+#define my_pid                  (G.my_pid             )
+#define last_search_pattern     (G.last_search_pattern)
+
+#define edit_file__cur_line     (G.edit_file__cur_line)
+#define refresh__old_offset     (G.refresh__old_offset)
+#define format_edit_status__tot (G.format_edit_status__tot)
+
+#define YDreg          (G.YDreg         )
+#define Ureg           (G.Ureg          )
+#define mark           (G.mark          )
+#define context_start  (G.context_start )
+#define context_end    (G.context_end   )
+#define restart        (G.restart       )
+#define term_orig      (G.term_orig     )
+#define term_vi        (G.term_vi       )
+#define initial_cmds   (G.initial_cmds  )
+#define readbuffer     (G.readbuffer    )
+#define scr_out_buf    (G.scr_out_buf   )
+#define last_modifying_cmd  (G.last_modifying_cmd )
+#define get_input_line__buf (G.get_input_line__buf)
+
+#define INIT_G() do { \
+       SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
+       last_file_modified = -1; \
+       /* "" but has space for 2 chars: */ \
+       USE_FEATURE_VI_SEARCH(last_search_pattern = xzalloc(2);) \
+} while (0)
+
+
+static int init_text_buffer(char *); // init from file or create new
+static void edit_file(char *); // edit one file
+static void do_cmd(int);       // execute a command
+static int next_tabstop(int);
+static void sync_cursor(char *, int *, int *); // synchronize the screen cursor to dot
+static char *begin_line(char *);       // return pointer to cur line B-o-l
+static char *end_line(char *); // return pointer to cur line E-o-l
+static char *prev_line(char *);        // return pointer to prev line B-o-l
+static char *next_line(char *);        // return pointer to next line B-o-l
+static char *end_screen(void); // get pointer to last char on screen
+static int count_lines(char *, char *);        // count line from start to stop
+static char *find_line(int);   // find begining of line #li
+static char *move_to_col(char *, int); // move "p" to column l
+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 char *bound_dot(char *);        // make sure  text[0] <= P < "end"
+static char *new_screen(int, int);     // malloc virtual screen memory
+static char *char_insert(char *, char);        // insert the char c at 'p'
+// might reallocate text[]! use p += stupid_insert(p, ...),
+// and be careful to not use pointers into potentially freed text[]!
+static uintptr_t stupid_insert(char *, char);  // stupidly insert the char c at 'p'
+static int find_range(char **, char **, char); // return pointers for an object
+static int st_test(char *, int, int, char *);  // helper for skip_thing()
+static char *skip_thing(char *, int, int, int);        // skip some object
+static char *find_pair(char *, char);  // find matching pair ()  []  {}
+static char *text_hole_delete(char *, char *); // at "p", delete a 'size' byte hole
+// might reallocate text[]! use p += text_hole_make(p, ...),
+// and be careful to not use pointers into potentially freed text[]!
+static uintptr_t text_hole_make(char *, int);  // at "p", make a 'size' byte hole
+static char *yank_delete(char *, char *, 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
+// sleep for 'h' 1/100 seconds, return 1/0 if stdin is (ready for read)/(not ready)
+static int mysleep(int);
+static int readit(void);       // read (maybe cursor) key from stdin
+static int get_one_char(void); // read 1 char from stdin
+static int file_size(const char *);   // what is the byte size of "fn"
+#if !ENABLE_FEATURE_VI_READONLY
+#define file_insert(fn, p, update_ro_status) file_insert(fn, p)
+#endif
+// file_insert might reallocate text[]!
+static int file_insert(const char *, char *, int);
+static int file_write(char *, char *, char *);
+#if !ENABLE_FEATURE_VI_OPTIMIZE_CURSOR
+#define place_cursor(a, b, optimize) place_cursor(a, b)
+#endif
+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 go_bottom_and_clear_to_eol(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 status_line(const char *, ...);     // print to status buf
+static void status_line_bold(const char *, ...);
+static void not_implemented(const char *); // display "Not implemented" message
+static int format_edit_status(void);   // format file status on status line
+static void redraw(int);       // force a full screen refresh
+static char* format_line(char* /*, 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);
+
+#if ENABLE_FEATURE_VI_SEARCH
+static char *char_search(char *, const char *, int, int);      // search for pattern starting at p
+static int mycmp(const char *, const char *, int);     // string cmp based in "ignorecase"
+#endif
+#if ENABLE_FEATURE_VI_COLON
+static char *get_one_address(char *, int *);   // get colon addr, if present
+static char *get_address(char *, int *, int *);        // get two colon addrs, if present
+static void colon(char *);     // execute the "colon" mode cmds
+#endif
+#if ENABLE_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
+#if ENABLE_FEATURE_VI_DOT_CMD
+static void start_new_cmd_q(char);     // new queue for command
+static void end_cmd_q(void);   // stop saving input chars
+#else
+#define end_cmd_q() ((void)0)
+#endif
+#if ENABLE_FEATURE_VI_SETOPTS
+static void showmatching(char *);      // show the matching pair ()  []  {}
+#endif
+#if ENABLE_FEATURE_VI_YANKMARK || (ENABLE_FEATURE_VI_COLON && ENABLE_FEATURE_VI_SEARCH) || ENABLE_FEATURE_VI_CRASHME
+// might reallocate text[]! use p += string_insert(p, ...),
+// and be careful to not use pointers into potentially freed text[]!
+static uintptr_t string_insert(char *, const char *);  // insert the string at 'p'
+#endif
+#if ENABLE_FEATURE_VI_YANKMARK
+static char *text_yank(char *, char *, int);   // save copy of "p" into a register
+static char what_reg(void);            // what is letter of current YDreg
+static void check_context(char);       // remember context for '' command
+#endif
+#if ENABLE_FEATURE_VI_CRASHME
+static void crash_dummy();
+static void crash_test();
+static int crashme = 0;
+#endif
+
+
+static void write1(const char *out)
+{
+       fputs(out, stdout);
+}
+
+int vi_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int vi_main(int argc, char **argv)
+{
+       int c;
+
+       INIT_G();
+
+#if ENABLE_FEATURE_VI_USE_SIGNALS || ENABLE_FEATURE_VI_CRASHME
+       my_pid = getpid();
+#endif
+#if ENABLE_FEATURE_VI_CRASHME
+       srand((long) my_pid);
+#endif
+#ifdef NO_SUCH_APPLET_YET
+       /* If we aren't "vi", we are "view" */
+       if (ENABLE_FEATURE_VI_READONLY && applet_name[2]) {
+               SET_READONLY_MODE(readonly_mode);
+       }
+#endif
+
+       vi_setops = VI_AUTOINDENT | VI_SHOWMATCH | VI_IGNORECASE;
+       //  1-  process $HOME/.exrc file (not inplemented yet)
+       //  2-  process EXINIT variable from environment
+       //  3-  process command line args
+#if ENABLE_FEATURE_VI_COLON
+       {
+               char *p = getenv("EXINIT");
+               if (p && *p)
+                       initial_cmds[0] = xstrndup(p, MAX_INPUT_LEN);
+       }
+#endif
+       while ((c = getopt(argc, argv, "hCRH" USE_FEATURE_VI_COLON("c:"))) != -1) {
+               switch (c) {
+#if ENABLE_FEATURE_VI_CRASHME
+               case 'C':
+                       crashme = 1;
+                       break;
+#endif
+#if ENABLE_FEATURE_VI_READONLY
+               case 'R':               // Read-only flag
+                       SET_READONLY_MODE(readonly_mode);
+                       break;
+#endif
+#if ENABLE_FEATURE_VI_COLON
+               case 'c':               // cmd line vi command
+                       if (*optarg)
+                               initial_cmds[initial_cmds[0] != 0] = xstrndup(optarg, MAX_INPUT_LEN);
+                       break;
+#endif
+               case 'H':
+                       show_help();
+                       /* fall through */
+               default:
+                       bb_show_usage();
+                       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) {
+               edit_file(0);
+       } else {
+               for (; optind < argc; optind++) {
+                       edit_file(argv[optind]);
+               }
+       }
+       //-----------------------------------------------------------
+
+       return 0;
+}
+
+/* read text from file or create an empty buf */
+/* will also update current_filename */
+static int init_text_buffer(char *fn)
+{
+       int rc;
+       int size = file_size(fn);       // file size. -1 means does not exist.
+
+       /* allocate/reallocate text buffer */
+       free(text);
+       text_size = size + 10240;
+       screenbegin = dot = end = text = xzalloc(text_size);
+
+       if (fn != current_filename) {
+               free(current_filename);
+               current_filename = xstrdup(fn);
+       }
+       if (size < 0) {
+               // file dont exist. Start empty buf with dummy line
+               char_insert(text, '\n');
+               rc = 0;
+       } else {
+               rc = file_insert(fn, text, 1);
+       }
+       file_modified = 0;
+       last_file_modified = -1;
+#if ENABLE_FEATURE_VI_YANKMARK
+       /* init the marks. */
+       memset(mark, 0, sizeof(mark));
+#endif
+       return rc;
+}
+
+static void edit_file(char *fn)
+{
+#if ENABLE_FEATURE_VI_YANKMARK
+#define cur_line edit_file__cur_line
+#endif
+       int c;
+       int size;
+#if ENABLE_FEATURE_VI_USE_SIGNALS
+       int sig;
+#endif
+
+       editing = 1;    // 0 = exit, 1 = one file, 2 = multiple files
+       rawmode();
+       rows = 24;
+       columns = 80;
+       size = 0;
+       if (ENABLE_FEATURE_VI_WIN_RESIZE) {
+               get_terminal_width_height(0, &columns, &rows);
+               if (rows > MAX_SCR_ROWS) rows = MAX_SCR_ROWS;
+               if (columns > MAX_SCR_COLS) columns = MAX_SCR_COLS;
+       }
+       new_screen(rows, columns);      // get memory for virtual screen
+       init_text_buffer(fn);
+
+#if ENABLE_FEATURE_VI_YANKMARK
+       YDreg = 26;                     // default Yank/Delete reg
+       Ureg = 27;                      // hold orig line for "U" cmd
+       mark[26] = mark[27] = text;     // init "previous context"
+#endif
+
+       last_forward_char = last_input_char = '\0';
+       crow = 0;
+       ccol = 0;
+
+#if ENABLE_FEATURE_VI_USE_SIGNALS
+       catch_sig(0);
+       signal(SIGWINCH, winch_sig);
+       signal(SIGTSTP, suspend_sig);
+       sig = sigsetjmp(restart, 1);
+       if (sig != 0) {
+               screenbegin = dot = text;
+       }
+#endif
+
+       cmd_mode = 0;           // 0=command  1=insert  2='R'eplace
+       cmdcnt = 0;
+       tabstop = 8;
+       offset = 0;                     // no horizontal offset
+       c = '\0';
+#if ENABLE_FEATURE_VI_DOT_CMD
+       free(ioq_start);
+       ioq = ioq_start = NULL;
+       lmc_len = 0;
+       adding2q = 0;
+#endif
+
+#if ENABLE_FEATURE_VI_COLON
+       {
+               char *p, *q;
+               int n = 0;
+
+               while ((p = initial_cmds[n])) {
+                       do {
+                               q = p;
+                               p = strchr(q, '\n');
+                               if (p)
+                                       while (*p == '\n')
+                                               *p++ = '\0';
+                               if (*q)
+                                       colon(q);
+                       } while (p);
+                       free(initial_cmds[n]);
+                       initial_cmds[n] = NULL;
+                       n++;
+               }
+       }
+#endif
+       redraw(FALSE);                  // dont force every col re-draw
+       //------This is the main Vi cmd handling loop -----------------------
+       while (editing > 0) {
+#if ENABLE_FEATURE_VI_CRASHME
+               if (crashme > 0) {
+                       if ((end - text) > 1) {
+                               crash_dummy();  // generate a random command
+                       } else {
+                               crashme = 0;
+                               string_insert(text, "\n\n#####  Ran out of text to work on.  #####\n\n"); // insert the string
+                               dot = text;
+                               refresh(FALSE);
+                       }
+               }
+#endif
+               last_input_char = c = get_one_char();   // get a cmd from user
+#if ENABLE_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
+#if ENABLE_FEATURE_VI_DOT_CMD
+               // These are commands that change text[].
+               // Remember the input for the "." command
+               if (!adding2q && ioq_start == NULL
+                && cmd_mode == 0 // command mode
+                && c > '\0' // exclude NUL and non-ASCII chars
+                && c < 0x7f // (Unicode and such)
+                && strchr(modifying_cmds, c)
+               ) {
+                       start_new_cmd_q(c);
+               }
+#endif
+               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 (!chars_to_parse && mysleep(0) == 0) {
+                       // no input pending - so update output
+                       refresh(FALSE);
+                       show_status_line();
+               }
+#if ENABLE_FEATURE_VI_CRASHME
+               if (crashme > 0)
+                       crash_test();   // test editor variables
+#endif
+       }
+       //-------------------------------------------------------------------
+
+       go_bottom_and_clear_to_eol();
+       cookmode();
+#undef cur_line
+}
+
+//----- The Colon commands -------------------------------------
+#if ENABLE_FEATURE_VI_COLON
+static char *get_one_address(char *p, int *addr)       // get colon addr, if present
+{
+       int st;
+       char *q;
+       USE_FEATURE_VI_YANKMARK(char c;)
+       USE_FEATURE_VI_SEARCH(char *pat;)
+
+       *addr = -1;                     // assume no addr
+       if (*p == '.') {        // the current line
+               p++;
+               q = begin_line(dot);
+               *addr = count_lines(text, q);
+       }
+#if ENABLE_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[(unsigned char) c];
+                       if (q != NULL) {        // is mark valid
+                               *addr = count_lines(text, q);
+                       }
+               }
+       }
+#endif
+#if ENABLE_FEATURE_VI_SEARCH
+       else if (*p == '/') {   // a search pattern
+               q = strchrnul(++p, '/');
+               pat = xstrndup(p, q - p); // save copy of pattern
+               p = q;
+               if (*p == '/')
+                       p++;
+               q = char_search(dot, pat, FORWARD, FULL);
+               if (q != NULL) {
+                       *addr = count_lines(text, q);
+               }
+               free(pat);
+       }
+#endif
+       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(p, "%d%n", addr, &st);
+               p += st;
+       } else {
+               // unrecognised address - assume -1
+               *addr = -1;
+       }
+       return p;
+}
+
+static char *get_address(char *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 (isblank(*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 (isblank(*p))
+               p++;
+       if (*p == ',') {                        // is there a address separator
+               p++;
+               while (isblank(*p))
+                       p++;
+               // get SECOND addr, if present
+               p = get_one_address(p, e);
+       }
+ ga0:
+       while (isblank(*p))
+               p++;                            // skip over trailing spaces
+       return p;
+}
+
+#if ENABLE_FEATURE_VI_SET && ENABLE_FEATURE_VI_SETOPTS
+static void setops(const char *args, const char *opname, int flg_no,
+                       const char *short_opname, int opt)
+{
+       const char *a = 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
+
+// buf must be no longer than MAX_INPUT_LEN!
+static void colon(char *buf)
+{
+       char c, *orig_buf, *buf1, *q, *r;
+       char *fn, cmd[MAX_INPUT_LEN], args[MAX_INPUT_LEN];
+       int i, l, li, ch, b, e;
+       int useforce, forced = FALSE;
+
+       // :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"
+       // :!<cmd>      // run <cmd> then return
+       //
+
+       if (!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 = current_filename;
+
+       // 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++;
+       }
+       *buf1 = '\0';
+       // get any ARGuments
+       while (isblank(*buf))
+               buf++;
+       strcpy(args, buf);
+       useforce = FALSE;
+       buf1 = last_char_is(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(cmd);
+       if (i == 0) {           // :123CR goto line #123
+               if (b >= 0) {
+                       dot = find_line(b);     // what line is #b
+                       dot_skip_over_ws();
+               }
+       }
+#if ENABLE_FEATURE_ALLOW_EXEC
+       else if (strncmp(cmd, "!", 1) == 0) {   // run a cmd
+               int retcode;
+               // :!ls   run the <cmd>
+               go_bottom_and_clear_to_eol();
+               cookmode();
+               retcode = system(orig_buf + 1); // run the cmd
+               if (retcode)
+                       printf("\nshell returned %i\n\n", retcode);
+               rawmode();
+               Hit_Return();                   // let user see results
+       }
+#endif
+       else if (strncmp(cmd, "=", i) == 0) {   // where is the address
+               if (b < 0) {    // no addr given- use defaults
+                       b = e = count_lines(text, dot);
+               }
+               status_line("%d", b);
+       } else if (strncasecmp(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(cmd, "edit", i) == 0) {  // Edit a file
+               // don't edit, if the current file has been modified
+               if (file_modified && !useforce) {
+                       status_line_bold("No write since last change (:edit! overrides)");
+                       goto vc1;
+               }
+               if (args[0]) {
+                       // the user supplied a file name
+                       fn = args;
+               } else if (current_filename && current_filename[0]) {
+                       // no user supplied name- use the current filename
+                       // fn = current_filename;  was set by default
+               } else {
+                       // no user file name, no current name- punt
+                       status_line_bold("No current filename");
+                       goto vc1;
+               }
+
+               if (init_text_buffer(fn) < 0)
+                       goto vc1;
+
+#if ENABLE_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;
+               }
+#endif
+               // how many lines in text[]?
+               li = count_lines(text, end - 1);
+               status_line("\"%s\"%s"
+                       USE_FEATURE_VI_READONLY("%s")
+                       " %dL, %dC", current_filename,
+                       (file_size(fn) < 0 ? " [New file]" : ""),
+                       USE_FEATURE_VI_READONLY(
+                               ((readonly_mode) ? " [Readonly]" : ""),
+                       )
+                       li, ch);
+       } else if (strncasecmp(cmd, "file", i) == 0) {  // what File is this
+               if (b != -1 || e != -1) {
+                       not_implemented("No address allowed on this command");
+                       goto vc1;
+               }
+               if (args[0]) {
+                       // user wants a new filename
+                       free(current_filename);
+                       current_filename = xstrdup(args);
+               } else {
+                       // user wants file status info
+                       last_status_cksum = 0;  // force status update
+               }
+       } else if (strncasecmp(cmd, "features", i) == 0) {      // what features are available
+               // print out values of all features
+               go_bottom_and_clear_to_eol();
+               cookmode();
+               show_help();
+               rawmode();
+               Hit_Return();
+       } else if (strncasecmp(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);
+               }
+               go_bottom_and_clear_to_eol();
+               puts("\r");
+               for (; q <= r; q++) {
+                       int c_is_no_print;
+
+                       c = *q;
+                       c_is_no_print = (c & 0x80) && !Isprint(c);
+                       if (c_is_no_print) {
+                               c = '.';
+                               standout_start();
+                       }
+                       if (c == '\n') {
+                               write1("$\r");
+                       } else if (c < ' ' || c == 127) {
+                               bb_putchar('^');
+                               if (c == 127)
+                                       c = '?';
+                               else
+                                       c += '@';
+                       }
+                       bb_putchar(c);
+                       if (c_is_no_print)
+                               standout_end();
+               }
+#if ENABLE_FEATURE_VI_SET
+ vc2:
+#endif
+               Hit_Return();
+       } else if (strncasecmp(cmd, "quit", i) == 0 // Quit
+               || strncasecmp(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) {
+                       status_line_bold("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) {
+                       status_line_bold("%d more file to edit", (save_argc - optind - 1));
+                       goto vc1;
+               }
+               if (*cmd == 'n' && optind >= save_argc - 1) {
+                       status_line_bold("No more files to edit");
+                       goto vc1;
+               }
+               editing = 0;
+       } else if (strncasecmp(cmd, "read", i) == 0) {  // read file into text[]
+               fn = args;
+               if (!fn[0]) {
+                       status_line_bold("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);
+               { // dance around potentially-reallocated text[]
+                       uintptr_t ofs = q - text;
+                       ch = file_insert(fn, q, 0);
+                       q = text + ofs;
+               }
+               if (ch < 0)
+                       goto vc1;       // nothing was inserted
+               // how many lines in text[]?
+               li = count_lines(q, q + ch - 1);
+               status_line("\"%s\""
+                       USE_FEATURE_VI_READONLY("%s")
+                       " %dL, %dC", fn,
+                       USE_FEATURE_VI_READONLY((readonly_mode ? " [Readonly]" : ""),)
+                       li, ch);
+               if (ch > 0) {
+                       // if the insert is before "dot" then we need to update
+                       if (q <= dot)
+                               dot += ch;
+                       /*file_modified++; - done by file_insert */
+               }
+       } else if (strncasecmp(cmd, "rewind", i) == 0) {        // rewind cmd line args
+               if (file_modified && !useforce) {
+                       status_line_bold("No write since last change (:rewind! overrides)");
+               } else {
+                       // reset the filenames to edit
+                       optind = fn_start - 1;
+                       editing = 0;
+               }
+#if ENABLE_FEATURE_VI_SET
+       } else if (strncasecmp(cmd, "set", i) == 0) {   // set or clear features
+#if ENABLE_FEATURE_VI_SETOPTS
+               char *argp;
+#endif
+               i = 0;                  // offset into args
+               // only blank is regarded as args delmiter. What about tab '\t' ?
+               if (!args[0] || strcasecmp(args, "all") == 0) {
+                       // print out values of all options
+                       go_bottom_and_clear_to_eol();
+                       printf("----------------------------------------\r\n");
+#if ENABLE_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
+                       printf("\r\n");
+                       goto vc2;
+               }
+#if ENABLE_FEATURE_VI_SETOPTS
+               argp = args;
+               while (*argp) {
+                       if (strncasecmp(argp, "no", 2) == 0)
+                               i = 2;          // ":set noautoindent"
+                       setops(argp, "autoindent ", i, "ai", VI_AUTOINDENT);
+                       setops(argp, "flash ", i, "fl", VI_ERR_METHOD);
+                       setops(argp, "ignorecase ", i, "ic", VI_IGNORECASE);
+                       setops(argp, "showmatch ", i, "ic", VI_SHOWMATCH);
+                       /* tabstopXXXX */
+                       if (strncasecmp(argp + i, "tabstop=%d ", 7) == 0) {
+                               sscanf(strchr(argp + i, '='), "tabstop=%d" + 7, &ch);
+                               if (ch > 0 && ch <= MAX_TABSTOP)
+                                       tabstop = ch;
+                       }
+                       while (*argp && *argp != ' ')
+                               argp++; // skip to arg delimiter (i.e. blank)
+                       while (*argp && *argp == ' ')
+                               argp++; // skip all delimiting blanks
+               }
+#endif /* FEATURE_VI_SETOPTS */
+#endif /* FEATURE_VI_SET */
+#if ENABLE_FEATURE_VI_SEARCH
+       } else if (strncasecmp(cmd, "s", 1) == 0) {     // substitute a pattern with a replacement pattern
+               char *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 = strchr(F, c);       // middle delimiter
+               if (!R)
+                       goto colon_s_fail;
+               *R++ = '\0';    // terminate "find"
+               buf1 = strchr(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) {
+                               uintptr_t bias;
+                               // we found the "find" pattern - delete it
+                               text_hole_delete(buf1, buf1 + strlen(F) - 1);
+                               // inset the "replace" patern
+                               bias = string_insert(buf1, R);  // insert the string
+                               buf1 += bias;
+                               ls += bias;
+                               /*q += bias; - recalculated anyway */
+                               // check for "global"  :s/foo/bar/g
+                               if (gflag == 1) {
+                                       if ((buf1 + strlen(R)) < end_line(ls)) {
+                                               q = buf1 + strlen(R);
+                                               goto vc4;       // don't let q move past cur line
+                                       }
+                               }
+                       }
+                       q = next_line(ls);
+               }
+#endif /* FEATURE_VI_SEARCH */
+       } else if (strncasecmp(cmd, "version", i) == 0) {  // show software version
+               status_line(BB_VER " " BB_BT);
+       } else if (strncasecmp(cmd, "write", i) == 0  // write text to file
+               || strncasecmp(cmd, "wq", i) == 0
+               || strncasecmp(cmd, "wn", i) == 0
+               || strncasecmp(cmd, "x", i) == 0
+       ) {
+               // is there a file name to write to?
+               if (args[0]) {
+                       fn = args;
+               }
+#if ENABLE_FEATURE_VI_READONLY
+               if (readonly_mode && !useforce) {
+                       status_line_bold("\"%s\" File is read only", fn);
+                       goto vc3;
+               }
+#endif
+               // 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)
+                               status_line_bold("\"%s\" %s", fn, strerror(errno));
+               } else {
+                       status_line("\"%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;
+                       }
+               }
+#if ENABLE_FEATURE_VI_READONLY
+ vc3:;
+#endif
+#if ENABLE_FEATURE_VI_YANKMARK
+       } else if (strncasecmp(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);
+               status_line("Yank %d lines (%d chars) into [%c]",
+                               li, strlen(reg[YDreg]), what_reg());
+#endif
+       } else {
+               // cmd unknown
+               not_implemented(cmd);
+       }
+ vc1:
+       dot = bound_dot(dot);   // make sure "dot" is valid
+       return;
+#if ENABLE_FEATURE_VI_SEARCH
+ colon_s_fail:
+       status_line(":s expression missing delimiters");
+#endif
+}
+
+#endif /* FEATURE_VI_COLON */
+
+static void Hit_Return(void)
+{
+       int c;
+
+       standout_start();
+       write1("[Hit return to continue]");
+       standout_end();
+       while ((c = get_one_char()) != '\n' && c != '\r')
+               continue;
+       redraw(TRUE);           // force redraw all
+}
+
+static int next_tabstop(int col)
+{
+       return col + ((tabstop - 1) - (col % tabstop));
+}
+
+//----- Synchronize the cursor to Dot --------------------------
+static void sync_cursor(char *d, int *row, int *col)
+{
+       char *beg_cur;  // begin and end of "d" line
+       char *tp;
+       int cnt, ro, co;
+
+       beg_cur = begin_line(d);        // first char of cur line
+
+       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 {
+               char *end_scr;  // begin and end of screen
+               end_scr = end_screen(); // last char of screen
+               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;
+       while (tp < d) { // drive "co" to correct column
+               if (*tp == '\n') //vda || *tp == '\0')
+                       break;
+               if (*tp == '\t') {
+                       // handle tabs like real vi
+                       if (d == tp && cmd_mode) {
+                               break;
+                       }
+                       co = next_tabstop(co);
+               } else if ((unsigned char)*tp < ' ' || *tp == 0x7f) {
+                       co++; // display as ^X, use 2 columns
+               }
+               co++;
+               tp++;
+       }
+
+       // "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 char *begin_line(char *p) // return pointer to first char cur line
+{
+       if (p > text) {
+               p = memrchr(text, '\n', p - text);
+               if (!p)
+                       return text;
+               return p + 1;
+       }
+       return p;
+}
+
+static char *end_line(char *p) // return pointer to NL of cur line
+{
+       if (p < end - 1) {
+               p = memchr(p, '\n', end - p - 1);
+               if (!p)
+                       return end - 1;
+       }
+       return p;
+}
+
+static char *dollar_line(char *p) // return pointer to just before NL line
+{
+       p = end_line(p);
+       // Try to stay off of the Newline
+       if (*p == '\n' && (p - begin_line(p)) > 0)
+               p--;
+       return p;
+}
+
+static char *prev_line(char *p) // return pointer first char prev line
+{
+       p = begin_line(p);      // goto begining of cur line
+       if (p > text && p[-1] == '\n')
+               p--;                    // step to prev line
+       p = begin_line(p);      // goto begining of prev line
+       return p;
+}
+
+static char *next_line(char *p) // return pointer first char next line
+{
+       p = end_line(p);
+       if (p < end - 1 && *p == '\n')
+               p++;                    // step to next line
+       return p;
+}
+
+//----- Text Information Routines ------------------------------
+static char *end_screen(void)
+{
+       char *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;
+}
+
+// count line from start to stop
+static int count_lines(char *start, char *stop)
+{
+       char *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);
+       while (start <= stop && start <= end - 1) {
+               start = end_line(start);
+               if (*start == '\n')
+                       cnt++;
+               start++;
+       }
+       return cnt;
+}
+
+static char *find_line(int li) // find begining of line #li
+{
+       char *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 char *move_to_col(char *p, int l)
+{
+       int co;
+
+       p = begin_line(p);
+       co = 0;
+       while (co < l && p < end) {
+               if (*p == '\n') //vda || *p == '\0')
+                       break;
+               if (*p == '\t') {
+                       co = next_tabstop(co);
+               } else if (*p < ' ' || *p == 127) {
+                       co++; // display as ^X, use 2 columns
+               }
+               co++;
+               p++;
+       }
+       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)
+{
+       char *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'
+{
+       text_hole_delete(dot, dot);
+}
+
+static char *bound_dot(char *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 char *new_screen(int ro, int co)
+{
+       int li;
+
+       free(screen);
+       screensize = ro * co + 8;
+       screen = 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;
+}
+
+#if ENABLE_FEATURE_VI_SEARCH
+static int mycmp(const char *s1, const char *s2, int len)
+{
+       int i;
+
+       i = strncmp(s1, s2, len);
+       if (ENABLE_FEATURE_VI_SETOPTS && ignorecase) {
+               i = strncasecmp(s1, s2, len);
+       }
+       return i;
+}
+
+// search for pattern starting at p
+static char *char_search(char *p, const char *pat, int dir, int range)
+{
+#ifndef REGEX_SEARCH
+       char *start, *stop;
+       int len;
+
+       len = strlen(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 = re_compile_pattern(pat, strlen(pat), &preg);
+       if (q != 0) {
+               // The pattern was not compiled
+               status_line_bold("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 /* FEATURE_VI_SEARCH */
+
+static char *char_insert(char *p, char c) // insert the char c at 'p'
+{
+       if (c == 22) {          // Is this an ctrl-V?
+               p += stupid_insert(p, '^');     // use ^ to indicate literal next
+               refresh(FALSE); // show the ^
+               c = get_one_char();
+               *p = c;
+               p++;
+               file_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[]
+               char *sp;               // "save p"
+
+               if (c == 13)
+                       c = '\n';       // translate \r to \n
+               sp = p;                 // remember addr of insert
+               p += 1 + stupid_insert(p, c);   // insert the char
+#if ENABLE_FEATURE_VI_SETOPTS
+               if (showmatch && strchr(")]}", *sp) != NULL) {
+                       showmatching(sp);
+               }
+               if (autoindent && c == '\n') {  // auto indent the new line
+                       char *q;
+                       size_t len;
+                       q = prev_line(p);       // use prev line as template
+                       len = strspn(q, " \t"); // space or tab
+                       if (len) {
+                               uintptr_t bias;
+                               bias = text_hole_make(p, len);
+                               p += bias;
+                               q += bias;
+                               memcpy(p, q, len);
+                               p += len;
+                       }
+               }
+#endif
+       }
+       return p;
+}
+
+// might reallocate text[]! use p += stupid_insert(p, ...),
+// and be careful to not use pointers into potentially freed text[]!
+static uintptr_t stupid_insert(char *p, char c) // stupidly insert the char c at 'p'
+{
+       uintptr_t bias;
+       bias = text_hole_make(p, 1);
+       p += bias;
+       *p = c;
+       //file_modified++; - done by text_hole_make()
+       return bias;
+}
+
+static int find_range(char **start, char **stop, char c)
+{
+       char *save_dot, *p, *q, *t;
+       int cnt, multiline = 0;
+
+       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("^%$0bBeEfth\b\177", 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 {
+           // nothing -- this causes any other values of c to
+           // represent the one-character range under the
+           // cursor.  this is correct for ' ' and 'l', but
+           // perhaps no others.
+           //
+       }
+       if (q < p) {
+               t = q;
+               q = p;
+               p = t;
+       }
+
+       // backward char movements don't include start position
+       if (q > p && strchr("^0bBh\b\177", c)) q--;
+
+       multiline = 0;
+       for (t = p; t <= q; t++) {
+               if (*t == '\n') {
+                       multiline = 1;
+                       break;
+               }
+       }
+
+       *start = p;
+       *stop = q;
+       dot = save_dot;
+       return multiline;
+}
+
+static int st_test(char *p, int type, int dir, char *tested)
+{
+       char 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 char *skip_thing(char *p, int linecnt, int dir, int type)
+{
+       char 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 char *find_pair(char *p, const char c)
+{
+       char 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;
+}
+
+#if ENABLE_FEATURE_VI_SETOPTS
+// show the matching char of a pair,  ()  []  {}
+static void showmatching(char *p)
+{
+       char *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
+               mysleep(40);    // give user some time
+               dot = save_dot; // go back to old loc
+               refresh(FALSE);
+       }
+}
+#endif /* FEATURE_VI_SETOPTS */
+
+// open a hole in text[]
+// might reallocate text[]! use p += text_hole_make(p, ...),
+// and be careful to not use pointers into potentially freed text[]!
+static uintptr_t text_hole_make(char *p, int size)     // at "p", make a 'size' byte hole
+{
+       uintptr_t bias = 0;
+
+       if (size <= 0)
+               return bias;
+       end += size;            // adjust the new END
+       if (end >= (text + text_size)) {
+               char *new_text;
+               text_size += end - (text + text_size) + 10240;
+               new_text = xrealloc(text, text_size);
+               bias = (new_text - text);
+               screenbegin += bias;
+               dot         += bias;
+               end         += bias;
+               p           += bias;
+               text = new_text;
+       }
+       memmove(p + size, p, end - size - p);
+       memset(p, ' ', size);   // clear new hole
+       file_modified++;
+       return bias;
+}
+
+//  close a hole in text[]
+static char *text_hole_delete(char *p, char *q) // delete "p" through "q", inclusive
+{
+       char *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
+       memmove(dest, src, cnt);
+ 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++;
+ thd0:
+       return dest;
+}
+
+// copy text into register, then delete text.
+// if dist <= 0, do not include, or go past, a NewLine
+//
+static char *yank_delete(char *start, char *stop, int dist, int yf)
+{
+       char *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;
+#if ENABLE_FEATURE_VI_YANKMARK
+       text_yank(start, stop, YDreg);
+#endif
+       if (yf == YANKDEL) {
+               p = text_hole_delete(start, stop);
+       }                                       // delete lines
+       return p;
+}
+
+static void show_help(void)
+{
+       puts("These features are available:"
+#if ENABLE_FEATURE_VI_SEARCH
+       "\n\tPattern searches with / and ?"
+#endif
+#if ENABLE_FEATURE_VI_DOT_CMD
+       "\n\tLast command repeat with \'.\'"
+#endif
+#if ENABLE_FEATURE_VI_YANKMARK
+       "\n\tLine marking with 'x"
+       "\n\tNamed buffers with \"x"
+#endif
+#if ENABLE_FEATURE_VI_READONLY
+       "\n\tReadonly if vi is called as \"view\""
+       "\n\tReadonly with -R command line arg"
+#endif
+#if ENABLE_FEATURE_VI_SET
+       "\n\tSome colon mode commands with \':\'"
+#endif
+#if ENABLE_FEATURE_VI_SETOPTS
+       "\n\tSettable options with \":set\""
+#endif
+#if ENABLE_FEATURE_VI_USE_SIGNALS
+       "\n\tSignal catching- ^C"
+       "\n\tJob suspend and resume with ^Z"
+#endif
+#if ENABLE_FEATURE_VI_WIN_RESIZE
+       "\n\tAdapt to window re-sizes"
+#endif
+       );
+}
+
+#if ENABLE_FEATURE_VI_DOT_CMD
+static void start_new_cmd_q(char c)
+{
+       // get buffer for new cmd
+       // if there is a current cmd count put it in the buffer first
+       if (cmdcnt > 0) {
+               lmc_len = sprintf(last_modifying_cmd, "%d%c", cmdcnt, c);
+       } else { // just save char c onto queue
+               last_modifying_cmd[0] = c;
+               lmc_len = 1;
+       }
+       adding2q = 1;
+}
+
+static void end_cmd_q(void)
+{
+#if ENABLE_FEATURE_VI_YANKMARK
+       YDreg = 26;                     // go back to default Yank/Delete reg
+#endif
+       adding2q = 0;
+}
+#endif /* FEATURE_VI_DOT_CMD */
+
+#if ENABLE_FEATURE_VI_YANKMARK \
+ || (ENABLE_FEATURE_VI_COLON && ENABLE_FEATURE_VI_SEARCH) \
+ || ENABLE_FEATURE_VI_CRASHME
+// might reallocate text[]! use p += string_insert(p, ...),
+// and be careful to not use pointers into potentially freed text[]!
+static uintptr_t string_insert(char *p, const char *s) // insert the string at 'p'
+{
+       uintptr_t bias;
+       int i;
+
+       i = strlen(s);
+       bias = text_hole_make(p, i);
+       p += bias;
+       memcpy(p, s, i);
+#if ENABLE_FEATURE_VI_YANKMARK
+       {
+               int cnt;
+               for (cnt = 0; *s != '\0'; s++) {
+                       if (*s == '\n')
+                               cnt++;
+               }
+               status_line("Put %d lines (%d chars) from [%c]", cnt, i, what_reg());
+       }
+#endif
+       return bias;
+}
+#endif
+
+#if ENABLE_FEATURE_VI_YANKMARK
+static char *text_yank(char *p, char *q, int dest)     // copy text into a register
+{
+       int cnt = q - p;
+       if (cnt < 0) {          // they are backwards- reverse them
+               p = q;
+               cnt = -cnt;
+       }
+       free(reg[dest]);        //  if already a yank register, free it
+       reg[dest] = xstrndup(p, cnt + 1);
+       return p;
+}
+
+static char what_reg(void)
+{
+       char c;
+
+       c = 'D';                        // default to D-reg
+       if (0 <= YDreg && YDreg <= 25)
+               c = 'a' + (char) YDreg;
+       if (YDreg == 26)
+               c = 'D';
+       if (YDreg == 27)
+               c = 'U';
+       return c;
+}
+
+static void check_context(char 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(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;
+               }
+       }
+}
+
+static char *swap_context(char *p) // goto new context for '' command make this the current context
+{
+       char *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 /* FEATURE_VI_YANKMARK */
+
+//----- 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_stdin_TCSANOW(&term_vi);
+}
+
+static void cookmode(void)
+{
+       fflush(stdout);
+       tcsetattr_stdin_TCSANOW(&term_orig);
+}
+
+//----- Come here when we get a window resize signal ---------
+#if ENABLE_FEATURE_VI_USE_SIGNALS
+static void winch_sig(int sig UNUSED_PARAM)
+{
+       // FIXME: do it in main loop!!!
+       signal(SIGWINCH, winch_sig);
+       if (ENABLE_FEATURE_VI_WIN_RESIZE) {
+               get_terminal_width_height(0, &columns, &rows);
+               if (rows > MAX_SCR_ROWS) rows = MAX_SCR_ROWS;
+               if (columns > MAX_SCR_COLS) columns = MAX_SCR_COLS;
+       }
+       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 UNUSED_PARAM)
+{
+       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); // huh? why? we are already "continued"...
+}
+
+//----- Come here when we get a Suspend signal -------------------
+static void suspend_sig(int sig UNUSED_PARAM)
+{
+       go_bottom_and_clear_to_eol();
+       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)
+               siglongjmp(restart, sig);
+}
+#endif /* FEATURE_VI_USE_SIGNALS */
+
+static int mysleep(int hund)   // sleep for 'h' 1/100 seconds
+{
+       struct pollfd pfd[1];
+
+       pfd[0].fd = 0;
+       pfd[0].events = POLLIN;
+       return safe_poll(pfd, 1, hund*10) > 0;
+}
+
+//----- IO Routines --------------------------------------------
+static int readit(void) // read (maybe cursor) key from stdin
+{
+       int c;
+
+       fflush(stdout);
+       c = read_key(STDIN_FILENO, &chars_to_parse, readbuffer);
+       if (c == -1) { // EOF/error
+               go_bottom_and_clear_to_eol();
+               cookmode(); // terminal to "cooked"
+               bb_error_msg_and_die("can't read user input");
+       }
+       return c;
+}
+
+//----- IO Routines --------------------------------------------
+static int get_one_char(void)
+{
+       int c;
+
+#if ENABLE_FEATURE_VI_DOT_CMD
+       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
+                       // careful with correct sign expansion!
+                       c = (unsigned char)*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 (lmc_len >= MAX_INPUT_LEN - 1) {
+                       status_line_bold("last_modifying_cmd overrun");
+               } else {
+                       // add new char to q
+                       last_modifying_cmd[lmc_len++] = c;
+               }
+       }
+#else
+       c = readit();           // get the users input
+#endif /* FEATURE_VI_DOT_CMD */
+       return c;
+}
+
+// Get input line (uses "status line" area)
+static char *get_input_line(const char *prompt)
+{
+       // char [MAX_INPUT_LEN]
+#define buf get_input_line__buf
+
+       int c;
+       int i;
+
+       strcpy(buf, prompt);
+       last_status_cksum = 0;  // force status update
+       go_bottom_and_clear_to_eol();
+       write1(prompt);      // write out the :, /, or ? prompt
+
+       i = strlen(buf);
+       while (i < MAX_INPUT_LEN) {
+               c = get_one_char();
+               if (c == '\n' || c == '\r' || c == 27)
+                       break;          // this is end of input
+               if (c == erase_char || c == 8 || c == 127) {
+                       // user wants to erase prev char
+                       buf[--i] = '\0';
+                       write1("\b \b"); // erase char on screen
+                       if (i <= 0) // user backs up before b-o-l, exit
+                               break;
+               } else if (c > 0 && c < 256) { // exclude Unicode
+                       // (TODO: need to handle Unicode)
+                       buf[i] = c;
+                       buf[++i] = '\0';
+                       bb_putchar(c);
+               }
+       }
+       refresh(FALSE);
+       return buf;
+#undef buf
+}
+
+static int file_size(const char *fn) // what is the byte size of "fn"
+{
+       struct stat st_buf;
+       int cnt;
+
+       cnt = -1;
+       if (fn && fn[0] && stat(fn, &st_buf) == 0)      // see if file exists
+               cnt = (int) st_buf.st_size;
+       return cnt;
+}
+
+// might reallocate text[]!
+static int file_insert(const char *fn, char *p, int update_ro_status)
+{
+       int cnt = -1;
+       int fd, size;
+       struct stat statbuf;
+
+       /* Validate file */
+       if (stat(fn, &statbuf) < 0) {
+               status_line_bold("\"%s\" %s", fn, strerror(errno));
+               goto fi0;
+       }
+       if (!S_ISREG(statbuf.st_mode)) {
+               // This is not a regular file
+               status_line_bold("\"%s\" Not a regular file", fn);
+               goto fi0;
+       }
+       if (p < text || p > end) {
+               status_line_bold("Trying to insert file outside of memory");
+               goto fi0;
+       }
+
+       // read file to buffer
+       fd = open(fn, O_RDONLY);
+       if (fd < 0) {
+               status_line_bold("\"%s\" %s", fn, strerror(errno));
+               goto fi0;
+       }
+       size = statbuf.st_size;
+       p += text_hole_make(p, size);
+       cnt = safe_read(fd, p, size);
+       if (cnt < 0) {
+               status_line_bold("\"%s\" %s", fn, strerror(errno));
+               p = text_hole_delete(p, p + size - 1);  // un-do buffer insert
+       } 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
+               status_line_bold("cannot read all of file \"%s\"", fn);
+       }
+       if (cnt >= size)
+               file_modified++;
+       close(fd);
+ fi0:
+#if ENABLE_FEATURE_VI_READONLY
+       if (update_ro_status
+        && ((access(fn, W_OK) < 0) ||
+               /* root will always have access()
+                * so we check fileperms too */
+               !(statbuf.st_mode & (S_IWUSR | S_IWGRP | S_IWOTH))
+           )
+       ) {
+               SET_READONLY_FILE(readonly_mode);
+       }
+#endif
+       return cnt;
+}
+
+static int file_write(char *fn, char *first, char *last)
+{
+       int fd, cnt, charcnt;
+
+       if (fn == 0) {
+               status_line_bold("No current filename");
+               return -2;
+       }
+       charcnt = 0;
+       /* By popular request we do not open file with O_TRUNC,
+        * but instead ftruncate() it _after_ successful write.
+        * Might reduce amount of data lost on power fail etc.
+        */
+       fd = open(fn, (O_WRONLY | O_CREAT), 0666);
+       if (fd < 0)
+               return -1;
+       cnt = last - first + 1;
+       charcnt = full_write(fd, first, cnt);
+       ftruncate(fd, charcnt);
+       if (charcnt == cnt) {
+               // good write
+               //file_modified = FALSE;
+       } 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 optimize)
+{
+       char cm1[sizeof(CMrc) + sizeof(int)*3 * 2];
+#if ENABLE_FEATURE_VI_OPTIMIZE_CURSOR
+       enum {
+               SZ_UP = sizeof(CMup),
+               SZ_DN = sizeof(CMdown),
+               SEQ_SIZE = SZ_UP > SZ_DN ? SZ_UP : SZ_DN,
+       };
+       char cm2[SEQ_SIZE * 5 + 32]; // bigger than worst case size
+#endif
+       char *cm;
+
+       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(cm1, CMrc, row + 1, col + 1);
+       cm = cm1;
+
+#if ENABLE_FEATURE_VI_OPTIMIZE_CURSOR
+       if (optimize && col < 16) {
+               char *screenp;
+               int Rrow = last_row;
+               int diff = Rrow - row;
+
+               if (diff < -5 || diff > 5)
+                       goto skip;
+
+               //----- find the minimum # of chars to move cursor -------------
+               //----- 2.  Try moving with discreet chars (Newline, [back]space, ...)
+               cm2[0] = '\0';
+
+               // 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, screenp, col);
+
+               // pick the shortest cursor motion to send out
+               if (strlen(cm2) < strlen(cm)) {
+                       cm = cm2;
+               }
+ skip: ;
+       }
+       last_row = row;
+#endif /* FEATURE_VI_OPTIMIZE_CURSOR */
+       write1(cm);
+}
+
+//----- Erase from cursor to end of line -----------------------
+static void clear_to_eol(void)
+{
+       write1(Ceol);   // Erase from cursor to end of line
+}
+
+static void go_bottom_and_clear_to_eol(void)
+{
+       place_cursor(rows - 1, 0, FALSE); // go to bottom of screen
+       clear_to_eol(); // erase 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);
+       mysleep(h);
+       standout_end();         // send "end reverse video" sequence
+       redraw(TRUE);
+}
+
+static void Indicate_Error(void)
+{
+#if ENABLE_FEATURE_VI_CRASHME
+       if (crashme > 0)
+               return;                 // generate a random command
+#endif
+       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(char *buf, int count)
+{
+       int sum = 0;
+       char *e = buf + count;
+
+       while (buf < e)
+               sum += (unsigned char) *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
+               go_bottom_and_clear_to_eol();
+               write1(status_buffer);
+               if (have_status_msg) {
+                       if (((int)strlen(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 status_line_bold(const char *format, ...)
+{
+       va_list args;
+
+       va_start(args, format);
+       strcpy(status_buffer, SOs);     // Terminal standout mode on
+       vsprintf(status_buffer + sizeof(SOs)-1, format, args);
+       strcat(status_buffer, SOn);     // Terminal standout mode off
+       va_end(args);
+
+       have_status_msg = 1 + sizeof(SOs) + sizeof(SOn) - 2;
+}
+
+// format status buffer
+static void status_line(const char *format, ...)
+{
+       va_list args;
+
+       va_start(args, format);
+       vsprintf(status_buffer, format, args);
+       va_end(args);
+
+       have_status_msg = 1;
+}
+
+// copy s to buf, convert unprintable
+static void print_literal(char *buf, const char *s)
+{
+       unsigned char c;
+       char b[2];
+
+       b[1] = '\0';
+       buf[0] = '\0';
+       if (!s[0])
+               s = "(NULL)";
+       for (; *s; s++) {
+               int c_is_no_print;
+
+               c = *s;
+               c_is_no_print = (c & 0x80) && !Isprint(c);
+               if (c_is_no_print) {
+                       strcat(buf, SOn);
+                       c = '.';
+               }
+               if (c < ' ' || c == 127) {
+                       strcat(buf, "^");
+                       if (c == 127)
+                               c = '?';
+                       else
+                               c += '@';
+               }
+               b[0] = c;
+               strcat(buf, b);
+               if (c_is_no_print)
+                       strcat(buf, SOs);
+               if (*s == '\n')
+                       strcat(buf, "$");
+               if (strlen(buf) > MAX_INPUT_LEN - 10) // paranoia
+                       break;
+       }
+}
+
+static void not_implemented(const char *s)
+{
+       char buf[MAX_INPUT_LEN];
+
+       print_literal(buf, s);
+       status_line_bold("\'%s\' is not implemented", buf);
+}
+
+// show file status on status line
+static int format_edit_status(void)
+{
+       static const char cmd_mode_indicator[] ALIGN1 = "-IR-";
+
+#define tot format_edit_status__tot
+
+       int cur, percent, ret, trunc_at;
+
+       // 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(status_buffer, trunc_at+1,
+#if ENABLE_FEATURE_VI_READONLY
+               "%c %s%s%s %d/%d %d%%",
+#else
+               "%c %s%s %d/%d %d%%",
+#endif
+               cmd_mode_indicator[cmd_mode & 3],
+               (current_filename != NULL ? current_filename : "No file"),
+#if ENABLE_FEATURE_VI_READONLY
+               (readonly_mode ? " [Readonly]" : ""),
+#endif
+               (file_modified ? " [Modified]" : ""),
+               cur, tot, percent);
+
+       if (ret >= 0 && ret < trunc_at)
+               return ret;  /* it all fit */
+
+       return trunc_at;  /* had to truncate */
+#undef tot
+}
+
+//----- Force refresh of all Lines -----------------------------
+static void redraw(int full_screen)
+{
+       place_cursor(0, 0, FALSE);      // put cursor in correct place
+       clear_to_eos();         // tell 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 char* format_line(char *src /*, int li*/)
+{
+       unsigned char c;
+       int co;
+       int ofs = offset;
+       char *dest = scr_out_buf; // [MAX_SCR_COLS + MAX_TABSTOP * 2]
+
+       c = '~'; // char in col 0 in non-existent lines is '~'
+       co = 0;
+       while (co < columns + tabstop) {
+               // have we gone past the end?
+               if (src < end) {
+                       c = *src++;
+                       if (c == '\n')
+                               break;
+                       if ((c & 0x80) && !Isprint(c)) {
+                               c = '.';
+                       }
+                       if (c < ' ' || c == 0x7f) {
+                               if (c == '\t') {
+                                       c = ' ';
+                                       //      co %    8     !=     7
+                                       while ((co % tabstop) != (tabstop - 1)) {
+                                               dest[co++] = c;
+                                       }
+                               } else {
+                                       dest[co++] = '^';
+                                       if (c == 0x7f)
+                                               c = '?';
+                                       else
+                                               c += '@'; // Ctrl-X -> 'X'
+                               }
+                       }
+               }
+               dest[co++] = c;
+               // discard scrolled-off-to-the-left portion,
+               // in tabstop-sized pieces
+               if (ofs >= tabstop && co >= tabstop) {
+                       memmove(dest, dest + tabstop, co);
+                       co -= tabstop;
+                       ofs -= tabstop;
+               }
+               if (src >= end)
+                       break;
+       }
+       // check "short line, gigantic offset" case
+       if (co < ofs)
+               ofs = co;
+       // discard last scrolled off part
+       co -= ofs;
+       dest += ofs;
+       // fill the rest with spaces
+       if (co < columns)
+               memset(&dest[co], ' ', columns - co);
+       return dest;
+}
+
+//----- 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)
+{
+#define old_offset refresh__old_offset
+
+       int li, changed;
+       char *tp, *sp;          // pointer into text[] and screen[]
+
+       if (ENABLE_FEATURE_VI_WIN_RESIZE) {
+               unsigned c = columns, r = rows;
+               get_terminal_width_height(0, &columns, &rows);
+               if (rows > MAX_SCR_ROWS) rows = MAX_SCR_ROWS;
+               if (columns > MAX_SCR_COLS) columns = MAX_SCR_COLS;
+               full_screen |= (c - columns) | (r - 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
+               char *out_buf;
+               // format current text line
+               out_buf = format_line(tp /*, li*/);
+
+               // skip to the end of the current text[] line
+               if (tp < end) {
+                       char *t = memchr(tp, '\n', end - tp);
+                       if (!t) t = end - 1;
+                       tp = t + 1;
+               }
+
+               // see if there are any changes between vitual screen and out_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 (out_buf[cs] != sp[cs]) {
+                               changed = TRUE; // mark for redraw
+                               break;
+                       }
+               }
+
+               // look backward for last difference between out_buf and screen
+               for (; ce >= cs; ce--) {
+                       if (out_buf[ce] != 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 out_buf
+               if (changed) {
+                       // copy changed part of buffer to virtual screen
+                       memcpy(sp+cs, out_buf+cs, ce-cs+1);
+
+                       // move cursor to column of first change
+                       //if (offset != old_offset) {
+                       //      // place_cursor is still too stupid
+                       //      // to handle offsets correctly
+                       //      place_cursor(li, cs, FALSE);
+                       //} else {
+                               place_cursor(li, cs, TRUE);
+                       //}
+
+                       // write line out to terminal
+                       fwrite(&sp[cs], ce - cs + 1, 1, stdout);
+               }
+       }
+
+       place_cursor(crow, ccol, TRUE);
+
+       old_offset = offset;
+#undef old_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(int c)
+{
+       const char *msg = msg; // for compiler
+       char *p, *q, *save_dot;
+       char buf[12];
+       int dir;
+       int cnt, i, j;
+       int c1;
+
+//     c1 = c; // quiet the compiler
+//     cnt = yf = 0; // quiet the compiler
+//     msg = p = q = save_dot = buf; // quiet the compiler
+       memset(buf, '\0', 12);
+
+       show_status_line();
+
+       /* if this is a cursor key, skip these checks */
+       switch (c) {
+               case KEYCODE_UP:
+               case KEYCODE_DOWN:
+               case KEYCODE_LEFT:
+               case KEYCODE_RIGHT:
+               case KEYCODE_HOME:
+               case KEYCODE_END:
+               case KEYCODE_PAGEUP:
+               case KEYCODE_PAGEDOWN:
+               case KEYCODE_DELETE:
+                       goto key_cmd_mode;
+       }
+
+       if (cmd_mode == 2) {
+               //  flip-flop Insert/Replace mode
+               if (c == KEYCODE_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 == KEYCODE_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
+#if ENABLE_FEATURE_VI_CRASHME
+       case 0x14:                      // dc4  ctrl-T
+               crashme = (crashme == 0) ? 1 : 0;
+               break;
+#endif
+               //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 'F':     // F-
+               //case 'K':     // K-
+               //case 'Q':     // Q-
+               //case 'S':     // S-
+               //case 'T':     // T-
+               //case 'V':     // V-
+               //case '[':     // [-
+               //case '\\':    // \-
+               //case ']':     // ]-
+               //case '_':     // _-
+               //case '`':     // `-
+               //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';
+               }
+               not_implemented(buf);
+               end_cmd_q();    // stop adding to q
+       case 0x00:                      // nul- ignore
+               break;
+       case 2:                 // ctrl-B  scroll up   full screen
+       case KEYCODE_PAGEUP:    // Cursor Key Page Up
+               dot_scroll(rows - 2, -1);
+               break;
+       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 KEYCODE_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 KEYCODE_LEFT:      // cursor key Left
+       case 8:         // ctrl-H- move left    (This may be ERASE char)
+       case 0x7f:      // 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 KEYCODE_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
+               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 KEYCODE_RIGHT:     // Cursor Key Right
+               if (cmdcnt-- > 1) {
+                       do_cmd(c);
+               }                               // repeat cnt
+               dot_right();
+               break;
+#if ENABLE_FEATURE_VI_YANKMARK
+       case '"':                       // "- name a register to use for Delete/Yank
+               c1 = (get_one_char() | 0x20) - 'a'; // | 0x20 is tolower()
+               if ((unsigned)c1 <= 25) { // a-z?
+                       YDreg = c1;
+               } else {
+                       indicate_error(c);
+               }
+               break;
+       case '\'':                      // '- goto a specific mark
+               c1 = (get_one_char() | 0x20) - 'a';
+               if ((unsigned)c1 <= 25) { // a-z?
+                       // get the b-o-l
+                       q = mark[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() | 0x20) - 'a';
+               if ((unsigned)c1 <= 25) { // a-z?
+                       // remember the line
+                       mark[c1] = dot;
+               } else {
+                       indicate_error(c);
+               }
+               break;
+       case 'P':                       // P- Put register before
+       case 'p':                       // p- put register after
+               p = reg[YDreg];
+               if (p == NULL) {
+                       status_line_bold("Nothing in register %c", what_reg());
+                       break;
+               }
+               // are we putting whole lines or strings
+               if (strchr(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
+               }
+               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 /* FEATURE_VI_YANKMARK */
+       case '$':                       // $- goto end of line
+       case KEYCODE_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 through 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 ',':           // repeat latest 'f' in opposite direction
+               if (cmdcnt-- > 1) {
+                       do_cmd(',');
+               }                               // repeat cnt
+               if (last_forward_char == 0)
+                       break;
+               q = dot - 1;
+               while (q >= text && *q != '\n' && *q != last_forward_char) {
+                       q--;
+               }
+               if (q >= text && *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;
+#if ENABLE_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 (lmc_len > 0) {
+                       last_modifying_cmd[lmc_len] = 0;
+                       ioq = ioq_start = xstrdup(last_modifying_cmd);
+               }
+               break;
+#endif
+#if ENABLE_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 (q[0] && !q[1]) {
+                       if (last_search_pattern[0])
+                               last_search_pattern[0] = c;
+                       goto dc3; // if no pat re-use old pat
+               }
+               if (q[0]) {       // strlen(q) > 1: new pat- save it and find
+                       // there is a new pat
+                       free(last_search_pattern);
+                       last_search_pattern = xstrdup(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:
+               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 = "";
+                       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 = "search hit BOTTOM, continuing at TOP";
+                       if (dir == BACK) {
+                               msg = "search hit TOP, continuing at BOTTOM";
+                       }
+               } else {
+                       msg = "Pattern not found";
+               }
+ dc2:
+               if (*msg)
+                       status_line_bold("%s", msg);
+               break;
+       case '{':                       // {- move backward paragraph
+               q = char_search(dot, "\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, "\n\n", FORWARD, FULL);
+               if (q != NULL) {        // found blank line
+                       dot = next_line(q);     // move to next blank line
+               }
+               break;
+#endif /* 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(":");        // get input line- use "status line"
+#if ENABLE_FEATURE_VI_COLON
+               colon(p);               // execute the command
+#else
+               if (*p == ':')
+                       p++;                            // move past the ':'
+               cnt = strlen(p);
+               if (cnt <= 0)
+                       break;
+               if (strncasecmp(p, "quit", cnt) == 0
+                || strncasecmp(p, "q!", cnt) == 0   // delete lines
+               ) {
+                       if (file_modified && p[1] != '!') {
+                               status_line_bold("No write since last change (:quit! overrides)");
+                       } else {
+                               editing = 0;
+                       }
+               } else if (strncasecmp(p, "write", cnt) == 0
+                       || strncasecmp(p, "wq", cnt) == 0
+                       || strncasecmp(p, "wn", cnt) == 0
+                       || strncasecmp(p, "x", cnt) == 0
+               ) {
+                       cnt = file_write(current_filename, text, end - 1);
+                       if (cnt < 0) {
+                               if (cnt == -1)
+                                       status_line_bold("Write error: %s", strerror(errno));
+                       } else {
+                               file_modified = 0;
+                               last_file_modified = -1;
+                               status_line("\"%s\" %dL, %dC", current_filename, 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(p, "file", cnt) == 0) {
+                       last_status_cksum = 0;  // force status update
+               } else if (sscanf(p, "%d", &j) > 0) {
+                       dot = find_line(j);             // go to line # j
+                       dot_skip_over_ws();
+               } else {                // unrecognised cmd
+                       not_implemented(p);
+               }
+#endif /* !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);
+               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
+                                       text_hole_delete(p, p);
+                               } else if (*p == ' ') {
+                                       // we should be calculating columns, not just SPACE
+                                       for (j = 0; *p == ' ' && j < tabstop; j++) {
+                                               text_hole_delete(p, p);
+                                       }
+                               }
+                       } else if (c == '>') {
+                               // shift right -- add tab or 8 spaces
+                               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 through 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
+#if ENABLE_FEATURE_VI_DOT_CMD
+               if (c == 'D')
+                       end_cmd_q();    // stop adding to q
+#endif
+               break;
+       case 'g': // 'gg' goto a line number (vim) (default: very first line)
+               c1 = get_one_char();
+               if (c1 != 'g') {
+                       buf[0] = 'g';
+                       buf[1] = c1; // TODO: if Unicode?
+                       buf[2] = '\0';
+                       not_implemented(buf);
+                       break;
+               }
+               if (cmdcnt == 0)
+                       cmdcnt = 1;
+               /* fall through */
+       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 through to ... 'i'
+       case 'i':                       // i- insert before current char
+       case KEYCODE_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 (isblank(*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 KEYCODE_DELETE:
+               c = 'x';
+               // fall through
+       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) {
+                       if (ENABLE_FEATURE_VI_READONLY && readonly_mode) {
+                               status_line_bold("\"%s\" File is read only", current_filename);
+                               break;
+                       }
+                       cnt = file_write(current_filename, text, end - 1);
+                       if (cnt < 0) {
+                               if (cnt == -1)
+                                       status_line_bold("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
+#if ENABLE_FEATURE_VI_YANKMARK
+       case 'y':                       // y- yank   something
+       case 'Y':                       // Y- Yank a line
+#endif
+       {
+               int yf, ml, whole = 0;
+               yf = YANKDEL;   // assume either "c" or "d"
+#if ENABLE_FEATURE_VI_YANKMARK
+               if (c == 'y' || c == 'Y')
+                       yf = YANKONLY;
+#endif
+               c1 = 'y';
+               if (c != 'Y')
+                       c1 = get_one_char();    // get the type of thing to delete
+               // determine range, and whether it spans lines
+               ml = 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 (isblank(*q)) {
+                                       if (q <= text || q[-1] == '\n')
+                                               break;
+                                       q--;
+                               }
+                       }
+                       dot = yank_delete(p, q, ml, yf);        // delete word
+               } else if (strchr("^0bBeEft%$ lh\b\177", c1)) {
+                       // partial line copy text into a register and delete
+                       dot = yank_delete(p, q, ml, yf);        // delete word
+               } else if (strchr("cdykjHL+-{}\r\n", c1)) {
+                       // whole line copy text into a register and delete
+                       dot = yank_delete(p, q, ml, yf);        // delete lines
+                       whole = 1;
+               } else {
+                       // could not recognize object
+                       c = c1 = 27;    // error-
+                       ml = 0;
+                       indicate_error(c);
+               }
+               if (ml && whole) {
+                       if (c == 'c') {
+                               dot = char_insert(dot, '\n');
+                               // on the last line of file don't move to prev line
+                               if (whole && dot != (end-1)) {
+                                       dot_prev();
+                               }
+                       } else if (c == 'd') {
+                               dot_begin();
+                               dot_skip_over_ws();
+                       }
+               }
+               if (c1 != 27) {
+                       // if CHANGING, not deleting, start inserting after the delete
+                       if (c == 'c') {
+                               strcpy(buf, "Change");
+                               goto dc_i;      // start inserting
+                       }
+                       if (c == 'd') {
+                               strcpy(buf, "Delete");
+                       }
+#if ENABLE_FEATURE_VI_YANKMARK
+                       if (c == 'y' || c == 'Y') {
+                               strcpy(buf, "Yank");
+                       }
+                       p = reg[YDreg];
+                       q = p + strlen(p);
+                       for (cnt = 0; p <= q; p++) {
+                               if (*p == '\n')
+                                       cnt++;
+                       }
+                       status_line("%s %d lines (%d chars) using [%c]",
+                               buf, cnt, strlen(reg[YDreg]), what_reg());
+#endif
+                       end_cmd_q();    // stop adding to q
+               }
+               break;
+       }
+       case 'k':                       // k- goto prev line, same col
+       case KEYCODE_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++;
+               }
+               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++;
+               } else if (isupper(*dot)) {
+                       *dot = tolower(*dot);
+                       file_modified++;
+               }
+               dot_right();
+               end_cmd_q();    // stop adding to q
+               break;
+               //----- The Cursor and Function Keys -----------------------------
+       case KEYCODE_HOME:      // Cursor Key Home
+               dot_begin();
+               break;
+               // The Fn keys could point to do_macro which could translate them
+#if 0
+       case KEYCODE_FUN1:      // Function Key F1
+       case KEYCODE_FUN2:      // Function Key F2
+       case KEYCODE_FUN3:      // Function Key F3
+       case KEYCODE_FUN4:      // Function Key F4
+       case KEYCODE_FUN5:      // Function Key F5
+       case KEYCODE_FUN6:      // Function Key F6
+       case KEYCODE_FUN7:      // Function Key F7
+       case KEYCODE_FUN8:      // Function Key F8
+       case KEYCODE_FUN9:      // Function Key F9
+       case KEYCODE_FUN10:     // Function Key F10
+       case KEYCODE_FUN11:     // Function Key F11
+       case KEYCODE_FUN12:     // Function Key F12
+               break;
+#endif
+       }
+
+ dc1:
+       // if text[] just became empty, add back an empty line
+       if (end == text) {
+               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
+       }
+#if ENABLE_FEATURE_VI_YANKMARK
+       check_context(c);       // update the current context
+#endif
+
+       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--;
+}
+
+/* NB!  the CRASHME code is unmaintained, and doesn't currently build */
+#if ENABLE_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;
+static const char chars[20] = "\t012345 abcdABCD-=.$";
+static const char *const words[20] = {
+       "this", "is", "a", "test",
+       "broadcast", "the", "emergency", "of",
+       "system", "quick", "brown", "fox",
+       "jumped", "over", "lazy", "dogs",
+       "back", "January", "Febuary", "March"
+};
+static const char *const 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",
+};
+static 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 (chars_to_parse > 0)
+               goto cd1;
+ cd0:
+       startrbi = rbi = 0;
+       sleeptime = 0;          // how long to pause between commands
+       memset(readbuffer, '\0', sizeof(readbuffer));
+       // 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(readbuffer, words[(int) lrand48() % 20]);
+                               strcat(readbuffer, " ");
+                               sleeptime = 0;  // how fast to type
+                       } else if (thing == 2) {        // insert lines
+                               strcat(readbuffer, lines[(int) lrand48() % 20]);
+                               sleeptime = 0;  // how fast to type
+                       } else {        // insert multi-lines
+                               strcat(readbuffer, multilines[(int) lrand48() % 20]);
+                               sleeptime = 0;  // how fast to type
+                       }
+               }
+               strcat(readbuffer, "\033");
+       }
+       chars_to_parse = strlen(readbuffer);
+ cd1:
+       totalcmds++;
+       if (sleeptime > 0)
+               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[80];
+
+       msg[0] = '\0';
+       if (end < text) {
+               strcat(msg, "end<text ");
+       }
+       if (end > textend) {
+               strcat(msg, "end>textend ");
+       }
+       if (dot < text) {
+               strcat(msg, "dot<text ");
+       }
+       if (dot > end) {
+               strcat(msg, "dot>end ");
+       }
+       if (screenbegin < text) {
+               strcat(msg, "screenbegin<text ");
+       }
+       if (screenbegin > end - 1) {
+               strcat(msg, "screenbegin>end-1 ");
+       }
+
+       if (msg[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 (safe_read(STDIN_FILENO, d, 1) > 0) {
+                       if (d[0] == '\n' || d[0] == '\r')
+                               break;
+               }
+       }
+       tim = time(NULL);
+       if (tim >= (oldtim + 3)) {
+               sprintf(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;
+       }
+}
+#endif
diff --git a/examples/bootfloppy/bootfloppy.txt b/examples/bootfloppy/bootfloppy.txt
new file mode 100644 (file)
index 0000000..9e2cbe2
--- /dev/null
@@ -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 CONFIG_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 (file)
index 0000000..399d326
--- /dev/null
@@ -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 (file)
index 0000000..ef14ca2
--- /dev/null
@@ -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 (executable)
index 0000000..4f29b92
--- /dev/null
@@ -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 (file)
index 0000000..eb3e979
--- /dev/null
@@ -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 (file)
index 0000000..8a7c77d
--- /dev/null
@@ -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 (executable)
index 0000000..8e9512f
--- /dev/null
@@ -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 $((128 + i))
+done
+ln -s vcsa0 vcsa
diff --git a/examples/bootfloppy/mkrootfs.sh b/examples/bootfloppy/mkrootfs.sh
new file mode 100755 (executable)
index 0000000..5cdff21
--- /dev/null
@@ -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 CONFIG_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 (executable)
index 0000000..e254173
--- /dev/null
@@ -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 (file)
index 0000000..0d80908
--- /dev/null
@@ -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 (file)
index 0000000..fa2677c
--- /dev/null
@@ -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 (file)
index 0000000..494eed9
--- /dev/null
@@ -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 <andersen@codepoet.org>
+
+%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 CONFIG_PREFIX=$RPM_BUILD_ROOT install
+
+%Clean
+rm -rf $RPM_BUILD_ROOT
+
+%Files
+%defattr(-,root,root)
+/
diff --git a/examples/depmod b/examples/depmod
new file mode 100644 (file)
index 0000000..d8c4cc5
--- /dev/null
@@ -0,0 +1,57 @@
+#!/bin/sh
+#
+# Simple depmod, use to generate modprobe.conf
+#
+# Copyright (C) 2008 by Vladimir Dronnikov <dronnikov@gmail.com>
+#
+# Licensed under GPLv2
+#
+
+local BASE="${1:-/usr/lib/modules}"
+
+find "$BASE" -name '*.ko.gz' | while read I ; do
+       N=`basename "$I" '.ko.gz'`
+       echo -n "@$N"
+       zcat "$I" | strings | grep '^depends=' | sed -e 's/^depends=$//' -e 's/^depends=/,/' -e 's/,/ @/g'
+done | awk '
+{
+       # modules which has no dependencies are resolved
+       if ( NF == 1 ) { res[$1] = ""; next }
+       # others have to be resolved based on those which already resolved
+       i = $1; $1 = ""; deps[i] = $0; ++ndeps
+}
+END {
+       # resolve implicit dependencies
+       while ( ndeps ) for (mod in deps) {
+               if ( index(deps[mod], "@") > 0 ) {
+                       $0 = deps[mod]
+                       for ( i=1; i<=NF; ++i ) {
+                               if ( substr($i,1,1) == "@" ) {
+                                       if ( $i in res ) {
+                                               $i = res[$i] " " substr($i,2)
+                                       }
+                               }
+                       }
+                       deps[mod] = $0
+               } else {
+                       res[mod] = deps[mod]
+                       delete deps[mod]
+                       --ndeps
+               }
+       }
+
+       # output dependencies in modules.dep format
+       for ( mod in res ) {
+               $0 = res[mod]
+               s = ""
+               delete a
+               for ( i=1; i<=NF; ++i ) {
+                       if ( ! ($i in a) ) {
+                               a[$i] = $i
+                               s = " ," $i s
+                       }
+               }
+               print "," substr(mod,2) ":" s
+       }
+}
+' | sort | sed -r -e "s!,([^,: ]*)!/usr/lib/modules/\\1.ko.gz!g"
diff --git a/examples/depmod.pl b/examples/depmod.pl
new file mode 100755 (executable)
index 0000000..8c6548d
--- /dev/null
@@ -0,0 +1,350 @@
+#!/usr/bin/perl -w
+# vi: set sw=4 ts=4:
+# Copyright (c) 2001 David Schleef <ds@schleef.org>
+# Copyright (c) 2001 Erik Andersen <andersen@codepoet.org>
+# Copyright (c) 2001 Stuart Hughes <seh@zee2.com>
+# Copyright (c) 2002 Steven J. Hill <shill@broadcom.com>
+# Copyright (c) 2006 Freescale Semiconductor, Inc <stuarth@freescale.com>
+#
+# History:
+# March 2006: Stuart Hughes <stuarth@freescale.com>.
+#             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 qw(:config no_auto_abbrev no_ignore_case);
+use File::Find;
+use strict;
+
+# Set up some default values
+my $kdir="";
+my $basedir="";
+my $kernel="";
+my $kernelsyms="";
+my $symprefix="";
+my $all=0;
+my $quick=0;
+my $errsyms=0;
+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 = <<TXT;
+$0 -b basedir { -k <vmlinux> | -F <System.map> } [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 <basedir>/modules.dep
+   -v --verbose       : Print out lots of debugging stuff
+   -P --symbol-prefix : Symbol prefix
+   -a --all           : Probe all modules (default/only thing supported)
+   -e --errsyms       : Report any symbols not supplied by modules/kernel
+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,
+       "symbol-prefix|P=s" => \$symprefix,
+       "all|a"             => \$all,
+       # unsupported options
+       "quick|A"           => \$quick,
+       # ignored options (for historical usage)
+       "quiet|q",
+       "root|r",
+       "unresolved-error|u"
+);
+
+die $usage if $help;
+die $usage unless $basedir && ( $kernel || $kernelsyms );
+die "can't use both -k and -F\n\n$usage" if $kernel && $kernelsyms;
+die "sorry, -A/--quick is not supported" if $quick;
+die "--errsyms requires --kernelsyms" if $errsyms && !$kernelsyms;
+
+# Strip any trailing or multiple slashes from basedir
+$basedir =~ s-/+$--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";
+        }
+    }
+}
+
+# build a complete dependency list for each module and make sure it
+# is kept in order proper order
+my $mod2 = {};
+sub maybe_unshift
+{
+       my ($array, $ele) = @_;
+       # chop off the leading path /lib/modules/<kver>/ as modprobe
+       # will handle relative paths just fine
+       $ele =~ s:^/lib/modules/[^/]*/::;
+       foreach (@{$array}) {
+               if ($_ eq $ele) {
+                       return;
+               }
+       }
+       unshift (@{$array}, $ele);
+}
+sub add_mod_deps
+{
+       my ($depth, $mod, $mod2, $module, $this_module) = @_;
+
+       $depth .= " ";
+       warn "${depth}loading deps of module: $this_module\n" if $verbose;
+
+       foreach my $md (keys %{$mod->{$this_module}}) {
+               add_mod_deps ($depth, $mod, $mod2, $module, $md);
+               warn "${depth} outputting $md\n" if $verbose;
+               maybe_unshift (\@{$$mod2->{$module}}, $md);
+       }
+
+       if (!%{$mod->{$this_module}}) {
+               warn "${depth} no deps\n" if $verbose;
+       }
+}
+foreach my $module (keys %$mod) {
+       warn "filling out module: $module\n" if $verbose;
+       @{$mod2->{$module}} = ();
+       add_mod_deps ("", $mod, \$mod2, $module, $module);
+}
+
+# figure out where the output should go
+if ($stdout == 0) {
+       warn "writing $basedir/modules.dep\n" if $verbose;
+    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 {
+           my $shortmod = $module;
+           $shortmod =~ s:^/lib/modules/[^/]*/::;
+           print "$shortmod:";
+           my @sorted = @{$mod2->{$module}};
+           printf " " if @sorted;
+           print join(" ",@sorted);
+           print "\n";
+    }
+}
+
+
+sub build_ref_tables
+{
+    my ($name, $sym_ar, $exp, $dep) = @_;
+
+       my $ksymtab = grep m/ ${symprefix}__ksymtab/, @$sym_ar;
+
+    # gather the exported symbols
+       if($ksymtab){
+        # explicitly exported
+        foreach ( @$sym_ar ) {
+            / ${symprefix}__ksymtab_(.*)$/ and do {
+                my $sym = ${symprefix} . $1;
+                warn "sym = $sym\n" if $verbose;
+                $exp->{$sym} = $name;
+            };
+        }
+       } else {
+        # exporting all symbols
+        foreach ( @$sym_ar ) {
+            / [ABCDGRSTW] (.*)$/ and do {
+                warn "syma = $1\n" if $verbose;
+                $exp->{$1} = $name;
+            };
+        }
+       }
+
+    # this takes makes sure modules with no dependencies get listed
+    push @{$dep->{$name}}, $symprefix . 'printk' unless $name eq 'vmlinux';
+
+    # gather the unresolved symbols
+    foreach ( @$sym_ar ) {
+        !/ ${symprefix}__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 <ds@schleef.org> 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 <ds@schleef.org>
+ Copyright (c) 2001 Erik Andersen <andersen@codepoet.org>
+ Copyright (c) 2001 Stuart Hughes <seh@zee2.com>
+ Copyright (c) 2002 Steven J. Hill <shill@broadcom.com>
+ Copyright (c) 2006 Freescale Semiconductor, Inc <stuarth@freescale.com>
+
+This program is free software; you can redistribute it and/or modify it
+under the same terms as Perl itself.
+
+=head1 AUTHOR
+
+David Schleef <ds@schleef.org>
+
+=cut
diff --git a/examples/devfsd.conf b/examples/devfsd.conf
new file mode 100644 (file)
index 0000000..10f1c87
--- /dev/null
@@ -0,0 +1,133 @@
+# Sample /etc/devfsd.conf configuration file.
+# Richard Gooch  <rgooch@atnf.csiro.au>                17-FEB-2002
+#
+# adapted for busybox devfsd implementation by Tito <farmatito@tiscali.it>
+#
+# 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 (file)
index 0000000..8af622b
--- /dev/null
@@ -0,0 +1 @@
+thebox 192.168.1.5
diff --git a/examples/inetd.conf b/examples/inetd.conf
new file mode 100644 (file)
index 0000000..ca7e3d8
--- /dev/null
@@ -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 <pid of inetd>
+# inetd will re-read this file whenever it gets that signal.
+# <service_name> <sock_type> <proto> <flags> <user> <server_path> <args>
+#
+#: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 (file)
index 0000000..5f2af87
--- /dev/null
@@ -0,0 +1,90 @@
+# /etc/inittab init(8) configuration for BusyBox
+#
+# Copyright (C) 1999-2004 by Erik Andersen <andersen@codepoet.org>
+#
+#
+# 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: <id>:<runlevels>:<action>:<process>
+#
+# <id>: 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.
+#
+# <runlevels>: The runlevels field is completely ignored.
+#
+# <action>: 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.
+#
+# <process>: 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 (executable)
index 0000000..1259b84
--- /dev/null
@@ -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 (executable)
index 0000000..2a95d8b
--- /dev/null
@@ -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 (executable)
index 0000000..b221bcf
--- /dev/null
@@ -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 (executable)
index 0000000..f4d08e6
--- /dev/null
@@ -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 (executable)
index 0000000..842bafe
--- /dev/null
@@ -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 (file)
index 0000000..9b717ac
--- /dev/null
@@ -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 (file)
index 0000000..98ebc15
--- /dev/null
@@ -0,0 +1,40 @@
+#!/bin/sh
+
+# udhcpc script edited by Tim Riker <Tim@Rikers.org>
+
+[ -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 (file)
index 0000000..8c9a968
--- /dev/null
@@ -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      # <--- useful 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 timesrv
+#opt namesrv
+#opt dns
+#opt logsrv
+#opt cookiesrv
+#opt lprsrv
+#opt bootsize
+#opt domain
+#opt swapsrv
+#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 (file)
index 0000000..37104e9
--- /dev/null
@@ -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            <Print control file info>"
+echo "       undeb -l package.deb            <List contents of deb package>"
+echo "       undeb -x package.deb /foo/boo   <Extract deb package to this directory,"
+echo "                                        put . for current directory>"
+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 (file)
index 0000000..7fd3676
--- /dev/null
@@ -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            <List contents of rpm package>"
+echo "       unrpm -x package.rpm /foo/boo   <Extract rpm package to this directory,"
+echo "                                        put . for current directory>"
+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 (file)
index 0000000..988e542
--- /dev/null
@@ -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 (file)
index 0000000..d69a238
--- /dev/null
@@ -0,0 +1,247 @@
+#
+# 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 (-mmin 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' option (-xdev)"
+       default y
+       depends on FIND
+       help
+         This option allows find to restrict searches to a single filesystem.
+
+config FEATURE_FIND_MAXDEPTH
+       bool "Enable -maxdepth N option"
+       default y
+       depends on FIND
+       help
+         This option enables -maxdepth N option.
+
+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 FEATURE_FIND_USER
+       bool "Enable username/uid matching (-user option)"
+       default y
+       depends on FIND
+       help
+         Support the 'find -user' option for searching by username or uid.
+
+config FEATURE_FIND_GROUP
+       bool "Enable group/gid matching (-group option)"
+       default y
+       depends on FIND
+       help
+         Support the 'find -group' option for searching by group name or gid.
+
+config FEATURE_FIND_NOT
+       bool "Enable the 'not' (!) operator"
+       default y
+       depends on FIND
+       help
+         Support the '!' operator to invert the test results.
+         If 'Enable full-blown desktop' is enabled, then will also support
+         the non-POSIX notation '-not'.
+
+config FEATURE_FIND_DEPTH
+       bool "Enable the -depth option"
+       default y
+       depends on FIND
+       help
+         Process each directory's contents before the directory itself.
+
+config FEATURE_FIND_PAREN
+       bool "Enable parens in options"
+       default y
+       depends on FIND
+       help
+         Enable usage of parens '(' to specify logical order of arguments.
+
+config FEATURE_FIND_SIZE
+       bool "Enable -size option allowing matching for file size"
+       default y
+       depends on FIND
+       help
+         Support the 'find -size' option for searching by file size.
+
+config FEATURE_FIND_PRUNE
+       bool "Enable -prune option allowing to exclude subdirectories"
+       default y
+       depends on FIND
+       help
+         If the file is a directory, dont descend into it. Useful for
+         exclusion .svn and CVS directories.
+
+config FEATURE_FIND_DELETE
+       bool "Enable -delete option allowing to delete files"
+       default n
+       depends on FIND && FEATURE_FIND_DEPTH
+       help
+         Support the 'find -delete' option for deleting files and directories.
+         WARNING: This option can do much harm if used wrong. Busybox will not
+         try to protect the user from doing stupid things. Use with care.
+
+config FEATURE_FIND_PATH
+       bool "Enable -path option allowing to match pathname patterns"
+       default y
+       depends on FIND
+       help
+         The -path option matches whole pathname instead of just filename.
+
+config FEATURE_FIND_REGEX
+       bool "Enable -regex: match pathname to regex"
+       default y
+       depends on FIND
+       help
+         The -regex option matches whole pathname against regular expression.
+
+config FEATURE_FIND_CONTEXT
+       bool "Enable -context option for matching security context"
+       default n
+       depends on FIND && SELINUX
+       help
+         Support the 'find -context' option for matching security context.
+
+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 (file)
index 0000000..7b504ba
--- /dev/null
@@ -0,0 +1,10 @@
+# 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_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 (file)
index 0000000..df632f2
--- /dev/null
@@ -0,0 +1,910 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Mini find implementation for busybox
+ *
+ * Copyright (C) 1999-2004 by Erik Andersen <andersen@codepoet.org>
+ *
+ * Reworked by David Douthitt <n9ubh@callsign.net> and
+ *  Matt Kraai <kraai@alumni.carnegiemellon.edu>.
+ *
+ * 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)
+ */
+
+/* Testing script
+ * ./busybox find "$@" | tee /tmp/bb_find
+ * echo ==================
+ * /path/to/gnu/find "$@" | tee /tmp/std_find
+ * echo ==================
+ * diff -u /tmp/std_find /tmp/bb_find && echo Identical
+ */
+
+#include <fnmatch.h>
+#include "libbb.h"
+#if ENABLE_FEATURE_FIND_REGEX
+#include "xregex.h"
+#endif
+
+/* This is a NOEXEC applet. Be very careful! */
+
+
+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;
+#if ENABLE_FEATURE_FIND_NOT
+       bool invert;
+#endif
+} action;
+#define ACTS(name, arg...) typedef struct { action a; arg; } action_##name;
+#define ACTF(name)         static int func_##name(const char *fileName UNUSED_PARAM, \
+                                                  struct stat *statbuf UNUSED_PARAM, \
+                                                  action_##name* ap UNUSED_PARAM)
+                         ACTS(print)
+                         ACTS(name,  const char *pattern; bool iname;)
+USE_FEATURE_FIND_PATH(   ACTS(path,  const char *pattern;))
+USE_FEATURE_FIND_REGEX(  ACTS(regex, regex_t compiled_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; mode_t perm_mask;))
+USE_FEATURE_FIND_MTIME(  ACTS(mtime, char mtime_char; unsigned mtime_days;))
+USE_FEATURE_FIND_MMIN(   ACTS(mmin,  char mmin_char; unsigned 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_USER(   ACTS(user,  uid_t uid;))
+USE_FEATURE_FIND_SIZE(   ACTS(size,  char size_char; off_t size;))
+USE_FEATURE_FIND_CONTEXT(ACTS(context, security_context_t context;))
+USE_FEATURE_FIND_PAREN(  ACTS(paren, action ***subexpr;))
+USE_FEATURE_FIND_PRUNE(  ACTS(prune))
+USE_FEATURE_FIND_DELETE( ACTS(delete))
+USE_FEATURE_FIND_EXEC(   ACTS(exec,  char **exec_argv; unsigned *subst_count; int exec_argc;))
+USE_FEATURE_FIND_GROUP(  ACTS(group, gid_t gid;))
+
+static action ***actions;
+static bool need_print = 1;
+static int recurse_flags = ACTION_RECURSE;
+
+#if ENABLE_FEATURE_FIND_EXEC
+static unsigned count_subst(const char *str)
+{
+       unsigned count = 0;
+       while ((str = strstr(str, "{}")) != NULL) {
+               count++;
+               str++;
+       }
+       return count;
+}
+
+
+static char* subst(const char *src, unsigned count, const char* filename)
+{
+       char *buf, *dst, *end;
+       size_t 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;
+}
+#endif
+
+/* Return values of ACTFs ('action functions') are a bit mask:
+ * bit 1=1: prune (use SKIP constant for setting it)
+ * bit 0=1: matched successfully (TRUE)
+ */
+
+static int exec_actions(action ***appp, const char *fileName, struct stat *statbuf)
+{
+       int cur_group;
+       int cur_action;
+       int rc = 0;
+       action **app, *ap;
+
+       /* "action group" is a set of actions ANDed together.
+        * groups are ORed together.
+        * We simply evaluate each group until we find one in which all actions
+        * succeed. */
+
+       /* -prune is special: if it is encountered, then we won't
+        * descend into current directory. It doesn't matter whether
+        * action group (in which -prune sits) will succeed or not:
+        * find * -prune -name 'f*' -o -name 'm*' -- prunes every dir
+        * find * -name 'f*' -o -prune -name 'm*' -- prunes all dirs
+        *     not starting with 'f' */
+
+       /* We invert TRUE bit (bit 0). Now 1 there means 'failure'.
+        * and bitwise OR in "rc |= TRUE ^ ap->f()" will:
+        * (1) make SKIP (-prune) bit stick; and (2) detect 'failure'.
+        * On return, bit is restored.  */
+
+       cur_group = -1;
+       while ((app = appp[++cur_group])) {
+               rc &= ~TRUE; /* 'success' so far, clear TRUE bit */
+               cur_action = -1;
+               while (1) {
+                       ap = app[++cur_action];
+                       if (!ap) /* all actions in group were successful */
+                               return rc ^ TRUE; /* restore TRUE bit */
+                       rc |= TRUE ^ ap->f(fileName, statbuf, ap);
+#if ENABLE_FEATURE_FIND_NOT
+                       if (ap->invert) rc ^= TRUE;
+#endif
+                       if (rc & TRUE) /* current group failed, try next */
+                               break;
+               }
+       }
+       return rc ^ TRUE; /* restore TRUE bit */
+}
+
+
+ACTF(name)
+{
+       const char *tmp = bb_basename(fileName);
+       if (tmp != fileName && !*tmp) { /* "foo/bar/". Oh no... go back to 'b' */
+               tmp--;
+               while (tmp != fileName && *--tmp != '/')
+                       continue;
+               if (*tmp == '/')
+                       tmp++;
+       }
+       return fnmatch(ap->pattern, tmp, FNM_PERIOD | (ap->iname ? FNM_CASEFOLD : 0)) == 0;
+}
+
+#if ENABLE_FEATURE_FIND_PATH
+ACTF(path)
+{
+       return fnmatch(ap->pattern, fileName, 0) == 0;
+}
+#endif
+#if ENABLE_FEATURE_FIND_REGEX
+ACTF(regex)
+{
+       regmatch_t match;
+       if (regexec(&ap->compiled_pattern, fileName, 1, &match, 0 /*eflags*/))
+               return 0; /* no match */
+       if (match.rm_so)
+               return 0; /* match doesn't start at pos 0 */
+       if (fileName[match.rm_eo])
+               return 0; /* match doesn't end exactly at end of pathname */
+       return 1;
+}
+#endif
+#if ENABLE_FEATURE_FIND_TYPE
+ACTF(type)
+{
+       return ((statbuf->st_mode & S_IFMT) == ap->type_mask);
+}
+#endif
+#if ENABLE_FEATURE_FIND_PERM
+ACTF(perm)
+{
+       /* -perm +mode: at least one of perm_mask bits are set */
+       if (ap->perm_char == '+')
+               return (statbuf->st_mode & ap->perm_mask) != 0;
+       /* -perm -mode: all of perm_mask are set */
+       if (ap->perm_char == '-')
+               return (statbuf->st_mode & ap->perm_mask) == ap->perm_mask;
+       /* -perm mode: file mode must match perm_mask */
+       return (statbuf->st_mode & 07777) == ap->perm_mask;
+}
+#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;
+       if (ap->mtime_char == '+')
+               return file_age >= mtime_secs + 24*60*60;
+       if (ap->mtime_char == '-')
+               return file_age < mtime_secs;
+       /* just numeric mtime */
+       return file_age >= mtime_secs && file_age < (mtime_secs + 24*60*60);
+}
+#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;
+       if (ap->mmin_char == '+')
+               return file_age >= mmin_secs + 60;
+       if (ap->mmin_char == '-')
+               return file_age < mmin_secs;
+       /* just numeric mmin */
+       return file_age >= mmin_secs && file_age < (mmin_secs + 60);
+}
+#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 */
+
+       rc = spawn_and_wait(argv);
+       if (rc < 0)
+               bb_simple_perror_msg(argv[0]);
+
+       i = 0;
+       while (argv[i])
+               free(argv[i++]);
+       return rc == 0; /* return 1 if exitcode 0 */
+}
+#endif
+#if ENABLE_FEATURE_FIND_USER
+ACTF(user)
+{
+       return (statbuf->st_uid == ap->uid);
+}
+#endif
+#if ENABLE_FEATURE_FIND_GROUP
+ACTF(group)
+{
+       return (statbuf->st_gid == ap->gid);
+}
+#endif
+#if ENABLE_FEATURE_FIND_PRINT0
+ACTF(print0)
+{
+       printf("%s%c", fileName, '\0');
+       return TRUE;
+}
+#endif
+ACTF(print)
+{
+       puts(fileName);
+       return TRUE;
+}
+#if ENABLE_FEATURE_FIND_PAREN
+ACTF(paren)
+{
+       return exec_actions(ap->subexpr, fileName, statbuf);
+}
+#endif
+#if ENABLE_FEATURE_FIND_SIZE
+ACTF(size)
+{
+       if (ap->size_char == '+')
+               return statbuf->st_size > ap->size;
+       if (ap->size_char == '-')
+               return statbuf->st_size < ap->size;
+       return statbuf->st_size == ap->size;
+}
+#endif
+#if ENABLE_FEATURE_FIND_PRUNE
+/*
+ * -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 + TRUE;
+}
+#endif
+#if ENABLE_FEATURE_FIND_DELETE
+ACTF(delete)
+{
+       int rc;
+       if (S_ISDIR(statbuf->st_mode)) {
+               rc = rmdir(fileName);
+       } else {
+               rc = unlink(fileName);
+       }
+       if (rc < 0)
+               bb_simple_perror_msg(fileName);
+       return TRUE;
+}
+#endif
+#if ENABLE_FEATURE_FIND_CONTEXT
+ACTF(context)
+{
+       security_context_t con;
+       int rc;
+
+       if (recurse_flags & ACTION_FOLLOWLINKS) {
+               rc = getfilecon(fileName, &con);
+       } else {
+               rc = lgetfilecon(fileName, &con);
+       }
+       if (rc < 0)
+               return FALSE;
+       rc = strcmp(ap->context, con);
+       freecon(con);
+       return rc == 0;
+}
+#endif
+
+
+static int FAST_FUNC fileAction(const char *fileName,
+               struct stat *statbuf,
+               void *userData SKIP_FEATURE_FIND_MAXDEPTH(UNUSED_PARAM),
+               int depth SKIP_FEATURE_FIND_MAXDEPTH(UNUSED_PARAM))
+{
+       int i;
+#if ENABLE_FEATURE_FIND_MAXDEPTH
+#define minmaxdepth ((int*)userData)
+
+       if (depth < minmaxdepth[0]) return TRUE;
+       if (depth > minmaxdepth[1]) return SKIP;
+#undef minmaxdepth
+#endif
+
+#if ENABLE_FEATURE_FIND_XDEV
+       if (S_ISDIR(statbuf->st_mode) && xdev_count) {
+               for (i = 0; i < xdev_count; i++) {
+                       if (xdev_dev[i] == statbuf->st_dev)
+                               break;
+               }
+               if (i == xdev_count)
+                       return SKIP;
+       }
+#endif
+       i = exec_actions(actions, fileName, statbuf);
+       /* Had no explicit -print[0] or -exec? then print */
+       if ((i & TRUE) && need_print)
+               puts(fileName);
+       /* Cannot return 0: our caller, recursive_action(),
+        * will perror() and skip dirs (if called on dir) */
+       return (i & SKIP) ? SKIP : TRUE;
+}
+
+
+#if ENABLE_FEATURE_FIND_TYPE
+static int find_type(const char *type)
+{
+       int mask = 0;
+
+       if (*type == 'b')
+               mask = S_IFBLK;
+       else if (*type == 'c')
+               mask = S_IFCHR;
+       else if (*type == 'd')
+               mask = S_IFDIR;
+       else if (*type == 'p')
+               mask = S_IFIFO;
+       else if (*type == 'f')
+               mask = S_IFREG;
+       else if (*type == 'l')
+               mask = S_IFLNK;
+       else if (*type == 's')
+               mask = S_IFSOCK;
+
+       if (mask == 0 || *(type + 1) != '\0')
+               bb_error_msg_and_die(bb_msg_invalid_arg, type, "-type");
+
+       return mask;
+}
+#endif
+
+#if ENABLE_FEATURE_FIND_PERM \
+ || ENABLE_FEATURE_FIND_MTIME || ENABLE_FEATURE_FIND_MMIN \
+ || ENABLE_FEATURE_FIND_SIZE
+static const char* plus_minus_num(const char* str)
+{
+       if (*str == '-' || *str == '+')
+               str++;
+       return str;
+}
+#endif
+
+static action*** parse_params(char **argv)
+{
+       enum {
+                                PARM_a         ,
+                                PARM_o         ,
+       USE_FEATURE_FIND_NOT(    PARM_char_not  ,)
+#if ENABLE_DESKTOP
+                                PARM_and       ,
+                                PARM_or        ,
+       USE_FEATURE_FIND_NOT(    PARM_not       ,)
+#endif
+                                PARM_print     ,
+       USE_FEATURE_FIND_PRINT0( PARM_print0    ,)
+       USE_FEATURE_FIND_DEPTH(  PARM_depth     ,)
+       USE_FEATURE_FIND_PRUNE(  PARM_prune     ,)
+       USE_FEATURE_FIND_DELETE( PARM_delete    ,)
+       USE_FEATURE_FIND_EXEC(   PARM_exec      ,)
+       USE_FEATURE_FIND_PAREN(  PARM_char_brace,)
+       /* All options starting from here require argument */
+                                PARM_name      ,
+                                PARM_iname     ,
+       USE_FEATURE_FIND_PATH(   PARM_path      ,)
+       USE_FEATURE_FIND_REGEX(  PARM_regex     ,)
+       USE_FEATURE_FIND_TYPE(   PARM_type      ,)
+       USE_FEATURE_FIND_PERM(   PARM_perm      ,)
+       USE_FEATURE_FIND_MTIME(  PARM_mtime     ,)
+       USE_FEATURE_FIND_MMIN(   PARM_mmin      ,)
+       USE_FEATURE_FIND_NEWER(  PARM_newer     ,)
+       USE_FEATURE_FIND_INUM(   PARM_inum      ,)
+       USE_FEATURE_FIND_USER(   PARM_user      ,)
+       USE_FEATURE_FIND_GROUP(  PARM_group     ,)
+       USE_FEATURE_FIND_SIZE(   PARM_size      ,)
+       USE_FEATURE_FIND_CONTEXT(PARM_context   ,)
+       };
+
+       static const char params[] ALIGN1 =
+                                "-a\0"
+                                "-o\0"
+       USE_FEATURE_FIND_NOT(    "!\0"       )
+#if ENABLE_DESKTOP
+                                "-and\0"
+                                "-or\0"
+       USE_FEATURE_FIND_NOT(    "-not\0"    )
+#endif
+                                "-print\0"
+       USE_FEATURE_FIND_PRINT0( "-print0\0" )
+       USE_FEATURE_FIND_DEPTH(  "-depth\0"  )
+       USE_FEATURE_FIND_PRUNE(  "-prune\0"  )
+       USE_FEATURE_FIND_DELETE( "-delete\0" )
+       USE_FEATURE_FIND_EXEC(   "-exec\0"   )
+       USE_FEATURE_FIND_PAREN(  "(\0"       )
+       /* All options starting from here require argument */
+                                "-name\0"
+                                "-iname\0"
+       USE_FEATURE_FIND_PATH(   "-path\0"   )
+       USE_FEATURE_FIND_REGEX(  "-regex\0"  )
+       USE_FEATURE_FIND_TYPE(   "-type\0"   )
+       USE_FEATURE_FIND_PERM(   "-perm\0"   )
+       USE_FEATURE_FIND_MTIME(  "-mtime\0"  )
+       USE_FEATURE_FIND_MMIN(   "-mmin\0"   )
+       USE_FEATURE_FIND_NEWER(  "-newer\0"  )
+       USE_FEATURE_FIND_INUM(   "-inum\0"   )
+       USE_FEATURE_FIND_USER(   "-user\0"   )
+       USE_FEATURE_FIND_GROUP(  "-group\0"  )
+       USE_FEATURE_FIND_SIZE(   "-size\0"   )
+       USE_FEATURE_FIND_CONTEXT("-context\0")
+                                ;
+
+       action*** appp;
+       unsigned cur_group = 0;
+       unsigned cur_action = 0;
+       USE_FEATURE_FIND_NOT( bool invert_flag = 0; )
+
+       /* This is the only place in busybox where we use nested function.
+        * So far more standard alternatives were bigger. */
+       /* Suppress a warning "func without a prototype" */
+       auto action* alloc_action(int sizeof_struct, action_fp f);
+       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;
+               USE_FEATURE_FIND_NOT( ap->invert = invert_flag; )
+               USE_FEATURE_FIND_NOT( invert_flag = 0; )
+               return ap;
+       }
+
+#define ALLOC_ACTION(name) (action_##name*)alloc_action(sizeof(action_##name), (action_fp) func_##name)
+
+       appp = xzalloc(2 * sizeof(appp[0])); /* 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) {
+               const char *arg = argv[0];
+               int parm = index_in_strings(params, arg);
+               const char *arg1 = argv[1];
+
+               if (parm >= PARM_name) {
+                       /* All options starting from -name require argument */
+                       if (!arg1)
+                               bb_error_msg_and_die(bb_msg_requires_arg, arg);
+                       argv++;
+               }
+
+               /* We can use big switch() here, but on i386
+                * it doesn't give smaller code. Other arches? */
+
+       /* --- Operators --- */
+               if (parm == PARM_a USE_DESKTOP(|| parm == PARM_and)) {
+                       /* no further special handling required */
+               }
+               else if (parm == PARM_o USE_DESKTOP(|| parm == PARM_or)) {
+                       /* start new OR group */
+                       cur_group++;
+                       appp = xrealloc(appp, (cur_group+2) * sizeof(*appp));
+                       /*appp[cur_group] = NULL; - already NULL */
+                       appp[cur_group+1] = NULL;
+                       cur_action = 0;
+               }
+#if ENABLE_FEATURE_FIND_NOT
+               else if (parm == PARM_char_not USE_DESKTOP(|| parm == PARM_not)) {
+                       /* also handles "find ! ! -name 'foo*'" */
+                       invert_flag ^= 1;
+               }
+#endif
+
+       /* --- Tests and actions --- */
+               else if (parm == PARM_print) {
+                       need_print = 0;
+                       /* GNU find ignores '!' here: "find ! -print" */
+                       USE_FEATURE_FIND_NOT( invert_flag = 0; )
+                       (void) ALLOC_ACTION(print);
+               }
+#if ENABLE_FEATURE_FIND_PRINT0
+               else if (parm == PARM_print0) {
+                       need_print = 0;
+                       USE_FEATURE_FIND_NOT( invert_flag = 0; )
+                       (void) ALLOC_ACTION(print0);
+               }
+#endif
+#if ENABLE_FEATURE_FIND_DEPTH
+               else if (parm == PARM_depth) {
+                       recurse_flags |= ACTION_DEPTHFIRST;
+               }
+#endif
+#if ENABLE_FEATURE_FIND_PRUNE
+               else if (parm == PARM_prune) {
+                       USE_FEATURE_FIND_NOT( invert_flag = 0; )
+                       (void) ALLOC_ACTION(prune);
+               }
+#endif
+#if ENABLE_FEATURE_FIND_DELETE
+               else if (parm == PARM_delete) {
+                       need_print = 0;
+                       recurse_flags |= ACTION_DEPTHFIRST;
+                       (void) ALLOC_ACTION(delete);
+               }
+#endif
+#if ENABLE_FEATURE_FIND_EXEC
+               else if (parm == PARM_exec) {
+                       int i;
+                       action_exec *ap;
+                       need_print = 0;
+                       USE_FEATURE_FIND_NOT( invert_flag = 0; )
+                       ap = ALLOC_ACTION(exec);
+                       ap->exec_argv = ++argv; /* first arg after -exec */
+                       ap->exec_argc = 0;
+                       while (1) {
+                               if (!*argv) /* did not see ';' until end */
+                                       bb_error_msg_and_die("-exec CMD must end by ';'");
+                               if (LONE_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_FEATURE_FIND_PAREN
+               else if (parm == PARM_char_brace) {
+                       action_paren *ap;
+                       char **endarg;
+                       unsigned nested = 1;
+
+                       endarg = argv;
+                       while (1) {
+                               if (!*++endarg)
+                                       bb_error_msg_and_die("unpaired '('");
+                               if (LONE_CHAR(*endarg, '('))
+                                       nested++;
+                               else if (LONE_CHAR(*endarg, ')') && !--nested) {
+                                       *endarg = NULL;
+                                       break;
+                               }
+                       }
+                       ap = ALLOC_ACTION(paren);
+                       ap->subexpr = parse_params(argv + 1);
+                       *endarg = (char*) ")"; /* restore NULLed parameter */
+                       argv = endarg;
+               }
+#endif
+               else if (parm == PARM_name || parm == PARM_iname) {
+                       action_name *ap;
+                       ap = ALLOC_ACTION(name);
+                       ap->pattern = arg1;
+                       ap->iname = (parm == PARM_iname);
+               }
+#if ENABLE_FEATURE_FIND_PATH
+               else if (parm == PARM_path) {
+                       action_path *ap;
+                       ap = ALLOC_ACTION(path);
+                       ap->pattern = arg1;
+               }
+#endif
+#if ENABLE_FEATURE_FIND_REGEX
+               else if (parm == PARM_regex) {
+                       action_regex *ap;
+                       ap = ALLOC_ACTION(regex);
+                       xregcomp(&ap->compiled_pattern, arg1, 0 /*cflags*/);
+               }
+#endif
+#if ENABLE_FEATURE_FIND_TYPE
+               else if (parm == PARM_type) {
+                       action_type *ap;
+                       ap = ALLOC_ACTION(type);
+                       ap->type_mask = find_type(arg1);
+               }
+#endif
+#if ENABLE_FEATURE_FIND_PERM
+/* -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 (parm == PARM_perm) {
+                       action_perm *ap;
+                       ap = ALLOC_ACTION(perm);
+                       ap->perm_char = arg1[0];
+                       arg1 = plus_minus_num(arg1);
+                       ap->perm_mask = 0;
+                       if (!bb_parse_mode(arg1, &ap->perm_mask))
+                               bb_error_msg_and_die("invalid mode: %s", arg1);
+               }
+#endif
+#if ENABLE_FEATURE_FIND_MTIME
+               else if (parm == PARM_mtime) {
+                       action_mtime *ap;
+                       ap = ALLOC_ACTION(mtime);
+                       ap->mtime_char = arg1[0];
+                       ap->mtime_days = xatoul(plus_minus_num(arg1));
+               }
+#endif
+#if ENABLE_FEATURE_FIND_MMIN
+               else if (parm == PARM_mmin) {
+                       action_mmin *ap;
+                       ap = ALLOC_ACTION(mmin);
+                       ap->mmin_char = arg1[0];
+                       ap->mmin_mins = xatoul(plus_minus_num(arg1));
+               }
+#endif
+#if ENABLE_FEATURE_FIND_NEWER
+               else if (parm == PARM_newer) {
+                       struct stat stat_newer;
+                       action_newer *ap;
+                       ap = ALLOC_ACTION(newer);
+                       xstat(arg1, &stat_newer);
+                       ap->newer_mtime = stat_newer.st_mtime;
+               }
+#endif
+#if ENABLE_FEATURE_FIND_INUM
+               else if (parm == PARM_inum) {
+                       action_inum *ap;
+                       ap = ALLOC_ACTION(inum);
+                       ap->inode_num = xatoul(arg1);
+               }
+#endif
+#if ENABLE_FEATURE_FIND_USER
+               else if (parm == PARM_user) {
+                       action_user *ap;
+                       ap = ALLOC_ACTION(user);
+                       ap->uid = bb_strtou(arg1, NULL, 10);
+                       if (errno)
+                               ap->uid = xuname2uid(arg1);
+               }
+#endif
+#if ENABLE_FEATURE_FIND_GROUP
+               else if (parm == PARM_group) {
+                       action_group *ap;
+                       ap = ALLOC_ACTION(group);
+                       ap->gid = bb_strtou(arg1, NULL, 10);
+                       if (errno)
+                               ap->gid = xgroup2gid(arg1);
+               }
+#endif
+#if ENABLE_FEATURE_FIND_SIZE
+               else if (parm == PARM_size) {
+/* -size n[bckw]: file uses n units of space
+ * b (default): units are 512-byte blocks
+ * c: 1 byte
+ * k: kilobytes
+ * w: 2-byte words
+ */
+#if ENABLE_LFS
+#define XATOU_SFX xatoull_sfx
+#else
+#define XATOU_SFX xatoul_sfx
+#endif
+                       static const struct suffix_mult find_suffixes[] = {
+                               { "c", 1 },
+                               { "w", 2 },
+                               { "", 512 },
+                               { "b", 512 },
+                               { "k", 1024 },
+                               { }
+                       };
+                       action_size *ap;
+                       ap = ALLOC_ACTION(size);
+                       ap->size_char = arg1[0];
+                       ap->size = XATOU_SFX(plus_minus_num(arg1), find_suffixes);
+               }
+#endif
+#if ENABLE_FEATURE_FIND_CONTEXT
+               else if (parm == PARM_context) {
+                       action_context *ap;
+                       ap = ALLOC_ACTION(context);
+                       ap->context = NULL;
+                       /* SELinux headers erroneously declare non-const parameter */
+                       if (selinux_raw_to_trans_context((char*)arg1, &ap->context))
+                               bb_simple_perror_msg(arg1);
+               }
+#endif
+               else {
+                       bb_error_msg("unrecognized: %s", arg);
+                       bb_show_usage();
+               }
+               argv++;
+       }
+       return appp;
+#undef ALLOC_ACTION
+}
+
+
+int find_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int find_main(int argc, char **argv)
+{
+       static const char options[] ALIGN1 =
+                         "-follow\0"
+USE_FEATURE_FIND_XDEV(    "-xdev\0"    )
+USE_FEATURE_FIND_MAXDEPTH("-mindepth\0""-maxdepth\0")
+                         ;
+       enum {
+                         OPT_FOLLOW,
+USE_FEATURE_FIND_XDEV(    OPT_XDEV    ,)
+USE_FEATURE_FIND_MAXDEPTH(OPT_MINDEPTH,)
+       };
+
+       char *arg;
+       char **argp;
+       int i, firstopt, status = EXIT_SUCCESS;
+#if ENABLE_FEATURE_FIND_MAXDEPTH
+       int minmaxdepth[2] = { 0, INT_MAX };
+#else
+#define minmaxdepth NULL
+#endif
+
+       for (firstopt = 1; firstopt < argc; firstopt++) {
+               if (argv[firstopt][0] == '-')
+                       break;
+               if (ENABLE_FEATURE_FIND_NOT && LONE_CHAR(argv[firstopt], '!'))
+                       break;
+#if ENABLE_FEATURE_FIND_PAREN
+               if (LONE_CHAR(argv[firstopt], '('))
+                       break;
+#endif
+       }
+       if (firstopt == 1) {
+               argv[0] = (char*)".";
+               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, -maxdepth
+ */
+       /* Process options, and replace then with -a */
+       /* (-a will be ignored by recursive parser later) */
+       argp = &argv[firstopt];
+       while ((arg = argp[0])) {
+               int opt = index_in_strings(options, arg);
+               if (opt == OPT_FOLLOW) {
+                       recurse_flags |= ACTION_FOLLOWLINKS;
+                       argp[0] = (char*)"-a";
+               }
+#if ENABLE_FEATURE_FIND_XDEV
+               if (opt == OPT_XDEV) {
+                       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] = (char*)"-a";
+               }
+#endif
+#if ENABLE_FEATURE_FIND_MAXDEPTH
+               if (opt == OPT_MINDEPTH || opt == OPT_MINDEPTH + 1) {
+                       if (!argp[1])
+                               bb_show_usage();
+                       minmaxdepth[opt - OPT_MINDEPTH] = xatoi_u(argp[1]);
+                       argp[0] = (char*)"-a";
+                       argp[1] = (char*)"-a";
+                       argp++;
+               }
+#endif
+               argp++;
+       }
+
+       actions = parse_params(&argv[firstopt]);
+
+       for (i = 1; i < firstopt; i++) {
+               if (!recursive_action(argv[i],
+                               recurse_flags,  /* flags */
+                               fileAction,     /* file action */
+                               fileAction,     /* dir action */
+#if ENABLE_FEATURE_FIND_MAXDEPTH
+                               minmaxdepth,    /* user data */
+#else
+                               NULL,           /* user data */
+#endif
+                               0))             /* depth */
+                       status = EXIT_FAILURE;
+       }
+       return status;
+}
diff --git a/findutils/grep.c b/findutils/grep.c
new file mode 100644 (file)
index 0000000..e23f9bc
--- /dev/null
@@ -0,0 +1,675 @@
+/* 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 <markw@codepoet.org>
+ *
+ * Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
+ */
+/* BB_AUDIT SUSv3 defects - unsupported option -x "match whole line only". */
+/* BB_AUDIT GNU defects - always acts as -a.  */
+/* http://www.opengroup.org/onlinepubs/007904975/utilities/grep.html */
+/*
+ * 2004,2006 (C) Vladimir Oleynik <dzo@simtreas.ru> -
+ * correction "-e pattern1 -e pattern2" logic and more optimizations.
+ * precompiled regex
+ */
+/*
+ * (C) 2006 Jac Goudsmit added -o option
+ */
+
+#include "libbb.h"
+#include "xregex.h"
+
+/* options */
+#define OPTSTR_GREP \
+       "lnqvscFiHhe:f:Lorm:" \
+       USE_FEATURE_GREP_CONTEXT("A:B:C:") \
+       USE_FEATURE_GREP_EGREP_ALIAS("E") \
+       USE_DESKTOP("w") \
+       USE_EXTRA_COMPAT("z") \
+       "aI"
+
+/* ignored: -a "assume all files to be text" */
+/* ignored: -I "assume binary files have no matches" */
+
+enum {
+       OPTBIT_l, /* list matched file names only */
+       OPTBIT_n, /* print line# */
+       OPTBIT_q, /* quiet - exit(EXIT_SUCCESS) of first match */
+       OPTBIT_v, /* invert the match, to select non-matching lines */
+       OPTBIT_s, /* suppress errors about file open errors */
+       OPTBIT_c, /* count matches per file (suppresses normal output) */
+       OPTBIT_F, /* literal match */
+       OPTBIT_i, /* case-insensitive */
+       OPTBIT_H, /* force filename display */
+       OPTBIT_h, /* inhibit filename display */
+       OPTBIT_e, /* -e PATTERN */
+       OPTBIT_f, /* -f FILE_WITH_PATTERNS */
+       OPTBIT_L, /* list unmatched file names only */
+       OPTBIT_o, /* show only matching parts of lines */
+       OPTBIT_r, /* recurse dirs */
+       OPTBIT_m, /* -m MAX_MATCHES */
+       USE_FEATURE_GREP_CONTEXT(    OPTBIT_A ,) /* -A NUM: after-match context */
+       USE_FEATURE_GREP_CONTEXT(    OPTBIT_B ,) /* -B NUM: before-match context */
+       USE_FEATURE_GREP_CONTEXT(    OPTBIT_C ,) /* -C NUM: -A and -B combined */
+       USE_FEATURE_GREP_EGREP_ALIAS(OPTBIT_E ,) /* extended regexp */
+       USE_DESKTOP(                 OPTBIT_w ,) /* whole word match */
+       USE_EXTRA_COMPAT(            OPTBIT_z ,) /* input is NUL terminated */
+       OPT_l = 1 << OPTBIT_l,
+       OPT_n = 1 << OPTBIT_n,
+       OPT_q = 1 << OPTBIT_q,
+       OPT_v = 1 << OPTBIT_v,
+       OPT_s = 1 << OPTBIT_s,
+       OPT_c = 1 << OPTBIT_c,
+       OPT_F = 1 << OPTBIT_F,
+       OPT_i = 1 << OPTBIT_i,
+       OPT_H = 1 << OPTBIT_H,
+       OPT_h = 1 << OPTBIT_h,
+       OPT_e = 1 << OPTBIT_e,
+       OPT_f = 1 << OPTBIT_f,
+       OPT_L = 1 << OPTBIT_L,
+       OPT_o = 1 << OPTBIT_o,
+       OPT_r = 1 << OPTBIT_r,
+       OPT_m = 1 << OPTBIT_m,
+       OPT_A = USE_FEATURE_GREP_CONTEXT(    (1 << OPTBIT_A)) + 0,
+       OPT_B = USE_FEATURE_GREP_CONTEXT(    (1 << OPTBIT_B)) + 0,
+       OPT_C = USE_FEATURE_GREP_CONTEXT(    (1 << OPTBIT_C)) + 0,
+       OPT_E = USE_FEATURE_GREP_EGREP_ALIAS((1 << OPTBIT_E)) + 0,
+       OPT_w = USE_DESKTOP(                 (1 << OPTBIT_w)) + 0,
+       OPT_z = USE_EXTRA_COMPAT(            (1 << OPTBIT_z)) + 0,
+};
+
+#define PRINT_FILES_WITH_MATCHES    (option_mask32 & OPT_l)
+#define PRINT_LINE_NUM              (option_mask32 & OPT_n)
+#define BE_QUIET                    (option_mask32 & OPT_q)
+#define SUPPRESS_ERR_MSGS           (option_mask32 & OPT_s)
+#define PRINT_MATCH_COUNTS          (option_mask32 & OPT_c)
+#define FGREP_FLAG                  (option_mask32 & OPT_F)
+#define PRINT_FILES_WITHOUT_MATCHES (option_mask32 & OPT_L)
+#define NUL_DELIMITED               (option_mask32 & OPT_z)
+
+struct globals {
+       int max_matches;
+#if !ENABLE_EXTRA_COMPAT
+       int reflags;
+#else
+       RE_TRANSLATE_TYPE case_fold; /* RE_TRANSLATE_TYPE is [[un]signed] char* */
+#endif
+       smalluint invert_search;
+       smalluint print_filename;
+       smalluint open_errors;
+#if ENABLE_FEATURE_GREP_CONTEXT
+       smalluint did_print_line;
+       int lines_before;
+       int lines_after;
+       char **before_buf;
+       USE_EXTRA_COMPAT(size_t *before_buf_size;)
+       int last_line_printed;
+#endif
+       /* globals used internally */
+       llist_t *pattern_head;   /* growable list of patterns to match */
+       const char *cur_file;    /* the current file we are reading */
+};
+#define G (*(struct globals*)&bb_common_bufsiz1)
+#define INIT_G() do { \
+       struct G_sizecheck { \
+               char G_sizecheck[sizeof(G) > COMMON_BUFSIZE ? -1 : 1]; \
+       }; \
+} while (0)
+#define max_matches       (G.max_matches         )
+#if !ENABLE_EXTRA_COMPAT
+#define reflags           (G.reflags             )
+#else
+#define case_fold         (G.case_fold           )
+/* http://www.delorie.com/gnu/docs/regex/regex_46.html */
+#define reflags           re_syntax_options
+#undef REG_NOSUB
+#undef REG_EXTENDED
+#undef REG_ICASE
+#define REG_NOSUB    bug:is:here /* should not be used */
+#define REG_EXTENDED RE_SYNTAX_EGREP
+#define REG_ICASE    bug:is:here /* should not be used */
+#endif
+#define invert_search     (G.invert_search       )
+#define print_filename    (G.print_filename      )
+#define open_errors       (G.open_errors         )
+#define did_print_line    (G.did_print_line      )
+#define lines_before      (G.lines_before        )
+#define lines_after       (G.lines_after         )
+#define before_buf        (G.before_buf          )
+#define before_buf_size   (G.before_buf_size     )
+#define last_line_printed (G.last_line_printed   )
+#define pattern_head      (G.pattern_head        )
+#define cur_file          (G.cur_file            )
+
+
+typedef struct grep_list_data_t {
+       char *pattern;
+/* for GNU regex, matched_range must be persistent across grep_file() calls */
+#if !ENABLE_EXTRA_COMPAT
+       regex_t compiled_regex;
+       regmatch_t matched_range;
+#else
+       struct re_pattern_buffer compiled_regex;
+       struct re_registers matched_range;
+#endif
+#define ALLOCATED 1
+#define COMPILED 2
+       int flg_mem_alocated_compiled;
+} grep_list_data_t;
+
+#if !ENABLE_EXTRA_COMPAT
+#define print_line(line, line_len, linenum, decoration) \
+       print_line(line, linenum, decoration)
+#endif
+static void print_line(const char *line, size_t line_len, int linenum, char decoration)
+{
+#if ENABLE_FEATURE_GREP_CONTEXT
+       /* Happens when we go to next file, immediately hit match
+        * and try to print prev context... from prev file! Don't do it */
+       if (linenum < 1)
+               return;
+       /* possibly print the little '--' separator */
+       if ((lines_before || lines_after) && did_print_line
+        && last_line_printed != linenum - 1
+       ) {
+               puts("--");
+       }
+       /* guard against printing "--" before first line of first file */
+       did_print_line = 1;
+       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 & (OPT_v|OPT_o)) != (OPT_v|OPT_o)) {
+#if !ENABLE_EXTRA_COMPAT
+               puts(line);
+#else
+               fwrite(line, 1, line_len, stdout);
+               putchar(NUL_DELIMITED ? '\0' : '\n');
+#endif
+       }
+}
+
+#if ENABLE_EXTRA_COMPAT
+/* Unlike getline, this one removes trailing '\n' */
+static ssize_t FAST_FUNC bb_getline(char **line_ptr, size_t *line_alloc_len, FILE *file)
+{
+       ssize_t res_sz;
+       char *line;
+       int delim = (NUL_DELIMITED ? '\0' : '\n');
+
+       res_sz = getdelim(line_ptr, line_alloc_len, delim, file);
+       line = *line_ptr;
+
+       if (res_sz > 0) {
+               if (line[res_sz - 1] == delim)
+                       line[--res_sz] = '\0';
+       } else {
+               free(line); /* uclibc allocates a buffer even on EOF. WTF? */
+       }
+       return res_sz;
+}
+#endif
+
+static int grep_file(FILE *file)
+{
+       smalluint found;
+       int linenum = 0;
+       int nmatches = 0;
+#if !ENABLE_EXTRA_COMPAT
+       char *line;
+#else
+       char *line = NULL;
+       ssize_t line_len;
+       size_t line_alloc_len;
+#define rm_so start[0]
+#define rm_eo end[0]
+#endif
+#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 */
+#else
+       enum { print_n_lines_after = 0 };
+#endif /* ENABLE_FEATURE_GREP_CONTEXT */
+
+       while (
+#if !ENABLE_EXTRA_COMPAT
+               (line = xmalloc_fgetline(file)) != NULL
+#else
+               (line_len = bb_getline(&line, &line_alloc_len, file)) >= 0
+#endif
+       ) {
+               llist_t *pattern_ptr = pattern_head;
+               grep_list_data_t *gl = gl; /* for gcc */
+
+               linenum++;
+               found = 0;
+               while (pattern_ptr) {
+                       gl = (grep_list_data_t *)pattern_ptr->data;
+                       if (FGREP_FLAG) {
+                               found |= (strstr(line, gl->pattern) != NULL);
+                       } else {
+                               if (!(gl->flg_mem_alocated_compiled & COMPILED)) {
+                                       gl->flg_mem_alocated_compiled |= COMPILED;
+#if !ENABLE_EXTRA_COMPAT
+                                       xregcomp(&gl->compiled_regex, gl->pattern, reflags);
+#else
+                                       memset(&gl->compiled_regex, 0, sizeof(gl->compiled_regex));
+                                       gl->compiled_regex.translate = case_fold; /* for -i */
+                                       if (re_compile_pattern(gl->pattern, strlen(gl->pattern), &gl->compiled_regex))
+                                               bb_error_msg_and_die("bad regex '%s'", gl->pattern);
+#endif
+                               }
+#if !ENABLE_EXTRA_COMPAT
+                               gl->matched_range.rm_so = 0;
+                               gl->matched_range.rm_eo = 0;
+#endif
+                               if (
+#if !ENABLE_EXTRA_COMPAT
+                                       regexec(&gl->compiled_regex, line, 1, &gl->matched_range, 0) == 0
+#else
+                                       re_search(&gl->compiled_regex, line, line_len,
+                                                       /*start:*/ 0, /*range:*/ line_len,
+                                                       &gl->matched_range) >= 0
+#endif
+                               ) {
+                                       if (!(option_mask32 & OPT_w))
+                                               found = 1;
+                                       else {
+                                               char c = ' ';
+                                               if (gl->matched_range.rm_so)
+                                                       c = line[gl->matched_range.rm_so - 1];
+                                               if (!isalnum(c) && c != '_') {
+                                                       c = line[gl->matched_range.rm_eo];
+                                                       if (!c || (!isalnum(c) && c != '_'))
+                                                               found = 1;
+                                               }
+                                       }
+                               }
+                       }
+                       /* If it's non-inverted search, we can stop
+                        * at first match */
+                       if (found && !invert_search)
+                               goto do_found;
+                       pattern_ptr = pattern_ptr->link;
+               } /* while (pattern_ptr) */
+
+               if (found ^ invert_search) {
+ do_found:
+                       /* keep track of matches */
+                       nmatches++;
+
+                       /* quiet/print (non)matching file names only? */
+                       if (option_mask32 & (OPT_q|OPT_l|OPT_L)) {
+                               free(line); /* we don't need line anymore */
+                               if (BE_QUIET) {
+                                       /* manpage says about -q:
+                                        * "exit immediately with zero status
+                                        * if any match is found,
+                                        * even if errors were detected" */
+                                       exit(EXIT_SUCCESS);
+                               }
+                               /* if we're just printing filenames, we stop after the first match */
+                               if (PRINT_FILES_WITH_MATCHES) {
+                                       puts(cur_file);
+                                       /* fall through to "return 1" */
+                               }
+                               /* OPT_L aka PRINT_FILES_WITHOUT_MATCHES: return early */
+                               return 1; /* one match */
+                       }
+
+#if ENABLE_FEATURE_GREP_CONTEXT
+                       /* Were we printing context and saw next (unwanted) match? */
+                       if ((option_mask32 & OPT_m) && nmatches > max_matches)
+                               break;
+#endif
+
+                       /* 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], before_buf_size[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 & OPT_o) {
+                                       if (FGREP_FLAG) {
+                                               /* -Fo just prints the pattern
+                                                * (unless -v: -Fov doesnt print anything at all) */
+                                               if (found)
+                                                       print_line(gl->pattern, strlen(gl->pattern), linenum, ':');
+                                       } else while (1) {
+                                               char old = line[gl->matched_range.rm_eo];
+                                               line[gl->matched_range.rm_eo] = '\0';
+                                               print_line(line + gl->matched_range.rm_so,
+                                                               gl->matched_range.rm_eo - gl->matched_range.rm_so,
+                                                               linenum, ':');
+                                               line[gl->matched_range.rm_eo] = old;
+#if !ENABLE_EXTRA_COMPAT
+                                               break;
+#else
+                                               if (re_search(&gl->compiled_regex, line, line_len,
+                                                               gl->matched_range.rm_eo, line_len - gl->matched_range.rm_eo,
+                                                               &gl->matched_range) < 0)
+                                                       break;
+#endif
+                                       }
+                               } else {
+                                       print_line(line, line_len, linenum, ':');
+                               }
+                       }
+               }
+#if ENABLE_FEATURE_GREP_CONTEXT
+               else { /* no match */
+                       /* if we need to print some context lines after the last match, do so */
+                       if (print_n_lines_after) {
+                               print_line(line, strlen(line), linenum, '-');
+                               print_n_lines_after--;
+                       } else if (lines_before) {
+                               /* Add the line to the circular 'before' buffer */
+                               free(before_buf[curpos]);
+                               before_buf[curpos] = line;
+                               USE_EXTRA_COMPAT(before_buf_size[curpos] = line_len;)
+                               curpos = (curpos + 1) % lines_before;
+                               /* avoid free(line) - we took the line */
+                               line = NULL;
+                       }
+               }
+
+#endif /* ENABLE_FEATURE_GREP_CONTEXT */
+#if !ENABLE_EXTRA_COMPAT
+               free(line);
+#endif
+               /* Did we print all context after last requested match? */
+               if ((option_mask32 & OPT_m)
+                && !print_n_lines_after
+                && nmatches == max_matches
+               ) {
+                       break;
+               }
+       } /* while (read 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 */
+       if (PRINT_FILES_WITHOUT_MATCHES) {
+               /* nmatches is zero, no need to check it:
+                * we return 1 early if we detected a match
+                * and PRINT_FILES_WITHOUT_MATCHES is set */
+               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 = xzalloc(sizeof(*gl));
+       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_stdin(ffile);
+               while ((line = xmalloc_fgetline(f)) != NULL) {
+                       llist_add_to(&pattern_head,
+                               new_grep_list_data(line, ALLOCATED));
+               }
+       }
+}
+
+static int FAST_FUNC file_action_grep(const char *filename,
+                       struct stat *statbuf UNUSED_PARAM,
+                       void* matched,
+                       int depth UNUSED_PARAM)
+{
+       FILE *file = fopen_for_read(filename);
+       if (file == NULL) {
+               if (!SUPPRESS_ERR_MSGS)
+                       bb_simple_perror_msg(filename);
+               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=yes */ ACTION_RECURSE |
+               /* followLinks=no */
+               /* depthFirst=yes */ ACTION_DEPTHFIRST,
+               /* fileAction= */ file_action_grep,
+               /* dirAction= */ NULL,
+               /* userData= */ &matched,
+               /* depth= */ 0);
+       return matched;
+}
+
+int grep_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int grep_main(int argc, char **argv)
+{
+       FILE *file;
+       int matched;
+       llist_t *fopt = NULL;
+
+       /* do normal option parsing */
+#if ENABLE_FEATURE_GREP_CONTEXT
+       int Copt;
+
+       /* -H unsets -h; -C unsets -A,-B; -e,-f are lists;
+        * -m,-A,-B,-C have numeric param */
+       opt_complementary = "H-h:C-AB:e::f::m+:A+:B+:C+";
+       getopt32(argv,
+               OPTSTR_GREP,
+               &pattern_head, &fopt, &max_matches,
+               &lines_after, &lines_before, &Copt);
+
+       if (option_mask32 & OPT_C) {
+               /* -C unsets prev -A and -B, but following -A or -B
+                  may override it */
+               if (!(option_mask32 & OPT_A)) /* not overridden */
+                       lines_after = Copt;
+               if (!(option_mask32 & OPT_B)) /* not overridden */
+                       lines_before = Copt;
+       }
+       /* sanity checks */
+       if (option_mask32 & (OPT_c|OPT_q|OPT_l|OPT_L)) {
+               option_mask32 &= ~OPT_n;
+               lines_before = 0;
+               lines_after = 0;
+       } else if (lines_before > 0) {
+               before_buf = xzalloc(lines_before * sizeof(before_buf[0]));
+               USE_EXTRA_COMPAT(before_buf_size = xzalloc(lines_before * sizeof(before_buf_size[0]));)
+       }
+#else
+       /* with auto sanity checks */
+       /* -H unsets -h; -c,-q or -l unset -n; -e,-f are lists; -m N */
+       opt_complementary = "H-h:c-n:q-n:l-n:e::f::m+";
+       getopt32(argv, OPTSTR_GREP,
+               &pattern_head, &fopt, &max_matches);
+#endif
+       invert_search = ((option_mask32 & 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 & OPT_f)
+               load_regexes_from_file(fopt);
+
+       if (ENABLE_FEATURE_GREP_FGREP_ALIAS && applet_name[0] == 'f')
+               option_mask32 |= OPT_F;
+
+#if !ENABLE_EXTRA_COMPAT
+       if (!(option_mask32 & (OPT_o | OPT_w)))
+               reflags = REG_NOSUB;
+#endif
+
+       if (ENABLE_FEATURE_GREP_EGREP_ALIAS
+        && (applet_name[0] == 'e' || (option_mask32 & OPT_E))
+       ) {
+               reflags |= REG_EXTENDED;
+       }
+#if ENABLE_EXTRA_COMPAT
+       else {
+               reflags = RE_SYNTAX_GREP;
+       }
+#endif
+
+       if (option_mask32 & OPT_i) {
+#if !ENABLE_EXTRA_COMPAT
+               reflags |= REG_ICASE;
+#else
+               int i;
+               case_fold = xmalloc(256);
+               for (i = 0; i < 256; i++)
+                       case_fold[i] = (unsigned char)i;
+               for (i = 'a'; i <= 'z'; i++)
+                       case_fold[i] = (unsigned char)(i - ('a' - 'A'));
+#endif
+       }
+
+       argv += optind;
+       argc -= optind;
+
+       /* if we didn't get a pattern from -e and no command file was specified,
+        * first parameter should be the pattern. no pattern, no worky */
+       if (pattern_head == NULL) {
+               char *pattern;
+               if (*argv == NULL)
+                       bb_show_usage();
+               pattern = new_grep_list_data(*argv++, 0);
+               llist_add_to(&pattern_head, pattern);
+               argc--;
+       }
+
+       /* argv[0..(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 & OPT_H)
+               print_filename = 1;
+       if (option_mask32 & 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. */
+       matched = 0;
+       do {
+               cur_file = *argv++;
+               file = stdin;
+               if (!cur_file || LONE_DASH(cur_file)) {
+                       cur_file = "(standard input)";
+               } else {
+                       if (option_mask32 & OPT_r) {
+                               struct stat st;
+                               if (stat(cur_file, &st) == 0 && S_ISDIR(st.st_mode)) {
+                                       if (!(option_mask32 & OPT_h))
+                                               print_filename = 1;
+                                       matched += grep_dir(cur_file);
+                                       goto grep_done;
+                               }
+                       }
+                       /* else: fopen(dir) will succeed, but reading won't */
+                       file = fopen_for_read(cur_file);
+                       if (file == NULL) {
+                               if (!SUPPRESS_ERR_MSGS)
+                                       bb_simple_perror_msg(cur_file);
+                               open_errors = 1;
+                               continue;
+                       }
+               }
+               matched += grep_file(file);
+               fclose_if_not_stdin(file);
+ grep_done: ;
+       } while (--argc > 0);
+
+       /* 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 & ALLOCATED)
+                               free(gl->pattern);
+                       if (gl->flg_mem_alocated_compiled & COMPILED)
+                               regfree(&gl->compiled_regex);
+                       free(gl);
+                       free(pattern_head_ptr);
+               }
+       }
+       /* 0 = success, 1 = failed, 2 = error */
+       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 (file)
index 0000000..f22d089
--- /dev/null
@@ -0,0 +1,535 @@
+/* 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 <dzo@simtreas.ru>
+ *
+ * Special thanks
+ * - Mark Whitley and Glenn McGrath for stimulus to rewrite :)
+ * - Mike Rendell <michael@cs.mun.ca>
+ * and David MacKenzie <djm@gnu.ai.mit.edu>.
+ *
+ * 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 "libbb.h"
+
+/* This is a NOEXEC applet. Be very careful! */
+
+
+/* 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 ENABLE_FEATURE_XARGS_SUPPORT_CONFIRMATION
+#  define ENABLE_FEATURE_XARGS_SUPPORT_CONFIRMATION 1
+# endif
+# ifndef ENABLE_FEATURE_XARGS_SUPPORT_QUOTES
+#  define ENABLE_FEATURE_XARGS_SUPPORT_QUOTES 1
+# endif
+# ifndef ENABLE_FEATURE_XARGS_SUPPORT_TERMOPT
+#  define ENABLE_FEATURE_XARGS_SUPPORT_TERMOPT 1
+# endif
+# ifndef ENABLE_FEATURE_XARGS_SUPPORT_ZERO_TERM
+#  define ENABLE_FEATURE_XARGS_SUPPORT_ZERO_TERM 1
+# endif
+#endif
+
+/*
+   This function has special algorithm.
+   Don't use fork and include to main!
+*/
+static int xargs_exec(char **args)
+{
+       int status;
+
+       status = spawn_and_wait(args);
+       if (status < 0) {
+               bb_simple_perror_msg(args[0]);
+               return errno == ENOENT ? 127 : 126;
+       }
+       if (status == 255) {
+               bb_error_msg("%s: exited with status 255; aborting", args[0]);
+               return 124;
+       }
+/* Huh? I think we won't see this, ever. We don't wait with WUNTRACED!
+       if (WIFSTOPPED(status)) {
+               bb_error_msg("%s: stopped by signal %d",
+                       args[0], WSTOPSIG(status));
+               return 125;
+       }
+*/
+       if (status >= 1000) {
+               bb_error_msg("%s: terminated by signal %d",
+                       args[0], status - 1000);
+               return 125;
+       }
+       if (status)
+               return 123;
+       return 0;
+}
+
+
+typedef struct xlist_t {
+       struct xlist_t *link;
+       size_t length;
+       char xstr[1];
+} xlist_t;
+
+static smallint eof_stdin_detected;
+
+#define ISBLANK(c) ((c) == ' ' || (c) == '\t')
+#define ISSPACE(c) (ISBLANK(c) || (c) == '\n' || (c) == '\r' \
+                   || (c) == '\f' || (c) == '\v')
+
+#if ENABLE_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;
+
+       prev = cur = list_arg;
+       while (1) {
+               if (!cur) break;
+               prev = cur;
+               line_l += cur->length;
+               cur = cur->link;
+       }
+
+       while (!eof_stdin_detected) {
+               c = getchar();
+               if (c == EOF) {
+                       eof_stdin_detected = 1;
+                       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)
+                               goto set;
+                       q = '\0';
+                       state = NORM;
+               } 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 length = (p - buf);
+                               /* Dont xzalloc - it can be quite big */
+                               cur = xmalloc(offsetof(xlist_t, xstr) + length);
+                               cur->link = NULL;
+                               cur->length = length;
+                               memcpy(cur->xstr, s, length);
+                               if (prev == NULL) {
+                                       list_arg = cur;
+                               } else {
+                                       prev->link = cur;
+                               }
+                               prev = cur;
+                               line_l += length;
+                               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 */
+       char 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;
+
+       prev = cur = list_arg;
+       while (1) {
+               if (!cur) break;
+               prev = cur;
+               line_l += cur->length;
+               cur = cur->link;
+       }
+
+       while (!eof_stdin_detected) {
+               c = getchar();
+               if (c == EOF) {
+                       eof_stdin_detected = 1;
+               }
+               if (eof_str_detected)
+                       continue;
+               if (c == EOF || ISSPACE(c)) {
+                       if (s == NULL)
+                               continue;
+                       c = EOF;
+               }
+               if (s == NULL)
+                       s = p = buf;
+               if ((size_t)(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 length = (p - buf);
+                               /* Dont xzalloc - it can be quite big */
+                               cur = xmalloc(offsetof(xlist_t, xstr) + length);
+                               cur->link = NULL;
+                               cur->length = length;
+                               memcpy(cur->xstr, s, length);
+                               if (prev == NULL) {
+                                       list_arg = cur;
+                               } else {
+                                       prev->link = cur;
+                               }
+                               prev = cur;
+                               line_l += length;
+                               if (line_l > mc) {
+                                       /* stop memory usage :-) */
+                                       break;
+                               }
+                               s = NULL;
+                       }
+               }
+       }
+       return list_arg;
+}
+#endif /* FEATURE_XARGS_SUPPORT_QUOTES */
+
+
+#if ENABLE_FEATURE_XARGS_SUPPORT_CONFIRMATION
+/* Prompt the user for a response, and
+   if the user responds affirmatively, return true;
+   otherwise, return false. Uses "/dev/tty", not stdin. */
+static int xargs_ask_confirmation(void)
+{
+       FILE *tty_stream;
+       int c, savec;
+
+       tty_stream = xfopen_for_read(CURRENT_TTY);
+       fputs(" ?...", stderr);
+       fflush(stderr);
+       c = savec = getc(tty_stream);
+       while (c != EOF && c != '\n')
+               c = getc(tty_stream);
+       fclose(tty_stream);
+       return (savec == 'y' || savec == 'Y');
+}
+#else
+# define xargs_ask_confirmation() 1
+#endif /* FEATURE_XARGS_SUPPORT_CONFIRMATION */
+
+#if ENABLE_FEATURE_XARGS_SUPPORT_ZERO_TERM
+static xlist_t *process0_stdin(xlist_t *list_arg,
+               const char *eof_str UNUSED_PARAM, 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;
+
+       prev = cur = list_arg;
+       while (1) {
+               if (!cur) break;
+               prev = cur;
+               line_l += cur->length;
+               cur = cur->link;
+       }
+
+       while (!eof_stdin_detected) {
+               c = getchar();
+               if (c == EOF) {
+                       eof_stdin_detected = 1;
+                       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 length = (p - buf);
+                       /* Dont xzalloc - it can be quite big */
+                       cur = xmalloc(offsetof(xlist_t, xstr) + length);
+                       cur->link = NULL;
+                       cur->length = length;
+                       memcpy(cur->xstr, s, length);
+                       if (prev == NULL) {
+                               list_arg = cur;
+                       } else {
+                               prev->link = cur;
+                       }
+                       prev = cur;
+                       line_l += length;
+                       if (line_l > mc) {
+                               /* stop memory usage :-) */
+                               break;
+                       }
+                       s = NULL;
+               }
+       }
+       return list_arg;
+}
+#endif /* 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,
+       OPTBIT_EOF_STRING1,
+       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 << OPTBIT_VERBOSE    ,
+       OPT_NO_EMPTY    = 1 << OPTBIT_NO_EMPTY   ,
+       OPT_UPTO_NUMBER = 1 << OPTBIT_UPTO_NUMBER,
+       OPT_UPTO_SIZE   = 1 << OPTBIT_UPTO_SIZE  ,
+       OPT_EOF_STRING  = 1 << OPTBIT_EOF_STRING , /* GNU: -e[<param>] */
+       OPT_EOF_STRING1 = 1 << OPTBIT_EOF_STRING1, /* SUS: -E<param> */
+       OPT_INTERACTIVE = USE_FEATURE_XARGS_SUPPORT_CONFIRMATION((1 << OPTBIT_INTERACTIVE)) + 0,
+       OPT_TERMINATE   = USE_FEATURE_XARGS_SUPPORT_TERMOPT(     (1 << OPTBIT_TERMINATE  )) + 0,
+       OPT_ZEROTERM    = USE_FEATURE_XARGS_SUPPORT_ZERO_TERM(   (1 << OPTBIT_ZEROTERM   )) + 0,
+};
+#define OPTION_STR "+trn:s:e::E:" \
+       USE_FEATURE_XARGS_SUPPORT_CONFIRMATION("p") \
+       USE_FEATURE_XARGS_SUPPORT_TERMOPT(     "x") \
+       USE_FEATURE_XARGS_SUPPORT_ZERO_TERM(   "0")
+
+int xargs_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int xargs_main(int argc, char **argv)
+{
+       char **args;
+       int i, n;
+       xlist_t *list = NULL;
+       xlist_t *cur;
+       int child_error = 0;
+       char *max_args, *max_chars;
+       int n_max_arg;
+       size_t n_chars = 0;
+       long orig_arg_max;
+       const char *eof_str = NULL;
+       unsigned opt;
+       size_t n_max_chars;
+#if ENABLE_FEATURE_XARGS_SUPPORT_ZERO_TERM
+       xlist_t* (*read_args)(xlist_t*, const char*, size_t, char*) = process_stdin;
+#else
+#define read_args process_stdin
+#endif
+
+       opt = getopt32(argv, OPTION_STR, &max_args, &max_chars, &eof_str, &eof_str);
+
+       /* -E ""? You may wonder why not just omit -E?
+        * This is used for portability:
+        * old xargs was using "_" as default for -E / -e */
+       if ((opt & OPT_EOF_STRING1) && eof_str[0] == '\0')
+               eof_str = NULL;
+
+       if (opt & OPT_ZEROTERM)
+               USE_FEATURE_XARGS_SUPPORT_ZERO_TERM(read_args = process0_stdin);
+
+       argv += optind;
+       argc -= optind;
+       if (!argc) {
+               /* default behavior is to echo all the filenames */
+               *argv = (char*)"echo";
+               argc++;
+       }
+
+       orig_arg_max = ARG_MAX;
+       if (orig_arg_max == -1)
+               orig_arg_max = LONG_MAX;
+       orig_arg_max -= 2048;   /* POSIX.2 requires subtracting 2048 */
+
+       if (opt & OPT_UPTO_SIZE) {
+               n_max_chars = xatoul_range(max_chars, 1, orig_arg_max);
+               for (i = 0; i < argc; i++) {
+                       n_chars += strlen(*argv) + 1;
+               }
+               if (n_max_chars < n_chars) {
+                       bb_error_msg_and_die("cannot fit single argument within argument list size limit");
+               }
+               n_max_chars -= n_chars;
+       } else {
+               /* Sanity check for systems with huge ARG_MAX defines (e.g., Suns which
+                  have it at 1 meg).  Things will work fine with a large ARG_MAX but it
+                  will probably hurt the system more than it needs to; an array of this
+                  size is allocated.  */
+               if (orig_arg_max > 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;
+#if ENABLE_FEATURE_XARGS_SUPPORT_TERMOPT
+               for (cur = list; cur;) {
+                       n_chars += cur->length;
+                       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->length;
+                       n++;
+                       if (n_chars > n_max_chars || n == n_max_arg) {
+                               break;
+                       }
+               }
+#endif /* 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->xstr;
+                       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;
+               }
+       } /* while */
+       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(EXIT_FAILURE);
+}
+
+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 (file)
index 0000000..32c596d
--- /dev/null
@@ -0,0 +1,430 @@
+/* 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.
+ */
+
+/*
+name  - applet name as it is typed on command line
+name2 - applet name, converted to C (ether-wake: name2 = ether_wake)
+main  - corresponding <applet>_main to call (bzcat: main = bunzip2)
+l     - location to install link to: [/usr]/[s]bin
+s     - suid type:
+        _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
+*/
+
+#if defined(PROTOTYPES)
+# define APPLET(name,l,s)                    int name##_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+# define APPLET_ODDNAME(name,main,l,s,name2) int main##_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+# define APPLET_NOEXEC(name,main,l,s,name2)  int main##_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+# define APPLET_NOFORK(name,main,l,s,name2)  int main##_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+
+#elif defined(NAME_MAIN_CNAME)
+# define APPLET(name,l,s)                    name name##_main name
+# define APPLET_ODDNAME(name,main,l,s,name2) name main##_main name2
+# define APPLET_NOEXEC(name,main,l,s,name2)  name main##_main name2
+# define APPLET_NOFORK(name,main,l,s,name2)  name main##_main name2
+
+#elif defined(MAKE_USAGE) && ENABLE_FEATURE_VERBOSE_USAGE
+# define APPLET(name,l,s)                    name##_trivial_usage name##_full_usage "\0"
+# define APPLET_ODDNAME(name,main,l,s,name2) name2##_trivial_usage name2##_full_usage "\0"
+# define APPLET_NOEXEC(name,main,l,s,name2)  name2##_trivial_usage name2##_full_usage "\0"
+# define APPLET_NOFORK(name,main,l,s,name2)  name2##_trivial_usage name2##_full_usage "\0"
+
+#elif defined(MAKE_USAGE) && !ENABLE_FEATURE_VERBOSE_USAGE
+# define APPLET(name,l,s)                    name##_trivial_usage "\0"
+# define APPLET_ODDNAME(name,main,l,s,name2) name2##_trivial_usage "\0"
+# define APPLET_NOEXEC(name,main,l,s,name2)  name2##_trivial_usage "\0"
+# define APPLET_NOFORK(name,main,l,s,name2)  name2##_trivial_usage "\0"
+
+#elif defined(MAKE_LINKS)
+# define APPLET(name,l,c)                    LINK l name
+# define APPLET_ODDNAME(name,main,l,s,name2) LINK l name
+# define APPLET_NOEXEC(name,main,l,s,name2)  LINK l name
+# define APPLET_NOFORK(name,main,l,s,name2)  LINK l name
+
+#else
+  static struct bb_applet applets[] = { /*    name, main, location, need_suid */
+# define APPLET(name,l,s)                    { #name, #name, l, s },
+# define APPLET_ODDNAME(name,main,l,s,name2) { #name, #main, l, s },
+# define APPLET_NOEXEC(name,main,l,s,name2)  { #name, #main, l, s, 1 },
+# define APPLET_NOFORK(name,main,l,s,name2)  { #name, #main, l, s, 1, 1 },
+#endif
+
+#if ENABLE_INSTALL_NO_USR
+# define _BB_DIR_USR_BIN _BB_DIR_BIN
+# define _BB_DIR_USR_SBIN _BB_DIR_SBIN
+#endif
+
+
+USE_TEST(APPLET_NOFORK([,  test, _BB_DIR_USR_BIN, _BB_SUID_NEVER, test))
+USE_TEST(APPLET_NOFORK([[, test, _BB_DIR_USR_BIN, _BB_SUID_NEVER, test))
+USE_ACPID(APPLET(acpid, _BB_DIR_SBIN, _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_ARP(APPLET(arp, _BB_DIR_SBIN, _BB_SUID_NEVER))
+USE_ARPING(APPLET(arping, _BB_DIR_USR_BIN, _BB_SUID_NEVER))
+USE_ASH(APPLET(ash, _BB_DIR_BIN, _BB_SUID_NEVER))
+USE_AWK(APPLET_NOEXEC(awk, awk, _BB_DIR_USR_BIN, _BB_SUID_NEVER, awk))
+USE_BASENAME(APPLET_NOFORK(basename, basename, _BB_DIR_USR_BIN, _BB_SUID_NEVER, basename))
+USE_BBCONFIG(APPLET(bbconfig, _BB_DIR_BIN, _BB_SUID_NEVER))
+//USE_BBSH(APPLET(bbsh, _BB_DIR_BIN, _BB_SUID_NEVER))
+USE_BLKID(APPLET(blkid, _BB_DIR_SBIN, _BB_SUID_NEVER))
+USE_BRCTL(APPLET(brctl, _BB_DIR_USR_SBIN, _BB_SUID_NEVER))
+USE_BUNZIP2(APPLET(bunzip2, _BB_DIR_USR_BIN, _BB_SUID_NEVER))
+USE_BUNZIP2(APPLET_ODDNAME(bzcat, bunzip2, _BB_DIR_USR_BIN, _BB_SUID_NEVER, bzcat))
+USE_BZIP2(APPLET(bzip2, _BB_DIR_USR_BIN, _BB_SUID_NEVER))
+USE_CAL(APPLET(cal, _BB_DIR_USR_BIN, _BB_SUID_NEVER))
+USE_CAT(APPLET_NOFORK(cat, cat, _BB_DIR_BIN, _BB_SUID_NEVER, cat))
+USE_CATV(APPLET(catv, _BB_DIR_BIN, _BB_SUID_NEVER))
+USE_CHAT(APPLET(chat, _BB_DIR_USR_BIN, _BB_SUID_NEVER))
+USE_CHATTR(APPLET(chattr, _BB_DIR_BIN, _BB_SUID_NEVER))
+USE_CHCON(APPLET(chcon, _BB_DIR_USR_BIN, _BB_SUID_NEVER))
+USE_CHGRP(APPLET_NOEXEC(chgrp, chgrp, _BB_DIR_BIN, _BB_SUID_NEVER, chgrp))
+USE_CHMOD(APPLET_NOEXEC(chmod, chmod, _BB_DIR_BIN, _BB_SUID_NEVER, chmod))
+USE_CHOWN(APPLET_NOEXEC(chown, chown, _BB_DIR_BIN, _BB_SUID_NEVER, chown))
+USE_CHPASSWD(APPLET(chpasswd, _BB_DIR_USR_SBIN, _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_CHRT(APPLET(chrt, _BB_DIR_USR_BIN, _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_NOEXEC(cp, cp, _BB_DIR_BIN, _BB_SUID_NEVER, cp))
+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_CRYPTPW(APPLET(cryptpw, _BB_DIR_USR_BIN, _BB_SUID_NEVER))
+USE_CTTYHACK(APPLET(cttyhack, _BB_DIR_BIN, _BB_SUID_NEVER))
+USE_CUT(APPLET_NOEXEC(cut, cut, _BB_DIR_USR_BIN, _BB_SUID_NEVER, cut))
+USE_DATE(APPLET(date, _BB_DIR_BIN, _BB_SUID_NEVER))
+USE_DC(APPLET(dc, _BB_DIR_USR_BIN, _BB_SUID_NEVER))
+USE_DD(APPLET_NOEXEC(dd, dd, _BB_DIR_BIN, _BB_SUID_NEVER, dd))
+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_DEPMOD(APPLET(depmod, _BB_DIR_SBIN, _BB_SUID_NEVER))
+USE_MODPROBE_SMALL(APPLET_ODDNAME(depmod, modprobe, _BB_DIR_SBIN, _BB_SUID_NEVER, modprobe))
+USE_DEVFSD(APPLET(devfsd, _BB_DIR_SBIN, _BB_SUID_NEVER))
+USE_DEVMEM(APPLET(devmem, _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_NOFORK(dirname, dirname, _BB_DIR_USR_BIN, _BB_SUID_NEVER, dirname))
+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_ODDNAME(e2label, tune2fs, _BB_DIR_SBIN, _BB_SUID_NEVER, e2label))
+USE_ECHO(APPLET_NOFORK(echo, echo, _BB_DIR_BIN, _BB_SUID_NEVER, echo))
+USE_ED(APPLET(ed, _BB_DIR_BIN, _BB_SUID_NEVER))
+USE_FEATURE_GREP_EGREP_ALIAS(APPLET_ODDNAME(egrep, grep, _BB_DIR_BIN, _BB_SUID_NEVER, egrep))
+USE_EJECT(APPLET(eject, _BB_DIR_USR_BIN, _BB_SUID_NEVER))
+USE_ENV(APPLET_NOEXEC(env, env, _BB_DIR_USR_BIN, _BB_SUID_NEVER, env))
+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_EXPAND(APPLET(expand, _BB_DIR_USR_BIN, _BB_SUID_NEVER))
+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_NOFORK(false, false, _BB_DIR_BIN, _BB_SUID_NEVER, false))
+USE_FBSET(APPLET(fbset, _BB_DIR_USR_SBIN, _BB_SUID_NEVER))
+USE_FBSPLASH(APPLET(fbsplash, _BB_DIR_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_ODDNAME(fgrep, grep, _BB_DIR_BIN, _BB_SUID_NEVER, fgrep))
+USE_FIND(APPLET_NOEXEC(find, find, _BB_DIR_USR_BIN, _BB_SUID_NEVER, find))
+USE_FINDFS(APPLET(findfs, _BB_DIR_SBIN, _BB_SUID_MAYBE))
+//USE_FLASH_ERASEALL(APPLET_ODDNAME(flash_eraseall, flash_eraseall, _BB_DIR_USR_SBIN, _BB_SUID_NEVER, flash_eraseall))
+USE_FLASH_ERASEALL(APPLET(flash_eraseall, _BB_DIR_USR_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_ODDNAME(fsck.ext2, e2fsck, _BB_DIR_SBIN, _BB_SUID_NEVER, fsck_ext2))
+//USE_E2FSCK(APPLET_ODDNAME(fsck.ext3, e2fsck, _BB_DIR_SBIN, _BB_SUID_NEVER, fsck_ext3))
+USE_FSCK_MINIX(APPLET_ODDNAME(fsck.minix, fsck_minix, _BB_DIR_SBIN, _BB_SUID_NEVER, fsck_minix))
+USE_FTPD(APPLET(ftpd, _BB_DIR_USR_SBIN, _BB_SUID_NEVER))
+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_GETENFORCE(APPLET(getenforce, _BB_DIR_USR_SBIN, _BB_SUID_NEVER))
+USE_GETOPT(APPLET(getopt, _BB_DIR_BIN, _BB_SUID_NEVER))
+USE_GETSEBOOL(APPLET(getsebool, _BB_DIR_USR_SBIN, _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_HD(APPLET_NOEXEC(hd, hexdump, _BB_DIR_USR_BIN, _BB_SUID_NEVER, hd))
+USE_HDPARM(APPLET(hdparm, _BB_DIR_SBIN, _BB_SUID_NEVER))
+USE_HEAD(APPLET(head, _BB_DIR_USR_BIN, _BB_SUID_NEVER))
+USE_HEXDUMP(APPLET_NOEXEC(hexdump, hexdump, _BB_DIR_USR_BIN, _BB_SUID_NEVER, hexdump))
+USE_HOSTID(APPLET_NOFORK(hostid, hostid, _BB_DIR_USR_BIN, _BB_SUID_NEVER, hostid))
+USE_HOSTNAME(APPLET(hostname, _BB_DIR_BIN, _BB_SUID_NEVER))
+USE_HTTPD(APPLET(httpd, _BB_DIR_USR_SBIN, _BB_SUID_NEVER))
+USE_HUSH(APPLET(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_IFENSLAVE(APPLET(ifenslave, _BB_DIR_SBIN, _BB_SUID_NEVER))
+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_INOTIFYD(APPLET(inotifyd, _BB_DIR_SBIN, _BB_SUID_NEVER))
+USE_INSMOD(APPLET(insmod, _BB_DIR_SBIN, _BB_SUID_NEVER))
+USE_MODPROBE_SMALL(APPLET_ODDNAME(insmod, modprobe, _BB_DIR_SBIN, _BB_SUID_NEVER, modprobe))
+USE_INSTALL(APPLET(install, _BB_DIR_USR_BIN, _BB_SUID_NEVER))
+USE_IONICE(APPLET(ionice, _BB_DIR_BIN, _BB_SUID_NEVER))
+#if ENABLE_FEATURE_IP_ADDRESS \
+ || ENABLE_FEATURE_IP_ROUTE \
+ || ENABLE_FEATURE_IP_LINK \
+ || ENABLE_FEATURE_IP_TUNNEL \
+ || ENABLE_FEATURE_IP_RULE
+USE_IP(APPLET(ip, _BB_DIR_BIN, _BB_SUID_NEVER))
+#endif
+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_KBD_MODE(APPLET(kbd_mode, _BB_DIR_USR_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_NOFORK(length, length, _BB_DIR_USR_BIN, _BB_SUID_NEVER, length))
+USE_LESS(APPLET(less, _BB_DIR_USR_BIN, _BB_SUID_NEVER))
+USE_SETARCH(APPLET_ODDNAME(linux32, setarch, _BB_DIR_BIN, _BB_SUID_NEVER, linux32))
+USE_SETARCH(APPLET_ODDNAME(linux64, setarch, _BB_DIR_BIN, _BB_SUID_NEVER, linux64))
+USE_FEATURE_INITRD(APPLET_ODDNAME(linuxrc, init, _BB_DIR_ROOT, _BB_SUID_NEVER, linuxrc))
+USE_LN(APPLET_NOEXEC(ln, ln, _BB_DIR_BIN, _BB_SUID_NEVER, ln))
+USE_LOAD_POLICY(APPLET(load_policy, _BB_DIR_USR_SBIN, _BB_SUID_NEVER))
+USE_LOADFONT(APPLET(loadfont, _BB_DIR_USR_SBIN, _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_NOFORK(logname, logname, _BB_DIR_USR_BIN, _BB_SUID_NEVER, logname))
+USE_LOGREAD(APPLET(logread, _BB_DIR_SBIN, _BB_SUID_NEVER))
+USE_LOSETUP(APPLET(losetup, _BB_DIR_SBIN, _BB_SUID_NEVER))
+USE_LPD(APPLET(lpd, _BB_DIR_USR_SBIN, _BB_SUID_NEVER))
+USE_LPQ(APPLET_ODDNAME(lpq, lpqr, _BB_DIR_USR_BIN, _BB_SUID_NEVER, lpq))
+USE_LPR(APPLET_ODDNAME(lpr, lpqr, _BB_DIR_USR_BIN, _BB_SUID_NEVER, lpr))
+USE_LS(APPLET_NOEXEC(ls, ls, _BB_DIR_BIN, _BB_SUID_NEVER, ls))
+USE_LSATTR(APPLET(lsattr, _BB_DIR_BIN, _BB_SUID_NEVER))
+USE_LSMOD(APPLET(lsmod, _BB_DIR_SBIN, _BB_SUID_NEVER))
+USE_MODPROBE_SMALL(APPLET_ODDNAME(lsmod, modprobe, _BB_DIR_SBIN, _BB_SUID_NEVER, modprobe))
+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_MAKEMIME(APPLET(makemime, _BB_DIR_BIN, _BB_SUID_NEVER))
+USE_MAN(APPLET(man, _BB_DIR_SBIN, _BB_SUID_NEVER))
+USE_MATCHPATHCON(APPLET(matchpathcon, _BB_DIR_USR_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_MICROCOM(APPLET(microcom, _BB_DIR_USR_BIN, _BB_SUID_NEVER))
+USE_MKDIR(APPLET_NOFORK(mkdir, mkdir, _BB_DIR_BIN, _BB_SUID_NEVER, mkdir))
+USE_MKFS_VFAT(APPLET_ODDNAME(mkdosfs, mkfs_vfat, _BB_DIR_SBIN, _BB_SUID_NEVER, mkfs_vfat))
+//USE_MKE2FS(APPLET(mke2fs, _BB_DIR_SBIN, _BB_SUID_NEVER))
+USE_MKFIFO(APPLET(mkfifo, _BB_DIR_USR_BIN, _BB_SUID_NEVER))
+//USE_MKE2FS(APPLET_ODDNAME(mkfs.ext2, mke2fs, _BB_DIR_SBIN, _BB_SUID_NEVER, mkfs_ext2))
+//USE_MKE2FS(APPLET_ODDNAME(mkfs.ext3, mke2fs, _BB_DIR_SBIN, _BB_SUID_NEVER, mkfs_ext3))
+USE_MKFS_MINIX(APPLET_ODDNAME(mkfs.minix, mkfs_minix, _BB_DIR_SBIN, _BB_SUID_NEVER, mkfs_minix))
+USE_MKFS_VFAT(APPLET_ODDNAME(mkfs.vfat, mkfs_vfat, _BB_DIR_SBIN, _BB_SUID_NEVER, mkfs_vfat))
+USE_MKNOD(APPLET(mknod, _BB_DIR_BIN, _BB_SUID_NEVER))
+USE_CRYPTPW(APPLET_ODDNAME(mkpasswd, cryptpw, _BB_DIR_USR_BIN, _BB_SUID_NEVER, mkpasswd))
+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_MODPROBE_SMALL(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(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_PARSE(APPLET(parse, _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_PGREP(APPLET(pgrep, _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_MAYBE))
+USE_PIPE_PROGRESS(APPLET(pipe_progress, _BB_DIR_BIN, _BB_SUID_NEVER))
+USE_PIVOT_ROOT(APPLET(pivot_root, _BB_DIR_SBIN, _BB_SUID_NEVER))
+USE_PKILL(APPLET_ODDNAME(pkill, pgrep, _BB_DIR_USR_BIN, _BB_SUID_NEVER, pkill))
+USE_POPMAILDIR(APPLET(popmaildir, _BB_DIR_USR_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_NOFORK(printf, printf, _BB_DIR_USR_BIN, _BB_SUID_NEVER, printf))
+USE_PS(APPLET(ps, _BB_DIR_BIN, _BB_SUID_NEVER))
+USE_PSCAN(APPLET(pscan, _BB_DIR_USR_BIN, _BB_SUID_NEVER))
+USE_PWD(APPLET_NOFORK(pwd, pwd, _BB_DIR_BIN, _BB_SUID_NEVER, pwd))
+USE_RAIDAUTORUN(APPLET(raidautorun, _BB_DIR_SBIN, _BB_SUID_NEVER))
+USE_RDATE(APPLET(rdate, _BB_DIR_USR_SBIN, _BB_SUID_NEVER))
+USE_RDEV(APPLET(rdev, _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_REFORMIME(APPLET(reformime, _BB_DIR_BIN, _BB_SUID_NEVER))
+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_RESTORECON(APPLET_ODDNAME(restorecon, setfiles, _BB_DIR_SBIN, _BB_SUID_NEVER, restorecon))
+USE_RM(APPLET_NOFORK(rm, rm, _BB_DIR_BIN, _BB_SUID_NEVER, rm))
+USE_RMDIR(APPLET_NOFORK(rmdir, rmdir, _BB_DIR_BIN, _BB_SUID_NEVER, rmdir))
+USE_RMMOD(APPLET(rmmod, _BB_DIR_SBIN, _BB_SUID_NEVER))
+USE_MODPROBE_SMALL(APPLET_ODDNAME(rmmod, modprobe, _BB_DIR_SBIN, _BB_SUID_NEVER, modprobe))
+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_RTCWAKE(APPLET(rtcwake, _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_RUNCON(APPLET(runcon, _BB_DIR_USR_BIN, _BB_SUID_NEVER))
+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_SCRIPT(APPLET(script, _BB_DIR_USR_BIN, _BB_SUID_NEVER))
+USE_SED(APPLET(sed, _BB_DIR_BIN, _BB_SUID_NEVER))
+USE_SELINUXENABLED(APPLET(selinuxenabled, _BB_DIR_USR_SBIN, _BB_SUID_NEVER))
+USE_SENDMAIL(APPLET(sendmail, _BB_DIR_USR_SBIN, _BB_SUID_NEVER))
+USE_SEQ(APPLET_NOFORK(seq, seq, _BB_DIR_USR_BIN, _BB_SUID_NEVER, seq))
+USE_SESTATUS(APPLET(sestatus, _BB_DIR_USR_SBIN, _BB_SUID_NEVER))
+USE_SETARCH(APPLET(setarch, _BB_DIR_BIN, _BB_SUID_NEVER))
+USE_SETCONSOLE(APPLET(setconsole, _BB_DIR_SBIN, _BB_SUID_NEVER))
+USE_SETENFORCE(APPLET(setenforce, _BB_DIR_USR_SBIN, _BB_SUID_NEVER))
+USE_SETFILES(APPLET(setfiles, _BB_DIR_SBIN, _BB_SUID_NEVER))
+USE_SETFONT(APPLET(setfont, _BB_DIR_USR_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_SETSEBOOL(APPLET(setsebool, _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_ODDNAME(sh, ash, _BB_DIR_BIN, _BB_SUID_NEVER, sh))
+USE_FEATURE_SH_IS_HUSH(APPLET_ODDNAME(sh, hush, _BB_DIR_BIN, _BB_SUID_NEVER, sh))
+USE_FEATURE_SH_IS_MSH(APPLET_ODDNAME(sh, msh, _BB_DIR_BIN, _BB_SUID_NEVER, sh))
+USE_SHA1SUM(APPLET_ODDNAME(sha1sum, md5_sha1_sum, _BB_DIR_USR_BIN, _BB_SUID_NEVER, sha1sum))
+USE_SHA256SUM(APPLET_ODDNAME(sha256sum, md5_sha1_sum, _BB_DIR_USR_BIN, _BB_SUID_NEVER, sha256sum))
+USE_SHA512SUM(APPLET_ODDNAME(sha512sum, md5_sha1_sum, _BB_DIR_USR_BIN, _BB_SUID_NEVER, sha512sum))
+USE_SHOWKEY(APPLET(showkey, _BB_DIR_USR_BIN, _BB_SUID_NEVER))
+USE_SLATTACH(APPLET(slattach, _BB_DIR_SBIN, _BB_SUID_NEVER))
+USE_SLEEP(APPLET_NOFORK(sleep, sleep, _BB_DIR_BIN, _BB_SUID_NEVER, sleep))
+USE_SOFTLIMIT(APPLET_ODDNAME(softlimit, chpst, _BB_DIR_USR_BIN, _BB_SUID_NEVER, softlimit))
+USE_SORT(APPLET_NOEXEC(sort, sort, _BB_DIR_USR_BIN, _BB_SUID_NEVER, sort))
+USE_SPLIT(APPLET(split, _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_SBIN, _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_NOFORK(sync, sync, _BB_DIR_BIN, _BB_SUID_NEVER, sync))
+USE_BB_SYSCTL(APPLET(sysctl, _BB_DIR_SBIN, _BB_SUID_NEVER))
+USE_SYSLOGD(APPLET(syslogd, _BB_DIR_SBIN, _BB_SUID_NEVER))
+USE_TAC(APPLET_NOEXEC(tac, tac, _BB_DIR_USR_BIN, _BB_SUID_NEVER, tac))
+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_TC(APPLET(tc, _BB_DIR_SBIN, _BB_SUID_NEVER)) */
+USE_TCPSVD(APPLET_ODDNAME(tcpsvd, tcpudpsvd, _BB_DIR_USR_BIN, _BB_SUID_NEVER, tcpsvd))
+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_NOFORK(test, test, _BB_DIR_USR_BIN, _BB_SUID_NEVER, test))
+#if ENABLE_FEATURE_TFTP_GET || ENABLE_FEATURE_TFTP_PUT
+USE_TFTP(APPLET(tftp, _BB_DIR_USR_BIN, _BB_SUID_NEVER))
+USE_TFTPD(APPLET(tftpd, _BB_DIR_USR_BIN, _BB_SUID_NEVER))
+#endif
+USE_TIME(APPLET(time, _BB_DIR_USR_BIN, _BB_SUID_NEVER))
+USE_TIMEOUT(APPLET(timeout, _BB_DIR_USR_BIN, _BB_SUID_NEVER))
+USE_TOP(APPLET(top, _BB_DIR_USR_BIN, _BB_SUID_NEVER))
+USE_TOUCH(APPLET_NOFORK(touch, touch, _BB_DIR_BIN, _BB_SUID_NEVER, touch))
+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_NOFORK(true, true, _BB_DIR_BIN, _BB_SUID_NEVER, true))
+USE_TTY(APPLET(tty, _BB_DIR_USR_BIN, _BB_SUID_NEVER))
+USE_TTYSIZE(APPLET(ttysize, _BB_DIR_USR_BIN, _BB_SUID_NEVER))
+USE_TUNCTL(APPLET(tunctl, _BB_DIR_SBIN, _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_UDPSVD(APPLET_ODDNAME(udpsvd, tcpudpsvd, _BB_DIR_USR_BIN, _BB_SUID_NEVER, udpsvd))
+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_UNEXPAND(APPLET_ODDNAME(unexpand, expand, _BB_DIR_USR_BIN, _BB_SUID_NEVER, unexpand))
+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_NOFORK(usleep, usleep, _BB_DIR_BIN, _BB_SUID_NEVER, usleep))
+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_NOFORK(whoami, whoami, _BB_DIR_USR_BIN, _BB_SUID_NEVER, whoami))
+USE_XARGS(APPLET_NOEXEC(xargs, xargs, _BB_DIR_USR_BIN, _BB_SUID_NEVER, xargs))
+USE_YES(APPLET_NOFORK(yes, yes, _BB_DIR_USR_BIN, _BB_SUID_NEVER, yes))
+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(NAME_MAIN_CNAME) && !defined(MAKE_USAGE)
+};
+#endif
+
+#undef APPLET
+#undef APPLET_ODDNAME
+#undef APPLET_NOEXEC
+#undef APPLET_NOFORK
diff --git a/include/busybox.h b/include/busybox.h
new file mode 100644 (file)
index 0000000..54c278f
--- /dev/null
@@ -0,0 +1,74 @@
+/* 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 BUSYBOX_H
+#define BUSYBOX_H 1
+
+#include "libbb.h"
+
+PUSH_AND_SET_FUNCTION_VISIBILITY_TO_HIDDEN
+
+/* order matters: used as index into "install_dir[]" in appletlib.c */
+typedef enum bb_install_loc_t {
+       _BB_DIR_ROOT = 0,
+       _BB_DIR_BIN,
+       _BB_DIR_SBIN,
+       _BB_DIR_USR_BIN,
+       _BB_DIR_USR_SBIN
+} bb_install_loc_t;
+
+typedef enum bb_suid_t {
+       _BB_SUID_NEVER = 0,
+       _BB_SUID_MAYBE,
+       _BB_SUID_ALWAYS
+} bb_suid_t;
+
+
+/* Defined in appletlib.c (by including generated applet_tables.h) */
+/* Keep in sync with applets/applet_tables.c! */
+extern const char applet_names[];
+extern int (*const applet_main[])(int argc, char **argv);
+extern const uint16_t applet_nameofs[];
+extern const uint8_t applet_install_loc[];
+
+#if ENABLE_FEATURE_SUID || ENABLE_FEATURE_PREFER_APPLETS
+#define APPLET_NAME(i) (applet_names + (applet_nameofs[i] & 0x0fff))
+#else
+#define APPLET_NAME(i) (applet_names + applet_nameofs[i])
+#endif
+
+#if ENABLE_FEATURE_PREFER_APPLETS
+#define APPLET_IS_NOFORK(i) (applet_nameofs[i] & (1 << 12))
+#define APPLET_IS_NOEXEC(i) (applet_nameofs[i] & (1 << 13))
+#endif
+
+#if ENABLE_FEATURE_SUID
+#define APPLET_SUID(i) ((applet_nameofs[i] >> 14) & 0x3)
+#endif
+
+#if ENABLE_FEATURE_INSTALLER
+#define APPLET_INSTALL_LOC(i) ({ \
+       unsigned v = (i); \
+       if (v & 1) v = applet_install_loc[v/2] >> 4; \
+       else v = applet_install_loc[v/2] & 0xf; \
+       v; })
+#endif
+
+
+/* Length of these names has effect on size of libbusybox
+ * and "individual" binaries. Keep them short.
+ */
+#if ENABLE_BUILD_LIBBUSYBOX
+#if ENABLE_FEATURE_SHARED_BUSYBOX
+int lbb_main(char **argv) EXTERNALLY_VISIBLE;
+#else
+int lbb_main(char **argv);
+#endif
+#endif
+
+POP_SAVED_FUNCTION_VISIBILITY
+
+#endif
diff --git a/include/dump.h b/include/dump.h
new file mode 100644 (file)
index 0000000..925270d
--- /dev/null
@@ -0,0 +1,56 @@
+/* vi: set sw=4 ts=4: */
+
+PUSH_AND_SET_FUNCTION_VISIBILITY_TO_HIDDEN
+
+#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 dump_vflag_t { ALL, DUP, FIRST, WAIT };   /* -v values */
+
+typedef struct PR {
+       struct PR *nextpr;              /* next print unit */
+       unsigned 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 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;
+
+typedef struct dumper_t {
+       off_t dump_skip;                /* bytes to skip */
+       int dump_length;                /* max bytes to read */
+       smallint dump_vflag; /*enum dump_vflag_t*/
+       FS *fshead;
+} dumper_t;
+
+dumper_t* alloc_dumper(void) FAST_FUNC;
+extern void bb_dump_add(dumper_t *dumper, const char *fmt) FAST_FUNC;
+extern int bb_dump_dump(dumper_t *dumper, char **argv) FAST_FUNC;
+
+POP_SAVED_FUNCTION_VISIBILITY
diff --git a/include/grp_.h b/include/grp_.h
new file mode 100644 (file)
index 0000000..deaf9e6
--- /dev/null
@@ -0,0 +1,117 @@
+/* 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     <grp.h>
+ */
+#ifndef BB_GRP_H
+#define BB_GRP_H 1
+
+PUSH_AND_SET_FUNCTION_VISIBILITY_TO_HIDDEN
+
+/* This file is #included after #include <grp.h>
+ * We will use libc-defined structures, but will #define function names
+ * so that function calls are directed to bb_internal_XXX replacements
+ */
+
+#define setgrent     bb_internal_setgrent
+#define endgrent     bb_internal_endgrent
+#define getgrent     bb_internal_getgrent
+#define fgetgrent    bb_internal_fgetgrent
+#define putgrent     bb_internal_putgrent
+#define getgrgid     bb_internal_getgrgid
+#define getgrnam     bb_internal_getgrnam
+#define getgrent_r   bb_internal_getgrent_r
+#define getgrgid_r   bb_internal_getgrgid_r
+#define getgrnam_r   bb_internal_getgrnam_r
+#define fgetgrent_r  bb_internal_fgetgrent_r
+#define getgrouplist bb_internal_getgrouplist
+#define initgroups   bb_internal_initgroups
+
+
+/* All function names below should be remapped by #defines above
+ * in order to not collide with libc names. */
+
+
+/* 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);
+
+/* 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);
+
+POP_SAVED_FUNCTION_VISIBILITY
+
+#endif
diff --git a/include/inet_common.h b/include/inet_common.h
new file mode 100644 (file)
index 0000000..f4374e5
--- /dev/null
@@ -0,0 +1,26 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * stolen from net-tools-1.59 and stripped down for busybox by
+ *                      Erik Andersen <andersen@codepoet.org>
+ *
+ * Heavily modified by Manuel Novoa III       Mar 12, 2001
+ *
+ */
+
+#include "platform.h"
+
+/* hostfirst!=0 If we expect this to be a hostname,
+   try hostname database first
+ */
+int INET_resolve(const char *name, struct sockaddr_in *s_in, int hostfirst) FAST_FUNC;
+
+/* numeric: & 0x8000: "default" instead of "*",
+ *          & 0x4000: host instead of net,
+ *          & 0x0fff: don't resolve
+ */
+
+int INET6_resolve(const char *name, struct sockaddr_in6 *sin6) FAST_FUNC;
+
+/* These return malloced string */
+char *INET_rresolve(struct sockaddr_in *s_in, int numeric, uint32_t netmask) FAST_FUNC;
+char *INET6_rresolve(struct sockaddr_in6 *sin6, int numeric) FAST_FUNC;
diff --git a/include/libbb.h b/include/libbb.h
new file mode 100644 (file)
index 0000000..2497ffe
--- /dev/null
@@ -0,0 +1,1551 @@
+/* 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 LIBBB_H
+#define LIBBB_H 1
+
+#include "platform.h"
+
+#include <ctype.h>
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <netdb.h>
+#include <setjmp.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <string.h>
+#include <sys/poll.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <termios.h>
+#include <time.h>
+#include <unistd.h>
+#include <utime.h>
+/* Try to pull in PATH_MAX */
+#include <limits.h>
+#include <sys/param.h>
+#ifndef PATH_MAX
+#define PATH_MAX 256
+#endif
+
+#ifdef HAVE_MNTENT_H
+#include <mntent.h>
+#endif
+
+#ifdef HAVE_SYS_STATFS_H
+#include <sys/statfs.h>
+#endif
+
+#if ENABLE_SELINUX
+#include <selinux/selinux.h>
+#include <selinux/context.h>
+#include <selinux/flask.h>
+#include <selinux/av_permissions.h>
+#endif
+
+#if ENABLE_LOCALE_SUPPORT
+#include <locale.h>
+#else
+#define setlocale(x,y) ((void)0)
+#endif
+
+#ifdef DMALLOC
+#include <dmalloc.h>
+#endif
+
+#include <pwd.h>
+#include <grp.h>
+#if ENABLE_FEATURE_SHADOWPASSWDS
+# include <shadow.h>
+#endif
+
+/* Some libc's forget to declare these, do it ourself */
+
+extern char **environ;
+#if defined(__GLIBC__) && __GLIBC__ < 2
+int vdprintf(int d, const char *format, va_list ap);
+#endif
+/* klogctl is in libc's klog.h, but we cheat and not #include that */
+int klogctl(int type, char *b, int len);
+/* This is declared here rather than #including <libgen.h> 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.. */
+};
+int sysinfo(struct sysinfo* info);
+
+
+/* Make all declarations hidden (-fvisibility flag only affects definitions) */
+/* (don't include system headers after this until corresponding pop!) */
+PUSH_AND_SET_FUNCTION_VISIBILITY_TO_HIDDEN
+
+
+#if ENABLE_USE_BB_PWD_GRP
+# include "pwd_.h"
+# include "grp_.h"
+#endif
+#if ENABLE_FEATURE_SHADOWPASSWDS
+# if ENABLE_USE_BB_SHADOW
+#  include "shadow_.h"
+# endif
+#endif
+
+/* Tested to work correctly with all int types (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=y 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 */
+typedef unsigned long uoff_t;
+#  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" */
+typedef unsigned long long uoff_t;
+#  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... */
+typedef unsigned long uoff_t;
+#  define XATOOFF(a) xatoi_u(a)
+#  define BB_STRTOOFF bb_strtou
+#  define STRTOOFF strtol
+#  define OFF_FMT "l"
+# else
+typedef unsigned long uoff_t;
+#  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 */
+#if ENABLE_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
+#if ENABLE_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 defined(__GLIBC__)
+/* glibc uses __errno_location() to get a ptr to errno */
+/* We can just memorize it once - no multithreading in busybox :) */
+extern int *const bb_errno;
+#undef errno
+#define errno (*bb_errno)
+#endif
+
+unsigned long long monotonic_ns(void) FAST_FUNC;
+unsigned long long monotonic_us(void) FAST_FUNC;
+unsigned monotonic_sec(void) FAST_FUNC;
+
+extern void chomp(char *s) FAST_FUNC;
+extern void trim(char *s) FAST_FUNC;
+extern char *skip_whitespace(const char *) FAST_FUNC;
+extern char *skip_non_whitespace(const char *) FAST_FUNC;
+extern char *strrstr(const char *haystack, const char *needle) FAST_FUNC;
+
+//TODO: supply a pointer to char[11] buffer (avoid statics)?
+extern const char *bb_mode_string(mode_t mode) FAST_FUNC;
+extern int is_directory(const char *name, int followLinks, struct stat *statBuf) FAST_FUNC;
+enum { /* DO NOT CHANGE THESE VALUES!  cp.c, mv.c, install.c depend 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,
+       FILEUTILS_DEREF_SOFTLINK = 0x80,
+#if ENABLE_SELINUX
+       FILEUTILS_PRESERVE_SECURITY_CONTEXT = 0x100,
+       FILEUTILS_SET_SECURITY_CONTEXT = 0x200
+#endif
+};
+#define FILEUTILS_CP_OPTSTR "pdRfilsL" USE_SELINUX("c")
+extern int remove_file(const char *path, int flags) FAST_FUNC;
+/* NB: without FILEUTILS_RECUR in flags, it will basically "cat"
+ * the source, not copy (unless "source" is a directory).
+ * This makes "cp /dev/null file" and "install /dev/null file" (!!!)
+ * work coreutils-compatibly. */
+extern int copy_file(const char *source, const char *dest, int flags) FAST_FUNC;
+
+enum {
+       ACTION_RECURSE        = (1 << 0),
+       ACTION_FOLLOWLINKS    = (1 << 1),
+       ACTION_FOLLOWLINKS_L0 = (1 << 2),
+       ACTION_DEPTHFIRST     = (1 << 3),
+       /*ACTION_REVERSE      = (1 << 4), - unused */
+       ACTION_QUIET          = (1 << 5),
+};
+extern int recursive_action(const char *fileName, unsigned flags,
+       int FAST_FUNC (*fileAction)(const char *fileName, struct stat* statbuf, void* userData, int depth),
+       int FAST_FUNC (*dirAction)(const char *fileName, struct stat* statbuf, void* userData, int depth),
+       void* userData, unsigned depth) FAST_FUNC;
+extern int device_open(const char *device, int mode) FAST_FUNC;
+enum { GETPTY_BUFSIZE = 16 }; /* more than enough for "/dev/ttyXXX" */
+extern int xgetpty(char *line) FAST_FUNC;
+extern int get_console_fd_or_die(void) FAST_FUNC;
+extern void console_make_active(int fd, const int vt_num) FAST_FUNC;
+extern char *find_block_device(const char *path) FAST_FUNC;
+/* bb_copyfd_XX print read/write errors and return -1 if they occur */
+extern off_t bb_copyfd_eof(int fd1, int fd2) FAST_FUNC;
+extern off_t bb_copyfd_size(int fd1, int fd2, off_t size) FAST_FUNC;
+extern void bb_copyfd_exact_size(int fd1, int fd2, off_t size) FAST_FUNC;
+/* "short" copy can be detected by return value < size */
+/* this helper yells "short read!" if param is not -1 */
+extern void complain_copyfd_and_die(off_t sz) NORETURN FAST_FUNC;
+extern char bb_process_escape_sequence(const char **ptr) FAST_FUNC;
+/* xxxx_strip version can modify its parameter:
+ * "/"        -> "/"
+ * "abc"      -> "abc"
+ * "abc/def"  -> "def"
+ * "abc/def/" -> "def" !!
+ */
+extern char *bb_get_last_path_component_strip(char *path) FAST_FUNC;
+/* "abc/def/" -> "" and it never modifies 'path' */
+extern char *bb_get_last_path_component_nostrip(const char *path) FAST_FUNC;
+
+int ndelay_on(int fd) FAST_FUNC;
+int ndelay_off(int fd) FAST_FUNC;
+int close_on_exec_on(int fd) FAST_FUNC;
+void xdup2(int, int) FAST_FUNC;
+void xmove_fd(int, int) FAST_FUNC;
+
+
+DIR *xopendir(const char *path) FAST_FUNC;
+DIR *warn_opendir(const char *path) FAST_FUNC;
+
+/* UNUSED: char *xmalloc_realpath(const char *path) FAST_FUNC; */
+char *xmalloc_readlink(const char *path) FAST_FUNC;
+char *xmalloc_readlink_or_warn(const char *path) FAST_FUNC;
+char *xrealloc_getcwd_or_warn(char *cwd) FAST_FUNC;
+
+char *xmalloc_follow_symlinks(const char *path) FAST_FUNC;
+
+
+enum {
+       /* bb_signals(BB_FATAL_SIGS, handler) catches all signals which
+        * otherwise would kill us, except for those resulting from bugs:
+        * SIGSEGV, SIGILL, SIGFPE.
+        * Other fatal signals not included (TODO?):
+        * SIGBUS   Bus error (bad memory access)
+        * SIGPOLL  Pollable event. Synonym of SIGIO
+        * SIGPROF  Profiling timer expired
+        * SIGSYS   Bad argument to routine
+        * SIGTRAP  Trace/breakpoint trap
+        *
+        * The only known arch with some of these sigs not fitting
+        * into 32 bits is parisc (SIGXCPU=33, SIGXFSZ=34, SIGSTKFLT=36).
+        * Dance around with long long to guard against that...
+        */
+       BB_FATAL_SIGS = (int)(0
+               + (1LL << SIGHUP)
+               + (1LL << SIGINT)
+               + (1LL << SIGTERM)
+               + (1LL << SIGPIPE)   // Write to pipe with no readers
+               + (1LL << SIGQUIT)   // Quit from keyboard
+               + (1LL << SIGABRT)   // Abort signal from abort(3)
+               + (1LL << SIGALRM)   // Timer signal from alarm(2)
+               + (1LL << SIGVTALRM) // Virtual alarm clock
+               + (1LL << SIGXCPU)   // CPU time limit exceeded
+               + (1LL << SIGXFSZ)   // File size limit exceeded
+               + (1LL << SIGUSR1)   // Yes kids, these are also fatal!
+               + (1LL << SIGUSR2)
+               + 0),
+};
+void bb_signals(int sigs, void (*f)(int)) FAST_FUNC;
+/* Unlike signal() and bb_signals, sets handler with sigaction()
+ * and in a way that while signal handler is run, no other signals
+ * will be blocked; syscalls will not be restarted: */
+void bb_signals_recursive_norestart(int sigs, void (*f)(int)) FAST_FUNC;
+/* syscalls like read() will be interrupted with EINTR: */
+void signal_no_SA_RESTART_empty_mask(int sig, void (*handler)(int)) FAST_FUNC;
+/* syscalls like read() won't be interrupted (though select/poll will be): */
+void signal_SA_RESTART_empty_mask(int sig, void (*handler)(int)) FAST_FUNC;
+void wait_for_any_sig(void) FAST_FUNC;
+void kill_myself_with_sig(int sig) NORETURN FAST_FUNC;
+void sig_block(int sig) FAST_FUNC;
+void sig_unblock(int sig) FAST_FUNC;
+/* Will do sigaction(signum, act, NULL): */
+int sigaction_set(int sig, const struct sigaction *act) FAST_FUNC;
+/* SIG_BLOCK/SIG_UNBLOCK all signals: */
+int sigprocmask_allsigs(int how) FAST_FUNC;
+/* Standard handler which just records signo */
+extern smallint bb_got_signal;
+void record_signo(int signo); /* not FAST_FUNC! */
+
+
+void xsetgid(gid_t gid) FAST_FUNC;
+void xsetuid(uid_t uid) FAST_FUNC;
+void xchdir(const char *path) FAST_FUNC;
+void xchroot(const char *path) FAST_FUNC;
+void xsetenv(const char *key, const char *value) FAST_FUNC;
+void bb_unsetenv(const char *key) FAST_FUNC;
+void xunlink(const char *pathname) FAST_FUNC;
+void xstat(const char *pathname, struct stat *buf) FAST_FUNC;
+int xopen(const char *pathname, int flags) FAST_FUNC FAST_FUNC;
+int xopen3(const char *pathname, int flags, int mode) FAST_FUNC;
+int open_or_warn(const char *pathname, int flags) FAST_FUNC;
+int open3_or_warn(const char *pathname, int flags, int mode) FAST_FUNC;
+int open_or_warn_stdin(const char *pathname) FAST_FUNC;
+void xrename(const char *oldpath, const char *newpath) FAST_FUNC;
+int rename_or_warn(const char *oldpath, const char *newpath) FAST_FUNC;
+off_t xlseek(int fd, off_t offset, int whence) FAST_FUNC;
+off_t fdlength(int fd) FAST_FUNC;
+
+void xpipe(int filedes[2]) FAST_FUNC;
+/* In this form code with pipes is much more readable */
+struct fd_pair { int rd; int wr; };
+#define piped_pair(pair)  pipe(&((pair).rd))
+#define xpiped_pair(pair) xpipe(&((pair).rd))
+
+/* Useful for having small structure members/global variables */
+typedef int8_t socktype_t;
+typedef int8_t family_t;
+struct BUG_too_small {
+       char BUG_socktype_t_too_small[(0
+                       | SOCK_STREAM
+                       | SOCK_DGRAM
+                       | SOCK_RDM
+                       | SOCK_SEQPACKET
+                       | SOCK_RAW
+                       ) <= 127 ? 1 : -1];
+       char BUG_family_t_too_small[(0
+                       | AF_UNSPEC
+                       | AF_INET
+                       | AF_INET6
+                       | AF_UNIX
+#ifdef AF_PACKET
+                       | AF_PACKET
+#endif
+#ifdef AF_NETLINK
+                       | AF_NETLINK
+#endif
+                       /* | AF_DECnet */
+                       /* | AF_IPX */
+                       ) <= 127 ? 1 : -1];
+};
+
+
+int xsocket(int domain, int type, int protocol) FAST_FUNC;
+void xbind(int sockfd, struct sockaddr *my_addr, socklen_t addrlen) FAST_FUNC;
+void xlisten(int s, int backlog) FAST_FUNC;
+void xconnect(int s, const struct sockaddr *s_addr, socklen_t addrlen) FAST_FUNC;
+ssize_t xsendto(int s, const void *buf, size_t len, const struct sockaddr *to,
+                               socklen_t tolen) FAST_FUNC;
+/* SO_REUSEADDR allows a server to rebind to an address that is already
+ * "in use" by old connections to e.g. previous server instance which is
+ * killed or crashed. Without it bind will fail until all such connections
+ * time out. Linux does not allow multiple live binds on same ip:port
+ * regardless of SO_REUSEADDR (unlike some other flavors of Unix).
+ * Turn it on before you call bind(). */
+void setsockopt_reuseaddr(int fd) FAST_FUNC; /* On Linux this never fails. */
+int setsockopt_broadcast(int fd) FAST_FUNC;
+int setsockopt_bindtodevice(int fd, const char *iface) FAST_FUNC;
+/* NB: returns port in host byte order */
+unsigned bb_lookup_port(const char *port, const char *protocol, unsigned default_port) FAST_FUNC;
+typedef struct len_and_sockaddr {
+       socklen_t len;
+       union {
+               struct sockaddr sa;
+               struct sockaddr_in sin;
+#if ENABLE_FEATURE_IPV6
+               struct sockaddr_in6 sin6;
+#endif
+       } u;
+} len_and_sockaddr;
+enum {
+       LSA_LEN_SIZE = offsetof(len_and_sockaddr, u),
+       LSA_SIZEOF_SA = sizeof(
+               union {
+                       struct sockaddr sa;
+                       struct sockaddr_in sin;
+#if ENABLE_FEATURE_IPV6
+                       struct sockaddr_in6 sin6;
+#endif
+               }
+       )
+};
+/* Create stream socket, and allocate suitable lsa.
+ * (lsa of correct size and lsa->sa.sa_family (AF_INET/AF_INET6))
+ * af == AF_UNSPEC will result in trying to create IPv6 socket,
+ * and if kernel doesn't support it, IPv4.
+ */
+#if ENABLE_FEATURE_IPV6
+int xsocket_type(len_and_sockaddr **lsap, int af, int sock_type) FAST_FUNC;
+#else
+int xsocket_type(len_and_sockaddr **lsap, int sock_type) FAST_FUNC;
+#define xsocket_type(lsap, af, sock_type) xsocket_type((lsap), (sock_type))
+#endif
+int xsocket_stream(len_and_sockaddr **lsap) FAST_FUNC;
+/* Create server socket bound to bindaddr:port. bindaddr can be NULL,
+ * numeric IP ("N.N.N.N") or numeric IPv6 address,
+ * and can have ":PORT" suffix (for IPv6 use "[X:X:...:X]:PORT").
+ * Only if there is no suffix, port argument is used */
+/* NB: these set SO_REUSEADDR before bind */
+int create_and_bind_stream_or_die(const char *bindaddr, int port) FAST_FUNC;
+int create_and_bind_dgram_or_die(const char *bindaddr, int port) FAST_FUNC;
+/* Create client TCP socket connected to peer:port. Peer cannot be NULL.
+ * Peer can be numeric IP ("N.N.N.N"), numeric IPv6 address or hostname,
+ * and can have ":PORT" suffix (for IPv6 use "[X:X:...:X]:PORT").
+ * If there is no suffix, port argument is used */
+int create_and_connect_stream_or_die(const char *peer, int port) FAST_FUNC;
+/* Connect to peer identified by lsa */
+int xconnect_stream(const len_and_sockaddr *lsa) FAST_FUNC;
+/* Get local address of bound or accepted socket */
+len_and_sockaddr *get_sock_lsa(int fd) FAST_FUNC;
+/* Return malloc'ed len_and_sockaddr with socket address of host:port
+ * Currently will return IPv4 or IPv6 sockaddrs only
+ * (depending on host), but in theory nothing prevents e.g.
+ * UNIX socket address being returned, IPX sockaddr etc...
+ * On error does bb_error_msg and returns NULL */
+len_and_sockaddr* host2sockaddr(const char *host, int port) FAST_FUNC;
+/* Version which dies on error */
+len_and_sockaddr* xhost2sockaddr(const char *host, int port) FAST_FUNC;
+len_and_sockaddr* xdotted2sockaddr(const char *host, int port) FAST_FUNC;
+/* Same, useful if you want to force family (e.g. IPv6) */
+#if !ENABLE_FEATURE_IPV6
+#define host_and_af2sockaddr(host, port, af) host2sockaddr((host), (port))
+#define xhost_and_af2sockaddr(host, port, af) xhost2sockaddr((host), (port))
+#else
+len_and_sockaddr* host_and_af2sockaddr(const char *host, int port, sa_family_t af) FAST_FUNC;
+len_and_sockaddr* xhost_and_af2sockaddr(const char *host, int port, sa_family_t af) FAST_FUNC;
+#endif
+/* Assign sin[6]_port member if the socket is an AF_INET[6] one,
+ * otherwise no-op. Useful for ftp.
+ * NB: does NOT do htons() internally, just direct assignment. */
+void set_nport(len_and_sockaddr *lsa, unsigned port) FAST_FUNC;
+/* Retrieve sin[6]_port or return -1 for non-INET[6] lsa's */
+int get_nport(const struct sockaddr *sa) FAST_FUNC;
+/* Reverse DNS. Returns NULL on failure. */
+char* xmalloc_sockaddr2host(const struct sockaddr *sa) FAST_FUNC;
+/* This one doesn't append :PORTNUM */
+char* xmalloc_sockaddr2host_noport(const struct sockaddr *sa) FAST_FUNC;
+/* This one also doesn't fall back to dotted IP (returns NULL) */
+char* xmalloc_sockaddr2hostonly_noport(const struct sockaddr *sa) FAST_FUNC;
+/* inet_[ap]ton on steroids */
+char* xmalloc_sockaddr2dotted(const struct sockaddr *sa) FAST_FUNC;
+char* xmalloc_sockaddr2dotted_noport(const struct sockaddr *sa) FAST_FUNC;
+// "old" (ipv4 only) API
+// users: traceroute.c hostname.c - use _list_ of all IPs
+struct hostent *xgethostbyname(const char *name) FAST_FUNC;
+// Also mount.c and inetd.c are using gethostbyname(),
+// + inet_common.c has additional IPv4-only stuff
+
+
+void socket_want_pktinfo(int fd) FAST_FUNC;
+ssize_t send_to_from(int fd, void *buf, size_t len, int flags,
+               const struct sockaddr *to,
+               const struct sockaddr *from,
+               socklen_t tolen) FAST_FUNC;
+ssize_t recv_from_to(int fd, void *buf, size_t len, int flags,
+               struct sockaddr *from,
+               struct sockaddr *to,
+               socklen_t sa_size) FAST_FUNC;
+
+char *xstrdup(const char *s) FAST_FUNC;
+char *xstrndup(const char *s, int n) FAST_FUNC;
+void overlapping_strcpy(char *dst, const char *src) FAST_FUNC;
+char *safe_strncpy(char *dst, const char *src, size_t size) FAST_FUNC;
+char *strncpy_IFNAMSIZ(char *dst, const char *src) FAST_FUNC;
+/* Guaranteed to NOT be a macro (smallest code). Saves nearly 2k on uclibc.
+ * But potentially slow, don't use in one-billion-times loops */
+int bb_putchar(int ch) FAST_FUNC;
+char *xasprintf(const char *format, ...) __attribute__ ((format (printf, 1, 2))) FAST_FUNC;
+/* Prints unprintable chars ch as ^C or M-c to file
+ * (M-c is used only if ch is ORed with PRINTABLE_META),
+ * else it is printed as-is (except for ch = 0x9b) */
+enum { PRINTABLE_META = 0x100 };
+void fputc_printable(int ch, FILE *file) FAST_FUNC;
+// gcc-4.1.1 still isn't good enough at optimizing it
+// (+200 bytes compared to macro)
+//static ALWAYS_INLINE
+//int LONE_DASH(const char *s) { return s[0] == '-' && !s[1]; }
+//static ALWAYS_INLINE
+//int NOT_LONE_DASH(const char *s) { return s[0] != '-' || s[1]; }
+#define LONE_DASH(s)     ((s)[0] == '-' && !(s)[1])
+#define NOT_LONE_DASH(s) ((s)[0] != '-' || (s)[1])
+#define LONE_CHAR(s,c)     ((s)[0] == (c) && !(s)[1])
+#define NOT_LONE_CHAR(s,c) ((s)[0] != (c) || (s)[1])
+#define DOT_OR_DOTDOT(s) ((s)[0] == '.' && (!(s)[1] || ((s)[1] == '.' && !(s)[2])))
+
+/* dmalloc will redefine these to it's own implementation. It is safe
+ * to have the prototypes here unconditionally.  */
+void *malloc_or_warn(size_t size) FAST_FUNC;
+void *xmalloc(size_t size) FAST_FUNC;
+void *xzalloc(size_t size) FAST_FUNC;
+void *xrealloc(void *old, size_t size) FAST_FUNC;
+/* After xrealloc_vector(v, 4, idx) it's ok to use
+ * at least v[idx] and v[idx+1], for all idx values.
+ * shift specifies how many new elements are added (1: 2, 2: 4... 8: 256...)
+ * when all elements are used up. New elements are zeroed out. */
+#define xrealloc_vector(vector, shift, idx) \
+       xrealloc_vector_helper((vector), (sizeof((vector)[0]) << 8) + (shift), (idx))
+void* xrealloc_vector_helper(void *vector, unsigned sizeof_and_shift, int idx) FAST_FUNC;
+
+
+extern ssize_t safe_read(int fd, void *buf, size_t count) FAST_FUNC;
+extern ssize_t nonblock_safe_read(int fd, void *buf, size_t count) FAST_FUNC;
+// NB: will return short read on error, not -1,
+// if some data was read before error occurred
+extern ssize_t full_read(int fd, void *buf, size_t count) FAST_FUNC;
+extern void xread(int fd, void *buf, size_t count) FAST_FUNC;
+extern unsigned char xread_char(int fd) FAST_FUNC;
+extern ssize_t read_close(int fd, void *buf, size_t maxsz) FAST_FUNC;
+extern ssize_t open_read_close(const char *filename, void *buf, size_t maxsz) FAST_FUNC;
+// Reads one line a-la fgets (but doesn't save terminating '\n').
+// Reads byte-by-byte. Useful when it is important to not read ahead.
+// Bytes are appended to pfx (which must be malloced, or NULL).
+extern char *xmalloc_reads(int fd, char *pfx, size_t *maxsz_p) FAST_FUNC;
+/* Reads block up to *maxsz_p (default: INT_MAX - 4095) */
+extern void *xmalloc_read(int fd, size_t *maxsz_p) FAST_FUNC;
+/* Returns NULL if file can't be opened (default max size: INT_MAX - 4095) */
+extern void *xmalloc_open_read_close(const char *filename, size_t *maxsz_p) FAST_FUNC;
+/* Autodetects .gz etc */
+extern int open_zipped(const char *fname) FAST_FUNC;
+extern void *xmalloc_open_zipped_read_close(const char *fname, size_t *maxsz_p) FAST_FUNC;
+/* Never returns NULL */
+extern void *xmalloc_xopen_read_close(const char *filename, size_t *maxsz_p) FAST_FUNC;
+
+extern ssize_t safe_write(int fd, const void *buf, size_t count) FAST_FUNC;
+// NB: will return short write on error, not -1,
+// if some data was written before error occurred
+extern ssize_t full_write(int fd, const void *buf, size_t count) FAST_FUNC;
+extern void xwrite(int fd, const void *buf, size_t count) FAST_FUNC;
+extern void xwrite_str(int fd, const char *str) FAST_FUNC;
+extern void xopen_xwrite_close(const char* file, const char *str) FAST_FUNC;
+
+/* Reads and prints to stdout till eof, then closes FILE. Exits on error: */
+extern void xprint_and_close_file(FILE *file) FAST_FUNC;
+
+extern char *bb_get_chunk_from_file(FILE *file, int *end) FAST_FUNC;
+extern char *bb_get_chunk_with_continuation(FILE *file, int *end, int *lineno) FAST_FUNC;
+/* Reads up to (and including) TERMINATING_STRING: */
+extern char *xmalloc_fgets_str(FILE *file, const char *terminating_string) FAST_FUNC;
+/* Same, with limited max size, and returns the length (excluding NUL): */
+extern char *xmalloc_fgets_str_len(FILE *file, const char *terminating_string, size_t *maxsz_p) FAST_FUNC;
+/* Chops off TERMINATING_STRING from the end: */
+extern char *xmalloc_fgetline_str(FILE *file, const char *terminating_string) FAST_FUNC;
+/* Reads up to (and including) "\n" or NUL byte: */
+extern char *xmalloc_fgets(FILE *file) FAST_FUNC;
+/* Chops off '\n' from the end, unlike fgets: */
+extern char *xmalloc_fgetline(FILE *file) FAST_FUNC;
+/* Same, but doesn't try to conserve space (may have some slack after the end) */
+/* extern char *xmalloc_fgetline_fast(FILE *file) FAST_FUNC; */
+
+extern void die_if_ferror(FILE *file, const char *msg) FAST_FUNC;
+extern void die_if_ferror_stdout(void) FAST_FUNC;
+extern void xfflush_stdout(void) FAST_FUNC;
+extern void fflush_stdout_and_exit(int retval) NORETURN FAST_FUNC;
+extern int fclose_if_not_stdin(FILE *file) FAST_FUNC;
+extern FILE *xfopen(const char *filename, const char *mode) FAST_FUNC;
+/* Prints warning to stderr and returns NULL on failure: */
+extern FILE *fopen_or_warn(const char *filename, const char *mode) FAST_FUNC;
+/* "Opens" stdin if filename is special, else just opens file: */
+extern FILE *xfopen_stdin(const char *filename) FAST_FUNC;
+extern FILE *fopen_or_warn_stdin(const char *filename) FAST_FUNC;
+extern FILE* fopen_for_read(const char *path) FAST_FUNC;
+extern FILE* xfopen_for_read(const char *path) FAST_FUNC;
+extern FILE* fopen_for_write(const char *path) FAST_FUNC;
+extern FILE* xfopen_for_write(const char *path) FAST_FUNC;
+
+int bb_pstrcmp(const void *a, const void *b) /* not FAST_FUNC! */;
+void qsort_string_vector(char **sv, unsigned count) FAST_FUNC;
+
+/* Wrapper which restarts poll on EINTR or ENOMEM.
+ * On other errors complains [perror("poll")] and returns.
+ * Warning! May take (much) longer than timeout_ms to return!
+ * If this is a problem, use bare poll and open-code EINTR/ENOMEM handling */
+int safe_poll(struct pollfd *ufds, nfds_t nfds, int timeout_ms) FAST_FUNC;
+
+char *safe_gethostname(void) FAST_FUNC;
+char *safe_getdomainname(void) FAST_FUNC;
+
+/* Convert each alpha char in str to lower-case */
+char* str_tolower(char *str) FAST_FUNC;
+
+char *utoa(unsigned n) FAST_FUNC;
+char *itoa(int n) FAST_FUNC;
+/* Returns a pointer past the formatted number, does NOT null-terminate */
+char *utoa_to_buf(unsigned n, char *buf, unsigned buflen) FAST_FUNC;
+char *itoa_to_buf(int n, char *buf, unsigned buflen) FAST_FUNC;
+/* Intelligent formatters of bignums */
+void smart_ulltoa4(unsigned long long ul, char buf[5], const char *scale) FAST_FUNC;
+void smart_ulltoa5(unsigned long long ul, char buf[5], const char *scale) FAST_FUNC;
+//TODO: provide pointer to buf (avoid statics)?
+const char *make_human_readable_str(unsigned long long size,
+               unsigned long block_size, unsigned long display_unit) FAST_FUNC;
+/* Put a string of hex bytes ("1b2e66fe"...), return advanced pointer */
+char *bin2hex(char *buf, const char *cp, int count) FAST_FUNC;
+
+/* Last element is marked by mult == 0 */
+struct suffix_mult {
+       char suffix[4];
+       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) FAST_FUNC;
+/* Useful for reading port numbers */
+uint16_t xatou16(const char *numstr) FAST_FUNC;
+
+
+/* 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.  */
+long xuname2uid(const char *name) FAST_FUNC;
+long xgroup2gid(const char *name) FAST_FUNC;
+/* wrapper: allows string to contain numeric uid or gid */
+unsigned long get_ug_id(const char *s, long FAST_FUNC (*xname2id)(const char *)) FAST_FUNC;
+/* from chpst. Does not die, returns 0 on failure */
+struct bb_uidgid_t {
+       uid_t uid;
+       gid_t gid;
+};
+/* always sets uid and gid */
+int get_uidgid(struct bb_uidgid_t*, const char*, int numeric_ok) FAST_FUNC;
+/* always sets uid and gid, allows numeric; exits on failure */
+void xget_uidgid(struct bb_uidgid_t*, const char*) FAST_FUNC;
+/* chown-like handling of "user[:[group]" */
+void parse_chown_usergroup_or_die(struct bb_uidgid_t *u, char *user_group) FAST_FUNC;
+struct passwd* xgetpwnam(const char *name) FAST_FUNC;
+struct group* xgetgrnam(const char *name) FAST_FUNC;
+struct passwd* xgetpwuid(uid_t uid) FAST_FUNC;
+struct group* xgetgrgid(gid_t gid) FAST_FUNC;
+char* xuid2uname(uid_t uid) FAST_FUNC;
+char* xgid2group(gid_t gid) FAST_FUNC;
+char* uid2uname(uid_t uid) FAST_FUNC;
+char* gid2group(gid_t gid) FAST_FUNC;
+char* uid2uname_utoa(long uid) FAST_FUNC;
+char* gid2group_utoa(long gid) FAST_FUNC;
+/* versions which cache results (useful for ps, ls etc) */
+const char* get_cached_username(uid_t uid) FAST_FUNC;
+const char* get_cached_groupname(gid_t gid) FAST_FUNC;
+void clear_username_cache(void) FAST_FUNC;
+/* internally usernames are saved in fixed-sized char[] buffers */
+enum { USERNAME_MAX_SIZE = 16 - sizeof(int) };
+#if ENABLE_FEATURE_CHECK_NAMES
+void die_if_bad_username(const char* name) FAST_FUNC;
+#else
+#define die_if_bad_username(name) ((void)(name))
+#endif
+
+int execable_file(const char *name) FAST_FUNC;
+char *find_execable(const char *filename, char **PATHp) FAST_FUNC;
+int exists_execable(const char *filename) FAST_FUNC;
+
+/* BB_EXECxx always execs (it's not doing NOFORK/NOEXEC stuff),
+ * but it may exec busybox and call applet instead of searching PATH.
+ */
+#if ENABLE_FEATURE_PREFER_APPLETS
+int bb_execvp(const char *file, char *const argv[]) FAST_FUNC;
+#define BB_EXECVP(prog,cmd) bb_execvp(prog,cmd)
+#define BB_EXECLP(prog,cmd,...) \
+       execlp((find_applet_by_name(prog) >= 0) ? CONFIG_BUSYBOX_EXEC_PATH : prog, \
+               cmd, __VA_ARGS__)
+#else
+#define BB_EXECVP(prog,cmd)     execvp(prog,cmd)
+#define BB_EXECLP(prog,cmd,...) execlp(prog,cmd, __VA_ARGS__)
+#endif
+
+/* NOMMU friendy fork+exec */
+pid_t spawn(char **argv) FAST_FUNC;
+pid_t xspawn(char **argv) FAST_FUNC;
+
+pid_t safe_waitpid(pid_t pid, int *wstat, int options) FAST_FUNC;
+/* Unlike waitpid, waits ONLY for one process.
+ * It's safe to pass negative 'pids' from failed [v]fork -
+ * wait4pid will return -1 (and will not clobber [v]fork's errno).
+ * IOW: rc = wait4pid(spawn(argv));
+ *      if (rc < 0) bb_perror_msg("%s", argv[0]);
+ *      if (rc > 0) bb_error_msg("exit code: %d", rc);
+ */
+int wait4pid(pid_t pid) FAST_FUNC;
+pid_t wait_any_nohang(int *wstat) FAST_FUNC;
+#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)
+/* wait4pid(spawn(argv)) + NOFORK/NOEXEC (if configured) */
+pid_t spawn_and_wait(char **argv) FAST_FUNC;
+struct nofork_save_area {
+       jmp_buf die_jmp;
+       const char *applet_name;
+       int xfunc_error_retval;
+       uint32_t option_mask32;
+       int die_sleep;
+       smallint saved;
+};
+void save_nofork_data(struct nofork_save_area *save) FAST_FUNC;
+void restore_nofork_data(struct nofork_save_area *save) FAST_FUNC;
+/* Does NOT check that applet is NOFORK, just blindly runs it */
+int run_nofork_applet(int applet_no, char **argv) FAST_FUNC;
+int run_nofork_applet_prime(struct nofork_save_area *old, int applet_no, char **argv) FAST_FUNC;
+
+/* Helpers for daemonization.
+ *
+ * bb_daemonize(flags) = daemonize, does not compile on NOMMU
+ *
+ * bb_daemonize_or_rexec(flags, argv) = daemonizes on MMU (and ignores argv),
+ *      rexec's itself on NOMMU with argv passed as command line.
+ * Thus bb_daemonize_or_rexec may cause your <applet>_main() to be re-executed
+ * from the start. (It will detect it and not reexec again second time).
+ * You have to audit carefully that you don't do something twice as a result
+ * (opening files/sockets, parsing config files etc...)!
+ *
+ * Both of the above will redirect fd 0,1,2 to /dev/null and drop ctty
+ * (will do setsid()).
+ *
+ * fork_or_rexec(argv) = bare-bones "fork" on MMU,
+ *      "vfork + re-exec ourself" on NOMMU. No fd redirection, no setsid().
+ *      On MMU ignores argv.
+ *
+ * Helper for network daemons in foreground mode:
+ *
+ * bb_sanitize_stdio() = make sure that fd 0,1,2 are opened by opening them
+ * to /dev/null if they are not.
+ */
+enum {
+       DAEMON_CHDIR_ROOT = 1,
+       DAEMON_DEVNULL_STDIO = 2,
+       DAEMON_CLOSE_EXTRA_FDS = 4,
+       DAEMON_ONLY_SANITIZE = 8, /* internal use */
+};
+#if BB_MMU
+  pid_t fork_or_rexec(void) FAST_FUNC;
+  enum { re_execed = 0 };
+# define fork_or_rexec(argv)                fork_or_rexec()
+# define bb_daemonize_or_rexec(flags, argv) bb_daemonize_or_rexec(flags)
+# define bb_daemonize(flags)                bb_daemonize_or_rexec(flags, bogus)
+#else
+  void re_exec(char **argv) NORETURN FAST_FUNC;
+  pid_t fork_or_rexec(char **argv) FAST_FUNC;
+  extern bool re_execed;
+  int  BUG_fork_is_unavailable_on_nommu(void) FAST_FUNC;
+  int  BUG_daemon_is_unavailable_on_nommu(void) FAST_FUNC;
+  void BUG_bb_daemonize_is_unavailable_on_nommu(void) FAST_FUNC;
+# define fork()          BUG_fork_is_unavailable_on_nommu()
+# define daemon(a,b)     BUG_daemon_is_unavailable_on_nommu()
+# define bb_daemonize(a) BUG_bb_daemonize_is_unavailable_on_nommu()
+#endif
+void bb_daemonize_or_rexec(int flags, char **argv) FAST_FUNC;
+void bb_sanitize_stdio(void) FAST_FUNC;
+/* Clear dangerous stuff, set PATH. Return 1 if was run by different user. */
+int sanitize_env_if_suid(void) FAST_FUNC;
+
+
+extern const char *const bb_argv_dash[]; /* "-", NULL */
+extern const char *opt_complementary;
+#if ENABLE_GETOPT_LONG
+#define No_argument "\0"
+#define Required_argument "\001"
+#define Optional_argument "\002"
+extern const char *applet_long_options;
+#endif
+extern uint32_t option_mask32;
+extern uint32_t getopt32(char **argv, const char *applet_opts, ...) FAST_FUNC;
+
+
+typedef struct llist_t {
+       char *data;
+       struct llist_t *link;
+} llist_t;
+void llist_add_to(llist_t **old_head, void *data) FAST_FUNC;
+void llist_add_to_end(llist_t **list_head, void *data) FAST_FUNC;
+void *llist_pop(llist_t **elm) FAST_FUNC;
+void llist_unlink(llist_t **head, llist_t *elm) FAST_FUNC;
+void llist_free(llist_t *elm, void (*freeit)(void *data)) FAST_FUNC;
+llist_t *llist_rev(llist_t *list) FAST_FUNC;
+llist_t *llist_find_str(llist_t *first, const char *str) FAST_FUNC;
+/* BTW, surprisingly, changing API to
+ *   llist_t *llist_add_to(llist_t *old_head, void *data)
+ * etc does not result in smaller code... */
+
+/* start_stop_daemon and udhcpc are special - they want
+ * to create pidfiles regardless of FEATURE_PIDFILE */
+#if ENABLE_FEATURE_PIDFILE || defined(WANT_PIDFILE)
+/* True only if we created pidfile which is *file*, not /dev/null etc */
+extern smallint wrote_pidfile;
+void write_pidfile(const char *path) FAST_FUNC;
+#define remove_pidfile(path) do { if (wrote_pidfile) unlink(path); } while (0)
+#else
+enum { wrote_pidfile = 0 };
+#define write_pidfile(path)  ((void)0)
+#define remove_pidfile(path) ((void)0)
+#endif
+
+enum {
+       LOGMODE_NONE = 0,
+       LOGMODE_STDIO = (1 << 0),
+       LOGMODE_SYSLOG = (1 << 1) * ENABLE_FEATURE_SYSLOG,
+       LOGMODE_BOTH = LOGMODE_SYSLOG + LOGMODE_STDIO,
+};
+extern const char *msg_eol;
+extern smallint logmode;
+extern int die_sleep;
+extern int xfunc_error_retval;
+extern jmp_buf die_jmp;
+extern void xfunc_die(void) NORETURN FAST_FUNC;
+extern void bb_show_usage(void) NORETURN FAST_FUNC;
+extern void bb_error_msg(const char *s, ...) __attribute__ ((format (printf, 1, 2))) FAST_FUNC;
+extern void bb_error_msg_and_die(const char *s, ...) __attribute__ ((noreturn, format (printf, 1, 2))) FAST_FUNC;
+extern void bb_perror_msg(const char *s, ...) __attribute__ ((format (printf, 1, 2))) FAST_FUNC;
+extern void bb_simple_perror_msg(const char *s) FAST_FUNC;
+extern void bb_perror_msg_and_die(const char *s, ...) __attribute__ ((noreturn, format (printf, 1, 2))) FAST_FUNC;
+extern void bb_simple_perror_msg_and_die(const char *s) NORETURN FAST_FUNC;
+extern void bb_herror_msg(const char *s, ...) __attribute__ ((format (printf, 1, 2))) FAST_FUNC;
+extern void bb_herror_msg_and_die(const char *s, ...) __attribute__ ((noreturn, format (printf, 1, 2))) FAST_FUNC;
+extern void bb_perror_nomsg_and_die(void) NORETURN FAST_FUNC;
+extern void bb_perror_nomsg(void) FAST_FUNC;
+extern void bb_info_msg(const char *s, ...) __attribute__ ((format (printf, 1, 2))) FAST_FUNC;
+extern void bb_verror_msg(const char *s, va_list p, const char *strerr) FAST_FUNC;
+
+/* We need to export XXX_main from libbusybox
+ * only if we build "individual" binaries
+ */
+#if ENABLE_FEATURE_INDIVIDUAL
+#define MAIN_EXTERNALLY_VISIBLE EXTERNALLY_VISIBLE
+#else
+#define MAIN_EXTERNALLY_VISIBLE
+#endif
+
+
+/* Applets which are useful from another applets */
+int bb_cat(char** argv);
+/* If shell needs them, they exist even if not enabled as applets */
+int echo_main(int argc, char** argv) USE_ECHO(MAIN_EXTERNALLY_VISIBLE);
+int printf_main(int argc, char **argv) USE_PRINTF(MAIN_EXTERNALLY_VISIBLE);
+int test_main(int argc, char **argv) USE_TEST(MAIN_EXTERNALLY_VISIBLE);
+int kill_main(int argc, char **argv) USE_KILL(MAIN_EXTERNALLY_VISIBLE);
+/* Similar, but used by chgrp, not shell */
+int chown_main(int argc, char **argv) USE_CHOWN(MAIN_EXTERNALLY_VISIBLE);
+/* Used by ftpd */
+int ls_main(int argc, char **argv) USE_LS(MAIN_EXTERNALLY_VISIBLE);
+/* Don't need USE_xxx() guard for these */
+int gunzip_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int bunzip2_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+
+#if ENABLE_ROUTE
+void bb_displayroutes(int noresolve, int netstatfmt) FAST_FUNC;
+#endif
+
+
+/* "Keycodes" that report an escape sequence.
+ * We use something which fits into signed char,
+ * yet doesn't represent any valid Unicode characher.
+ * Also, -1 is reserved for error indication and we don't use it. */
+enum {
+       KEYCODE_UP       =  -2,
+       KEYCODE_DOWN     =  -3,
+       KEYCODE_RIGHT    =  -4,
+       KEYCODE_LEFT     =  -5,
+       KEYCODE_HOME     =  -6,
+       KEYCODE_END      =  -7,
+       KEYCODE_INSERT   =  -8,
+       KEYCODE_DELETE   =  -9,
+       KEYCODE_PAGEUP   = -10,
+       KEYCODE_PAGEDOWN = -11,
+#if 0
+       KEYCODE_FUN1     = -12,
+       KEYCODE_FUN2     = -13,
+       KEYCODE_FUN3     = -14,
+       KEYCODE_FUN4     = -15,
+       KEYCODE_FUN5     = -16,
+       KEYCODE_FUN6     = -17,
+       KEYCODE_FUN7     = -18,
+       KEYCODE_FUN8     = -19,
+       KEYCODE_FUN9     = -20,
+       KEYCODE_FUN10    = -21,
+       KEYCODE_FUN11    = -22,
+       KEYCODE_FUN12    = -23,
+#endif
+       /* How long the longest ESC sequence we know? */
+       KEYCODE_BUFFER_SIZE = 4
+};
+/* Note: fd may be in blocking or non-blocking mode, both make sense.
+ * For one, less uses non-blocking mode.
+ * Only the first read syscall inside read_key may block indefinitely
+ * (unless fd is in non-blocking mode),
+ * subsequent reads will time out after a few milliseconds.
+ */
+int read_key(int fd, smalluint *nbuffered, char *buffer) FAST_FUNC;
+
+
+/* Networking */
+int create_icmp_socket(void) FAST_FUNC;
+int create_icmp6_socket(void) FAST_FUNC;
+/* interface.c */
+/* This structure defines protocol families and their handlers. */
+struct aftype {
+       const char *name;
+       const char *title;
+       int af;
+       int alen;
+       char*       FAST_FUNC (*print)(unsigned char *);
+       const char* FAST_FUNC (*sprint)(struct sockaddr *, int numeric);
+       int         FAST_FUNC (*input)(/*int type,*/ const char *bufp, struct sockaddr *);
+       void        FAST_FUNC (*herror)(char *text);
+       int         FAST_FUNC (*rprint)(int options);
+       int         FAST_FUNC (*rinput)(int typ, int ext, char **argv);
+       /* may modify src */
+       int         FAST_FUNC (*getmask)(char *src, struct sockaddr *mask, char *name);
+};
+/* This structure defines hardware protocols and their handlers. */
+struct hwtype {
+       const char *name;
+       const char *title;
+       int type;
+       int alen;
+       char* FAST_FUNC (*print)(unsigned char *);
+       int   FAST_FUNC (*input)(const char *, struct sockaddr *);
+       int   FAST_FUNC (*activate)(int fd);
+       int suppress_null_addr;
+};
+extern smallint interface_opt_a;
+int display_interfaces(char *ifname) FAST_FUNC;
+#if ENABLE_FEATURE_HWIB
+int in_ib(const char *bufp, struct sockaddr *sap) FAST_FUNC;
+#else
+#define in_ib(a, b) 1 /* fail */
+#endif
+const struct aftype *get_aftype(const char *name) FAST_FUNC;
+const struct hwtype *get_hwtype(const char *name) FAST_FUNC;
+const struct hwtype *get_hwntype(int type) FAST_FUNC;
+
+
+#ifndef BUILD_INDIVIDUAL
+extern int find_applet_by_name(const char *name) FAST_FUNC;
+/* Returns only if applet is not found. */
+extern void run_applet_and_exit(const char *name, char **argv) FAST_FUNC;
+extern void run_applet_no_and_exit(int a, char **argv) NORETURN FAST_FUNC;
+#endif
+
+#ifdef HAVE_MNTENT_H
+extern int match_fstype(const struct mntent *mt, const char *fstypes) FAST_FUNC;
+extern struct mntent *find_mount_point(const char *name) FAST_FUNC;
+#endif
+extern void erase_mtab(const char * name) FAST_FUNC;
+extern unsigned int tty_baud_to_value(speed_t speed) FAST_FUNC;
+extern speed_t tty_value_to_baud(unsigned int value) FAST_FUNC;
+extern void bb_warn_ignoring_args(int n) FAST_FUNC;
+
+extern int get_linux_version_code(void) FAST_FUNC;
+
+extern char *query_loop(const char *device) FAST_FUNC;
+extern int del_loop(const char *device) FAST_FUNC;
+/* If *devname is not NULL, use that name, otherwise try to find free one,
+ * malloc and return it in *devname.
+ * return value: 1: read-only loopdev was setup, 0: rw, < 0: error */
+extern int set_loop(char **devname, const char *file, unsigned long long offset) FAST_FUNC;
+
+/* Like bb_ask below, but asks on stdin with no timeout.  */
+char *bb_ask_stdin(const char * prompt) FAST_FUNC;
+//TODO: pass buf pointer or return allocated buf (avoid statics)?
+char *bb_ask(const int fd, int timeout, const char * prompt) FAST_FUNC;
+int bb_ask_confirmation(void) FAST_FUNC;
+
+int bb_parse_mode(const char* s, mode_t* theMode) FAST_FUNC;
+
+/*
+ * Config file parser
+ */
+enum {
+       PARSE_COLLAPSE  = 0x00010000, // treat consecutive delimiters as one
+       PARSE_TRIM      = 0x00020000, // trim leading and trailing delimiters
+// TODO: COLLAPSE and TRIM seem to always go in pair
+       PARSE_GREEDY    = 0x00040000, // last token takes entire remainder of the line
+       PARSE_MIN_DIE   = 0x00100000, // die if < min tokens found
+       // keep a copy of current line
+       PARSE_KEEP_COPY = 0x00200000 * ENABLE_FEATURE_CROND_D,
+//     PARSE_ESCAPE    = 0x00400000, // process escape sequences in tokens
+       // NORMAL is:
+       // * remove leading and trailing delimiters and collapse
+       //   multiple delimiters into one
+       // * warn and continue if less than mintokens delimiters found
+       // * grab everything into last token
+       PARSE_NORMAL    = PARSE_COLLAPSE | PARSE_TRIM | PARSE_GREEDY,
+};
+typedef struct parser_t {
+       FILE *fp;
+       char *line;
+       char *data;
+       int lineno;
+} parser_t;
+parser_t* config_open(const char *filename) FAST_FUNC;
+parser_t* config_open2(const char *filename, FILE* FAST_FUNC (*fopen_func)(const char *path)) FAST_FUNC;
+int config_read(parser_t *parser, char **tokens, unsigned flags, const char *delims) FAST_FUNC;
+#define config_read(parser, tokens, max, min, str, flags) \
+       config_read(parser, tokens, ((flags) | (((min) & 0xFF) << 8) | ((max) & 0xFF)), str)
+void config_close(parser_t *parser) FAST_FUNC;
+
+/* Concatenate path and filename to new allocated buffer.
+ * Add "/" only as needed (no duplicate "//" are produced).
+ * If path is NULL, it is assumed to be "/".
+ * filename should not be NULL. */
+char *concat_path_file(const char *path, const char *filename) FAST_FUNC;
+char *concat_subpath_file(const char *path, const char *filename) FAST_FUNC;
+const char *bb_basename(const char *name) FAST_FUNC;
+/* NB: can violate const-ness (similarly to strchr) */
+char *last_char_is(const char *s, int c) FAST_FUNC;
+
+
+int bb_make_directory(char *path, long mode, int flags) FAST_FUNC;
+
+int get_signum(const char *name) FAST_FUNC;
+const char *get_signame(int number) FAST_FUNC;
+void print_signames(void) FAST_FUNC;
+
+char *bb_simplify_path(const char *path) FAST_FUNC;
+/* Returns ptr to NUL */
+char *bb_simplify_abs_path_inplace(char *path) FAST_FUNC;
+
+#define FAIL_DELAY 3
+extern void bb_do_delay(int seconds) FAST_FUNC;
+extern void change_identity(const struct passwd *pw) FAST_FUNC;
+extern void run_shell(const char *shell, int loginshell, const char *command, const char **additional_args) NORETURN FAST_FUNC;
+extern void run_shell(const char *shell, int loginshell, const char *command, const char **additional_args) FAST_FUNC;
+#if ENABLE_SELINUX
+extern void renew_current_security_context(void) FAST_FUNC;
+extern void set_current_security_context(security_context_t sid) FAST_FUNC;
+extern context_t set_security_context_component(security_context_t cur_context,
+                                               char *user, char *role, char *type, char *range) FAST_FUNC;
+extern void setfscreatecon_or_die(security_context_t scontext) FAST_FUNC;
+extern void selinux_preserve_fcontext(int fdesc) FAST_FUNC;
+#else
+#define selinux_preserve_fcontext(fdesc) ((void)0)
+#endif
+extern void selinux_or_die(void) FAST_FUNC;
+extern int restricted_shell(const char *shell) FAST_FUNC;
+
+/* setup_environment:
+ * if clear_env = 1: cd(pw->pw_dir), clear environment, then set
+ *   TERM=(old value)
+ *   USER=pw->pw_name, LOGNAME=pw->pw_name
+ *   PATH=bb_default_[root_]path
+ *   HOME=pw->pw_dir
+ *   SHELL=shell
+ * else if change_env = 1:
+ *   if not root (if pw->pw_uid != 0):
+ *     USER=pw->pw_name, LOGNAME=pw->pw_name
+ *   HOME=pw->pw_dir
+ *   SHELL=shell
+ * else does nothing
+ */
+extern void setup_environment(const char *shell, int clear_env, int change_env, const struct passwd *pw) FAST_FUNC;
+extern int correct_password(const struct passwd *pw) FAST_FUNC;
+/* Returns a malloced string */
+#if !ENABLE_USE_BB_CRYPT
+#define pw_encrypt(clear, salt, cleanup) pw_encrypt(clear, salt)
+#endif
+extern char *pw_encrypt(const char *clear, const char *salt, int cleanup) FAST_FUNC;
+extern int obscure(const char *old, const char *newval, const struct passwd *pwdp) FAST_FUNC;
+/* rnd is additional random input. New one is returned.
+ * Useful if you call crypt_make_salt many times in a row:
+ * rnd = crypt_make_salt(buf1, 4, 0);
+ * rnd = crypt_make_salt(buf2, 4, rnd);
+ * rnd = crypt_make_salt(buf3, 4, rnd);
+ * (otherwise we risk having same salt generated)
+ */
+extern int crypt_make_salt(char *p, int cnt, int rnd) FAST_FUNC;
+
+/* Returns number of lines changed, or -1 on error */
+#if !(ENABLE_FEATURE_ADDUSER_TO_GROUP || ENABLE_FEATURE_DEL_USER_FROM_GROUP)
+#define update_passwd(filename, username, data, member) \
+       update_passwd(filename, username, data)
+#endif
+extern int update_passwd(const char *filename,
+               const char *username,
+               const char *data,
+               const char *member) FAST_FUNC;
+
+int index_in_str_array(const char *const string_array[], const char *key) FAST_FUNC;
+int index_in_strings(const char *strings, const char *key) FAST_FUNC;
+int index_in_substr_array(const char *const string_array[], const char *key) FAST_FUNC;
+int index_in_substrings(const char *strings, const char *key) FAST_FUNC;
+const char *nth_string(const char *strings, int n) FAST_FUNC;
+
+extern void print_login_issue(const char *issue_file, const char *tty) FAST_FUNC;
+extern void print_login_prompt(void) FAST_FUNC;
+
+char *xmalloc_ttyname(int fd) FAST_FUNC;
+/* NB: typically you want to pass fd 0, not 1. Think 'applet | grep something' */
+int get_terminal_width_height(int fd, unsigned *width, unsigned *height) FAST_FUNC;
+
+int tcsetattr_stdin_TCSANOW(const struct termios *tp) FAST_FUNC;
+
+/* NB: "unsigned request" is crucial! "int request" will break some arches! */
+int ioctl_or_perror(int fd, unsigned request, void *argp, const char *fmt,...) __attribute__ ((format (printf, 4, 5))) FAST_FUNC;
+int ioctl_or_perror_and_die(int fd, unsigned request, void *argp, const char *fmt,...) __attribute__ ((format (printf, 4, 5))) FAST_FUNC;
+#if ENABLE_IOCTL_HEX2STR_ERROR
+int bb_ioctl_or_warn(int fd, unsigned request, void *argp, const char *ioctl_name) FAST_FUNC;
+int bb_xioctl(int fd, unsigned request, void *argp, const char *ioctl_name) FAST_FUNC;
+#define ioctl_or_warn(fd,request,argp) bb_ioctl_or_warn(fd,request,argp,#request)
+#define xioctl(fd,request,argp)        bb_xioctl(fd,request,argp,#request)
+#else
+int bb_ioctl_or_warn(int fd, unsigned request, void *argp) FAST_FUNC;
+int bb_xioctl(int fd, unsigned request, void *argp) FAST_FUNC;
+#define ioctl_or_warn(fd,request,argp) bb_ioctl_or_warn(fd,request,argp)
+#define xioctl(fd,request,argp)        bb_xioctl(fd,request,argp)
+#endif
+
+char *is_in_ino_dev_hashtable(const struct stat *statbuf) FAST_FUNC;
+void add_to_ino_dev_hashtable(const struct stat *statbuf, const char *name) FAST_FUNC;
+void reset_ino_dev_hashtable(void) FAST_FUNC;
+#ifdef __GLIBC__
+/* At least glibc has horrendously large inline for this, so wrap it */
+unsigned long long bb_makedev(unsigned int major, unsigned int minor) FAST_FUNC;
+#undef makedev
+#define makedev(a,b) bb_makedev(a,b)
+#endif
+
+
+#if ENABLE_FEATURE_EDITING
+/* It's NOT just ENABLEd or disabled. It's a number: */
+#ifdef CONFIG_FEATURE_EDITING_HISTORY
+# define MAX_HISTORY (CONFIG_FEATURE_EDITING_HISTORY + 0)
+#else
+# define MAX_HISTORY 0
+#endif
+typedef struct line_input_t {
+       int flags;
+       const char *path_lookup;
+#if MAX_HISTORY
+       int cnt_history;
+       int cur_history;
+#if ENABLE_FEATURE_EDITING_SAVEHISTORY
+       unsigned cnt_history_in_file;
+       const char *hist_file;
+#endif
+       char *history[MAX_HISTORY + 1];
+#endif
+} line_input_t;
+enum {
+       DO_HISTORY = 1 * (MAX_HISTORY > 0),
+       SAVE_HISTORY = 2 * (MAX_HISTORY > 0) * ENABLE_FEATURE_EDITING_SAVEHISTORY,
+       TAB_COMPLETION = 4 * ENABLE_FEATURE_TAB_COMPLETION,
+       USERNAME_COMPLETION = 8 * ENABLE_FEATURE_USERNAME_COMPLETION,
+       VI_MODE = 0x10 * ENABLE_FEATURE_EDITING_VI,
+       WITH_PATH_LOOKUP = 0x20,
+       FOR_SHELL = DO_HISTORY | SAVE_HISTORY | TAB_COMPLETION | USERNAME_COMPLETION,
+};
+line_input_t *new_line_input_t(int flags) FAST_FUNC;
+/* so far static: void free_line_input_t(line_input_t *n) FAST_FUNC; */
+/* Returns:
+ * -1 on read errors or EOF, or on bare Ctrl-D,
+ * 0  on ctrl-C (the line entered is still returned in 'command'),
+ * >0 length of input string, including terminating '\n'
+ */
+int read_line_input(const char* prompt, char* command, int maxsize, line_input_t *state) FAST_FUNC;
+#else
+int read_line_input(const char* prompt, char* command, int maxsize) FAST_FUNC;
+#define read_line_input(prompt, command, maxsize, state) \
+       read_line_input(prompt, command, maxsize)
+#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 procps_status_t {
+       DIR *dir;
+       uint8_t shift_pages_to_bytes;
+       uint8_t shift_pages_to_kb;
+/* Fields are set to 0/NULL if failed to determine (or not requested) */
+       uint16_t argv_len;
+       char *argv0;
+       USE_SELINUX(char *context;)
+       /* Everything below must contain no ptrs to malloc'ed data:
+        * it is memset(0) for each process in procps_scan() */
+       unsigned long vsz, rss; /* we round it to kbytes */
+       unsigned long stime, utime;
+       unsigned long start_time;
+       unsigned pid;
+       unsigned ppid;
+       unsigned pgid;
+       unsigned sid;
+       unsigned uid;
+       unsigned gid;
+       unsigned tty_major,tty_minor;
+#if ENABLE_FEATURE_TOPMEM
+       unsigned long mapped_rw;
+       unsigned long mapped_ro;
+       unsigned long shared_clean;
+       unsigned long shared_dirty;
+       unsigned long private_clean;
+       unsigned long private_dirty;
+       unsigned long stack;
+#endif
+       char state[4];
+       /* basename of executable in exec(2), read from /proc/N/stat
+        * (if executable is symlink or script, it is NOT replaced
+        * by link target or interpreter name) */
+       char comm[COMM_LEN];
+       /* user/group? - use passwd/group parsing functions */
+#if ENABLE_FEATURE_TOP_SMP_PROCESS
+       int last_seen_on_cpu;
+#endif
+} 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, - use read_cmdline instead */
+       PSSCAN_ARGV0    = 1 << 7,
+       /* PSSCAN_EXE      = 1 << 8, - not implemented */
+       PSSCAN_STATE    = 1 << 9,
+       PSSCAN_VSZ      = 1 << 10,
+       PSSCAN_RSS      = 1 << 11,
+       PSSCAN_STIME    = 1 << 12,
+       PSSCAN_UTIME    = 1 << 13,
+       PSSCAN_TTY      = 1 << 14,
+       PSSCAN_SMAPS    = (1 << 15) * ENABLE_FEATURE_TOPMEM,
+       /* NB: used by find_pid_by_name(). Any applet using it
+        * needs to be mentioned here. */
+       PSSCAN_ARGVN    = (1 << 16) * (ENABLE_KILLALL
+                               || ENABLE_PGREP || ENABLE_PKILL
+                               || ENABLE_PIDOF
+                               || ENABLE_SESTATUS
+                               ),
+       USE_SELINUX(PSSCAN_CONTEXT = 1 << 17,)
+       PSSCAN_START_TIME = 1 << 18,
+       PSSCAN_CPU      = 1 << 19,
+       /* These are all retrieved from proc/NN/stat in one go: */
+       PSSCAN_STAT     = PSSCAN_PPID | PSSCAN_PGID | PSSCAN_SID
+       /**/            | PSSCAN_COMM | PSSCAN_STATE
+       /**/            | PSSCAN_VSZ | PSSCAN_RSS
+       /**/            | PSSCAN_STIME | PSSCAN_UTIME | PSSCAN_START_TIME
+       /**/            | PSSCAN_TTY
+#if ENABLE_FEATURE_TOP_SMP_PROCESS
+       /**/            | PSSCAN_CPU
+#endif
+};
+//procps_status_t* alloc_procps_scan(void) FAST_FUNC;
+void free_procps_scan(procps_status_t* sp) FAST_FUNC;
+procps_status_t* procps_scan(procps_status_t* sp, int flags) FAST_FUNC;
+/* Format cmdline (up to col chars) into char buf[col+1] */
+/* Puts [comm] if cmdline is empty (-> process is a kernel thread) */
+void read_cmdline(char *buf, int col, unsigned pid, const char *comm) FAST_FUNC;
+pid_t *find_pid_by_name(const char* procName) FAST_FUNC;
+pid_t *pidlist_reverse(pid_t *pidList) FAST_FUNC;
+
+
+extern const char bb_uuenc_tbl_base64[];
+extern const char bb_uuenc_tbl_std[];
+void bb_uuencode(char *store, const void *s, int length, const char *tbl) FAST_FUNC;
+
+typedef struct sha1_ctx_t {
+       uint32_t hash[8];    /* 5, +3 elements for sha256 */
+       uint64_t total64;
+       uint8_t wbuffer[64]; /* NB: always correctly aligned for uint64_t */
+       void (*process_block)(struct sha1_ctx_t*) FAST_FUNC;
+} sha1_ctx_t;
+void sha1_begin(sha1_ctx_t *ctx) FAST_FUNC;
+void sha1_hash(const void *data, size_t length, sha1_ctx_t *ctx) FAST_FUNC;
+void sha1_end(void *resbuf, sha1_ctx_t *ctx) FAST_FUNC;
+typedef struct sha1_ctx_t sha256_ctx_t;
+void sha256_begin(sha256_ctx_t *ctx) FAST_FUNC;
+#define sha256_hash sha1_hash
+#define sha256_end sha1_end
+typedef struct sha512_ctx_t {
+       uint64_t hash[8];
+       uint64_t total64[2];
+       uint8_t wbuffer[128]; /* NB: always correctly aligned for uint64_t */
+} sha512_ctx_t;
+void sha512_begin(sha512_ctx_t *ctx) FAST_FUNC;
+void sha512_hash(const void *buffer, size_t len, sha512_ctx_t *ctx) FAST_FUNC;
+void sha512_end(void *resbuf, sha512_ctx_t *ctx) FAST_FUNC;
+#if 1
+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;
+#else
+/* libbb/md5prime.c uses a bit different one: */
+typedef struct md5_ctx_t {
+       uint32_t state[4];      /* state (ABCD) */
+       uint32_t count[2];      /* number of bits, modulo 2^64 (lsb first) */
+       unsigned char buffer[64];       /* input buffer */
+} md5_ctx_t;
+#endif
+void md5_begin(md5_ctx_t *ctx) FAST_FUNC;
+void md5_hash(const void *data, size_t length, md5_ctx_t *ctx) FAST_FUNC;
+void md5_end(void *resbuf, md5_ctx_t *ctx) FAST_FUNC;
+
+
+uint32_t *crc32_filltable(uint32_t *tbl256, int endian) FAST_FUNC;
+
+typedef struct masks_labels_t {
+       const char *labels;
+       const int masks[];
+} masks_labels_t;
+int print_flags_separated(const int *masks, const char *labels,
+               int flags, const char *separator) FAST_FUNC;
+int print_flags(const masks_labels_t *ml, int flags) FAST_FUNC;
+
+
+extern const char *applet_name;
+/* "BusyBox vN.N.N (timestamp or extra_version)" */
+extern const char bb_banner[];
+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[];
+/* NB: (bb_hexdigits_upcase[i] | 0x20) -> lowercase hex digit */
+extern const char bb_hexdigits_upcase[];
+
+extern const char bb_path_mtab_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_motd_file[];
+extern const char bb_path_wtmp_file[];
+extern const char bb_dev_null[];
+extern const char bb_busybox_exec_path[];
+/* util-linux manpage says /sbin:/bin:/usr/sbin:/usr/bin,
+ * but I want to save a few bytes here */
+extern const char bb_PATH_root_path[]; /* "PATH=/sbin:/usr/sbin:/bin:/usr/bin" */
+#define bb_default_root_path (bb_PATH_root_path + sizeof("PATH"))
+#define bb_default_path      (bb_PATH_root_path + sizeof("PATH=/sbin:/usr/sbin"))
+
+extern const int const_int_0;
+extern const int const_int_1;
+
+
+#ifndef BUFSIZ
+#define BUFSIZ 4096
+#endif
+/* Providing hard guarantee on minimum size (think of BUFSIZ == 128) */
+enum { COMMON_BUFSIZE = (BUFSIZ >= 256*sizeof(void*) ? BUFSIZ+1 : 256*sizeof(void*)) };
+extern char bb_common_bufsiz1[COMMON_BUFSIZE];
+/* This struct is deliberately not defined. */
+/* See docs/keep_data_small.txt */
+struct globals;
+/* '*const' ptr makes gcc optimize code much better.
+ * Magic prevents ptr_to_globals from going into rodata.
+ * If you want to assign a value, use SET_PTR_TO_GLOBALS(x) */
+extern struct globals *const ptr_to_globals;
+/* At least gcc 3.4.6 on mipsel system needs optimization barrier */
+#define barrier() __asm__ __volatile__("":::"memory")
+#define SET_PTR_TO_GLOBALS(x) do { \
+       (*(struct globals**)&ptr_to_globals) = (x); \
+       barrier(); \
+} while (0)
+
+/* 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)
+
+#if ENABLE_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_NAMESIZE (sizeof("/dev/loop/") + sizeof(int)*3 + 1)
+# 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_NAMESIZE (sizeof("/dev/loop") + sizeof(int)*3 + 1)
+# 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 DEV_CONSOLE "/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)
+
+#define ARRAY_SIZE(x) ((unsigned)(sizeof(x) / sizeof((x)[0])))
+
+
+POP_SAVED_FUNCTION_VISIBILITY
+
+#endif
diff --git a/include/platform.h b/include/platform.h
new file mode 100644 (file)
index 0000000..317349f
--- /dev/null
@@ -0,0 +1,387 @@
+/* vi: set sw=4 ts=4: */
+/*
+   Copyright 2006, Bernhard Reutner-Fischer
+
+   Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
+*/
+#ifndef        BB_PLATFORM_H
+#define BB_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 UNUSED_PARAM __attribute__ ((__unused__))
+#define NORETURN __attribute__ ((__noreturn__))
+#define PACKED __attribute__ ((__packed__))
+#define ALIGNED(m) __attribute__ ((__aligned__(m)))
+/* __NO_INLINE__: some gcc's do not honor inlining! :( */
+#if __GNUC_PREREQ(3,0) && !defined(__NO_INLINE__)
+# define ALWAYS_INLINE __attribute__ ((always_inline)) inline
+/* I've seen a toolchain where I needed __noinline__ instead of noinline */
+# define NOINLINE      __attribute__((__noinline__))
+# if !ENABLE_WERROR
+#  define DEPRECATED __attribute__ ((__deprecated__))
+#  define UNUSED_PARAM_RESULT __attribute__ ((warn_unused_result))
+# else
+#  define DEPRECATED /* n/a */
+#  define UNUSED_PARAM_RESULT /* n/a */
+# endif
+#else
+# define ALWAYS_INLINE inline /* n/a */
+# define NOINLINE /* n/a */
+# define DEPRECATED /* n/a */
+# define UNUSED_PARAM_RESULT /* n/a */
+#endif
+
+/* -fwhole-program makes all symbols local. The attribute externally_visible
+   forces a symbol global.  */
+#if __GNUC_PREREQ(4,1)
+# define EXTERNALLY_VISIBLE __attribute__(( visibility("default") ))
+//__attribute__ ((__externally_visible__))
+#else
+# define EXTERNALLY_VISIBLE
+#endif
+
+/* 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 <stdarg.h>
+# if !defined va_copy && defined __va_copy
+#  define va_copy(d,s) __va_copy((d),(s))
+# endif
+#endif
+
+/* FAST_FUNC is a qualifier which (possibly) makes function call faster
+ * and/or smaller by using modified ABI. It is usually only needed
+ * on non-static, busybox internal functions. Recent versions of gcc
+ * optimize statics automatically. FAST_FUNC on static is required
+ * only if you need to match a function pointer's type */
+#if __GNUC_PREREQ(3,0) && defined(i386) /* || defined(__x86_64__)? */
+/* stdcall makes callee to pop arguments from stack, not caller */
+# define FAST_FUNC __attribute__((regparm(3),stdcall))
+/* #elif ... - add your favorite arch today! */
+#else
+# define FAST_FUNC
+#endif
+
+/* Make all declarations hidden (-fvisibility flag only affects definitions) */
+/* (don't include system headers after this until corresponding pop!) */
+#if __GNUC_PREREQ(4,1)
+# define PUSH_AND_SET_FUNCTION_VISIBILITY_TO_HIDDEN _Pragma("GCC visibility push(hidden)")
+# define POP_SAVED_FUNCTION_VISIBILITY              _Pragma("GCC visibility pop")
+#else
+# define PUSH_AND_SET_FUNCTION_VISIBILITY_TO_HIDDEN
+# define POP_SAVED_FUNCTION_VISIBILITY
+#endif
+
+/* ---- Endian Detection ------------------------------------ */
+
+#if (defined __digital__ && defined __unix__)
+# include <sex.h>
+# define __BIG_ENDIAN__ (BYTE_ORDER == BIG_ENDIAN)
+# define __BYTE_ORDER BYTE_ORDER
+#elif !defined __APPLE__
+# include <byteswap.h>
+# include <endian.h>
+#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
+
+/* SWAP_LEnn means "convert CPU<->little_endian by swapping bytes" */
+#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
+
+/* ---- Unaligned access ------------------------------------ */
+
+/* NB: unaligned parameter should be a pointer, aligned one -
+ * a lvalue. This makes it more likely to not swap them by mistake
+ */
+#if defined(i386) || defined(__x86_64__)
+#define move_from_unaligned16(v, u16p) ((v) = *(uint16_t*)(u16p))
+#define move_from_unaligned32(v, u32p) ((v) = *(uint32_t*)(u32p))
+#define move_to_unaligned32(u32p, v)   (*(uint32_t*)(u32p) = (v))
+/* #elif ... - add your favorite arch today! */
+#else
+/* performs reasonably well (gcc usually inlines memcpy here) */
+#define move_from_unaligned16(v, u16p) (memcpy(&(v), (u16p), 2))
+#define move_from_unaligned32(v, u32p) (memcpy(&(v), (u32p), 4))
+#define move_to_unaligned32(u32p, v) do { \
+       uint32_t __t = (v); \
+       memcpy((u32p), &__t, 4); \
+} while (0)
+#endif
+
+/* ---- Networking ------------------------------------------ */
+
+#ifndef __APPLE__
+# include <arpa/inet.h>
+# ifndef __socklen_t_defined
+typedef int socklen_t;
+# endif
+#else
+# include <netinet/in.h>
+#endif
+
+/* ---- Compiler dependent settings ------------------------- */
+
+#if (defined __digital__ && defined __unix__) || defined __APPLE__
+# undef HAVE_MNTENT_H
+# undef HAVE_SYS_STATFS_H
+#else
+# define HAVE_MNTENT_H 1
+# define HAVE_SYS_STATFS_H 1
+#endif /* ___digital__ && __unix__ */
+
+/* linux/loop.h relies on __u64. Make sure we have that as a proper type
+ * until userspace is widely fixed.  */
+#if (defined __INTEL_COMPILER && !defined __GNUC__) || \
+       (defined __GNUC__ && defined __STRICT_ANSI__)
+__extension__ typedef long long __s64;
+__extension__ typedef unsigned long long __u64;
+#endif
+
+/*----- 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 <features.h>
+#define HAVE_FEATURES_H
+#include <stdint.h>
+#define HAVE_STDINT_H
+#elif !defined __APPLE__
+/* Largest integral types.  */
+#if __BIG_ENDIAN__
+typedef long                intmax_t;
+typedef unsigned long       uintmax_t;
+#else
+__extension__
+typedef long long           intmax_t;
+__extension__
+typedef unsigned long long  uintmax_t;
+#endif
+#endif
+
+/* Size-saving "small" ints (arch-dependent) */
+#if defined(i386) || defined(__x86_64__) || defined(__mips__) || defined(__cris__)
+/* add other arches which benefit from this... */
+typedef signed char smallint;
+typedef unsigned char smalluint;
+#else
+/* for arches where byte accesses generate larger code: */
+typedef int smallint;
+typedef unsigned smalluint;
+#endif
+
+/* ISO C Standard:  7.16  Boolean type and values  <stdbool.h> */
+#if (defined __digital__ && defined __unix__)
+/* old system without (proper) C99 support */
+#define bool smalluint
+#else
+/* modern system, so use it */
+#include <stdbool.h>
+#endif
+
+/* Try to defeat gcc's alignment of "char message[]"-like data */
+#if 1 /* if needed: !defined(arch1) && !defined(arch2) */
+#define ALIGN1 __attribute__((aligned(1)))
+#define ALIGN2 __attribute__((aligned(2)))
+#else
+/* Arches which MUST have 2 or 4 byte alignment for everything are here */
+#define ALIGN1
+#define ALIGN2
+#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.
+ */
+#if ENABLE_NOMMU || \
+    (defined __UCLIBC__ && __UCLIBC_MAJOR__ >= 0 && __UCLIBC_MINOR__ >= 9 && \
+    __UCLIBC_SUBLEVEL__ > 28 && !defined __ARCH_USE_MMU__)
+#define BB_MMU 0
+#define USE_FOR_NOMMU(...) __VA_ARGS__
+#define USE_FOR_MMU(...)
+#else
+#define BB_MMU 1
+#define USE_FOR_NOMMU(...)
+#define USE_FOR_MMU(...) __VA_ARGS__
+#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. */
+#if !defined(__dietlibc__)
+/* Needed for: glibc */
+/* Not needed for: dietlibc */
+/* Others: ?? (add as needed) */
+#define fdprintf dprintf
+#endif
+
+#if defined(__dietlibc__)
+static ALWAYS_INLINE char* strchrnul(const char *s, char c)
+{
+       while (*s && *s != c) ++s;
+       return (char*)s;
+}
+#endif
+
+/* Don't use lchown with glibc older than 2.1.x ... uClibc lacks it */
+#if (defined __GLIBC__ && __GLIBC__ <= 2 && __GLIBC_MINOR__ < 1) || \
+    defined __UC_LIBC__
+# define lchown chown
+#endif
+
+#if (defined __digital__ && defined __unix__)
+
+# include <standards.h>
+# define HAVE_STANDARDS_H
+# include <inttypes.h>
+# define HAVE_INTTYPES_H
+# define PRIu32 "u"
+/* use legacy setpgrp(pid_t,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 /* !__digital__ */
+
+# define bb_setpgrp() setpgrp()
+
+#endif
+
+#if defined(__linux__)
+#include <sys/mount.h>
+/* 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
+#ifndef MS_RELATIME
+#define MS_RELATIME   (1 << 21)
+#endif
+
+#if !defined(BLKSSZGET)
+#define BLKSSZGET _IO(0x12, 104)
+#endif
+#if !defined(BLKGETSIZE64)
+#define BLKGETSIZE64 _IOR(0x12,114,size_t)
+#endif
+#endif
+
+#endif
diff --git a/include/pwd_.h b/include/pwd_.h
new file mode 100644 (file)
index 0000000..f52445c
--- /dev/null
@@ -0,0 +1,110 @@
+/* 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      <pwd.h>
+ */
+
+#ifndef BB_PWD_H
+#define BB_PWD_H 1
+
+PUSH_AND_SET_FUNCTION_VISIBILITY_TO_HIDDEN
+
+/* This file is #included after #include <pwd.h>
+ * We will use libc-defined structures, but will #define function names
+ * so that function calls are directed to bb_internal_XXX replacements
+ */
+
+#define setpwent    bb_internal_setpwent
+#define endpwent    bb_internal_endpwent
+#define getpwent    bb_internal_getpwent
+#define fgetpwent   bb_internal_fgetpwent
+#define putpwent    bb_internal_putpwent
+#define getpwuid    bb_internal_getpwuid
+#define getpwnam    bb_internal_getpwnam
+#define getpwent_r  bb_internal_getpwent_r
+#define getpwuid_r  bb_internal_getpwuid_r
+#define getpwnam_r  bb_internal_getpwnam_r
+#define fgetpwent_r bb_internal_fgetpwent_r
+//#define getpw       bb_internal_getpw
+
+
+/* All function names below should be remapped by #defines above
+ * in order to not collide with libc names. */
+
+
+/* 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.  */
+/* UNUSED extern int getpw(uid_t __uid, char *__buffer); */
+
+POP_SAVED_FUNCTION_VISIBILITY
+
+#endif /* pwd.h  */
diff --git a/include/rtc_.h b/include/rtc_.h
new file mode 100644 (file)
index 0000000..74bb695
--- /dev/null
@@ -0,0 +1,74 @@
+/*
+ * Common defines/structures/etc... for applets that need to work with the RTC.
+ *
+ * Licensed under the GPL-2 or later.
+ */
+
+#ifndef BB_RTC_H
+#define BB_RTC_H 1
+
+#include "libbb.h"
+
+PUSH_AND_SET_FUNCTION_VISIBILITY_TO_HIDDEN
+
+extern int rtc_adjtime_is_utc(void) FAST_FUNC;
+extern int rtc_xopen(const char **default_rtc, int flags) FAST_FUNC;
+extern time_t rtc_read_time(int fd, int utc) FAST_FUNC;
+
+/*
+ * Everything below this point has been copied from linux/rtc.h
+ * to eliminate the kernel header 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;
+};
+
+struct linux_rtc_wkalrm {
+       unsigned char enabled;  /* 0 = alarm disabled, 1 = alarm enabled */
+       unsigned char pending;  /* 0 = alarm not pending, 1 = alarm pending */
+       struct linux_rtc_time time;     /* time the alarm is set to */
+};
+
+/*
+ * ioctl calls that are permitted to the /dev/rtc interface, if
+ * any of the RTC drivers are enabled.
+ */
+#define RTC_AIE_ON      _IO('p', 0x01)  /* Alarm int. enable on         */
+#define RTC_AIE_OFF     _IO('p', 0x02)  /* ... off                      */
+#define RTC_UIE_ON      _IO('p', 0x03)  /* Update int. enable on        */
+#define RTC_UIE_OFF     _IO('p', 0x04)  /* ... off                      */
+#define RTC_PIE_ON      _IO('p', 0x05)  /* Periodic int. enable on      */
+#define RTC_PIE_OFF     _IO('p', 0x06)  /* ... off                      */
+#define RTC_WIE_ON      _IO('p', 0x0f)  /* Watchdog int. enable on      */
+#define RTC_WIE_OFF     _IO('p', 0x10)  /* ... off                      */
+
+#define RTC_ALM_SET     _IOW('p', 0x07, struct linux_rtc_time) /* Set alarm time  */
+#define RTC_ALM_READ    _IOR('p', 0x08, struct linux_rtc_time) /* Read alarm time */
+#define RTC_RD_TIME     _IOR('p', 0x09, struct linux_rtc_time) /* Read RTC time   */
+#define RTC_SET_TIME    _IOW('p', 0x0a, struct linux_rtc_time) /* Set RTC time    */
+#define RTC_IRQP_READ   _IOR('p', 0x0b, unsigned long)   /* Read IRQ rate   */
+#define RTC_IRQP_SET    _IOW('p', 0x0c, unsigned long)   /* Set IRQ rate    */
+#define RTC_EPOCH_READ  _IOR('p', 0x0d, unsigned long)   /* Read epoch      */
+#define RTC_EPOCH_SET   _IOW('p', 0x0e, unsigned long)   /* Set epoch       */
+
+#define RTC_WKALM_SET   _IOW('p', 0x0f, struct linux_rtc_wkalrm)/* Set wakeup alarm*/
+#define RTC_WKALM_RD    _IOR('p', 0x10, struct linux_rtc_wkalrm)/* Get wakeup alarm*/
+
+/* interrupt flags */
+#define RTC_IRQF 0x80 /* any of the following is active */
+#define RTC_PF 0x40
+#define RTC_AF 0x20
+#define RTC_UF 0x10
+
+POP_SAVED_FUNCTION_VISIBILITY
+
+#endif
diff --git a/include/shadow_.h b/include/shadow_.h
new file mode 100644 (file)
index 0000000..02d3bf9
--- /dev/null
@@ -0,0 +1,100 @@
+/* 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 */
+
+#ifndef BB_SHADOW_H
+#define BB_SHADOW_H 1
+
+PUSH_AND_SET_FUNCTION_VISIBILITY_TO_HIDDEN
+
+/* This file is #included after #include <shadow.h>
+ * We will use libc-defined structures, but will #define function names
+ * so that function calls are directed to bb_internal_XXX replacements
+ */
+
+/* Paths to the user database files */
+#ifndef _PATH_SHADOW
+#define _PATH_SHADOW "/etc/shadow"
+#endif
+
+#define setspent    bb_internal_setspent
+#define endspent    bb_internal_endspent
+#define getspent    bb_internal_getspent
+#define getspnam    bb_internal_getspnam
+#define sgetspent   bb_internal_sgetspent
+#define fgetspent   bb_internal_fgetspent
+#define putspent    bb_internal_putspent
+#define getspent_r  bb_internal_getspent_r
+#define getspnam_r  bb_internal_getspnam_r
+#define sgetspent_r bb_internal_sgetspent_r
+#define fgetspent_r bb_internal_fgetspent_r
+#define lckpwdf     bb_internal_lckpwdf
+#define ulckpwdf    bb_internal_ulckpwdf
+
+
+/* All function names below should be remapped by #defines above
+ * in order to not collide with libc names. */
+
+
+/* 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);
+
+POP_SAVED_FUNCTION_VISIBILITY
+
+#endif /* shadow.h */
diff --git a/include/unarchive.h b/include/unarchive.h
new file mode 100644 (file)
index 0000000..beb962c
--- /dev/null
@@ -0,0 +1,153 @@
+/* vi: set sw=4 ts=4: */
+#ifndef UNARCHIVE_H
+#define UNARCHIVE_H 1
+
+PUSH_AND_SET_FUNCTION_VISIBILITY_TO_HIDDEN
+
+#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
+
+typedef struct file_header_t {
+       char *name;
+       char *link_target;
+#if ENABLE_FEATURE_TAR_UNAME_GNAME
+       char *uname;
+       char *gname;
+#endif
+       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_t {
+       /* Define if the header and data component should be processed */
+       char FAST_FUNC (*filter)(struct archive_handle_t *);
+       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 FAST_FUNC (*action_header)(const file_header_t *);
+
+       /* Process the data component, e.g. extract to filesystem */
+       void FAST_FUNC (*action_data)(struct archive_handle_t *);
+
+#if ENABLE_DPKG || ENABLE_DPKG_DEB
+       /* "subarchive" is used only by dpkg[-deb] applets */
+       /* How to process any sub archive, e.g. get_header_tar_gz */
+       char FAST_FUNC (*action_data_subarchive)(struct archive_handle_t *);
+       /* Contains the handle to a sub archive */
+       struct archive_handle_t *sub_archive;
+#endif
+
+       /* 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 FAST_FUNC (*seek)(const struct archive_handle_t *archive_handle, const unsigned amount);
+
+       /* Temporary storage */
+       char *buffer;
+
+       /* Flags and misc. stuff */
+       unsigned char ah_flags;
+
+       /* "Private" storage for archivers */
+//     unsigned char ah_priv_inited;
+       void *ah_priv[8];
+
+} archive_handle_t;
+
+
+/* Info struct unpackers can fill out to inform users of thing like
+ * timestamps of unpacked files */
+typedef struct unpack_info_t {
+       time_t mtime;
+} unpack_info_t;
+
+extern archive_handle_t *init_handle(void) FAST_FUNC;
+
+extern char filter_accept_all(archive_handle_t *archive_handle) FAST_FUNC;
+extern char filter_accept_list(archive_handle_t *archive_handle) FAST_FUNC;
+extern char filter_accept_list_reassign(archive_handle_t *archive_handle) FAST_FUNC;
+extern char filter_accept_reject_list(archive_handle_t *archive_handle) FAST_FUNC;
+
+extern void unpack_ar_archive(archive_handle_t *ar_archive) FAST_FUNC;
+
+extern void data_skip(archive_handle_t *archive_handle) FAST_FUNC;
+extern void data_extract_all(archive_handle_t *archive_handle) FAST_FUNC;
+extern void data_extract_to_stdout(archive_handle_t *archive_handle) FAST_FUNC;
+extern void data_extract_to_buffer(archive_handle_t *archive_handle) FAST_FUNC;
+
+extern void header_skip(const file_header_t *file_header) FAST_FUNC;
+extern void header_list(const file_header_t *file_header) FAST_FUNC;
+extern void header_verbose_list(const file_header_t *file_header) FAST_FUNC;
+
+extern char get_header_ar(archive_handle_t *archive_handle) FAST_FUNC;
+extern char get_header_cpio(archive_handle_t *archive_handle) FAST_FUNC;
+extern char get_header_tar(archive_handle_t *archive_handle) FAST_FUNC;
+extern char get_header_tar_gz(archive_handle_t *archive_handle) FAST_FUNC;
+extern char get_header_tar_bz2(archive_handle_t *archive_handle) FAST_FUNC;
+extern char get_header_tar_lzma(archive_handle_t *archive_handle) FAST_FUNC;
+
+extern void seek_by_jump(const archive_handle_t *archive_handle, unsigned amount) FAST_FUNC;
+extern void seek_by_read(const archive_handle_t *archive_handle, unsigned amount) FAST_FUNC;
+
+extern void data_align(archive_handle_t *archive_handle, unsigned boundary) FAST_FUNC;
+extern const llist_t *find_list_entry(const llist_t *list, const char *filename) FAST_FUNC;
+extern const llist_t *find_list_entry2(const llist_t *list, const char *filename) FAST_FUNC;
+
+/* A bit of bunzip2 internals are exposed for compressed help support: */
+typedef struct bunzip_data bunzip_data;
+int start_bunzip(bunzip_data **bdp, int in_fd, const unsigned char *inbuf, int len) FAST_FUNC;
+int read_bunzip(bunzip_data *bd, char *outbuf, int len) FAST_FUNC;
+void dealloc_bunzip(bunzip_data *bd) FAST_FUNC;
+
+typedef struct inflate_unzip_result {
+       off_t bytes_out;
+       uint32_t crc;
+} inflate_unzip_result;
+
+USE_DESKTOP(long long) int inflate_unzip(inflate_unzip_result *res, off_t compr_size, int src_fd, int dst_fd) FAST_FUNC;
+/* lzma unpacker takes .lzma stream from offset 0 */
+USE_DESKTOP(long long) int unpack_lzma_stream(int src_fd, int dst_fd) FAST_FUNC;
+/* the rest wants 2 first bytes already skipped by the caller */
+USE_DESKTOP(long long) int unpack_bz2_stream(int src_fd, int dst_fd) FAST_FUNC;
+USE_DESKTOP(long long) int unpack_gz_stream(int src_fd, int dst_fd) FAST_FUNC;
+USE_DESKTOP(long long) int unpack_gz_stream_with_info(int src_fd, int dst_fd, unpack_info_t *info) FAST_FUNC;
+USE_DESKTOP(long long) int unpack_Z_stream(int fd_in, int fd_out) FAST_FUNC;
+/* wrapper which checks first two bytes to be "BZ" */
+USE_DESKTOP(long long) int unpack_bz2_stream_prime(int src_fd, int dst_fd) FAST_FUNC;
+
+int bbunpack(char **argv,
+            char* (*make_new_name)(char *filename),
+            USE_DESKTOP(long long) int (*unpacker)(unpack_info_t *info)) FAST_FUNC;
+
+#if BB_MMU
+void open_transformer(int fd,
+       USE_DESKTOP(long long) int FAST_FUNC (*transformer)(int src_fd, int dst_fd)) FAST_FUNC;
+#define open_transformer(fd, transformer, transform_prog) open_transformer(fd, transformer)
+#else
+void open_transformer(int src_fd, const char *transform_prog) FAST_FUNC;
+#define open_transformer(fd, transformer, transform_prog) open_transformer(fd, transform_prog)
+#endif
+
+POP_SAVED_FUNCTION_VISIBILITY
+
+#endif
diff --git a/include/usage.h b/include/usage.h
new file mode 100644 (file)
index 0000000..bfacc56
--- /dev/null
@@ -0,0 +1,4932 @@
+/* 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"....
+ * or
+ *    |<5 spaces>"\ntext with tabs"....
+ */
+#ifndef BB_USAGE_H
+#define BB_USAGE_H 1
+
+
+#define NOUSAGE_STR "\b"
+
+#define acpid_trivial_usage \
+       "[-d] [-c CONFDIR] [-l LOGFILE] [-e PROC_EVENT_FILE] [EVDEV_EVENT_FILE...]"
+
+#define acpid_full_usage "\n\n" \
+       "Listen to ACPI events and spawn specific helpers on event arrival\n" \
+     "\nOptions:" \
+     "\n       -d      Do not daemonize and log to stderr" \
+     "\n       -c DIR  Config directory [/etc/acpi]" \
+     "\n       -e FILE /proc event file [/proc/acpi/event]" \
+     "\n       -l FILE Log file [/var/log/acpid]" \
+       USE_FEATURE_ACPID_COMPAT( \
+     "\n\nAccept and ignore compatibility options -g -m -s -S -v" \
+       )
+
+#define acpid_example_usage \
+       "# acpid -l /var/log/my-acpi-log\n" \
+       "# acpid -d /dev/input/event*\n"
+
+#define addgroup_trivial_usage \
+       "[-g GID] " USE_FEATURE_ADDUSER_TO_GROUP("[user_name] ") "group_name"
+#define addgroup_full_usage "\n\n" \
+       "Add a group " USE_FEATURE_ADDUSER_TO_GROUP("or add a user to a group") "\n" \
+     "\nOptions:" \
+     "\n       -g GID  Group id" \
+
+#define adduser_trivial_usage \
+       "[OPTIONS] user_name"
+#define adduser_full_usage "\n\n" \
+       "Add a user\n" \
+     "\nOptions:" \
+     "\n       -h DIR          Home directory" \
+     "\n       -g GECOS        GECOS field" \
+     "\n       -s SHELL        Login shell" \
+     "\n       -G GROUP        Add user to existing group" \
+     "\n       -S              Create a system user" \
+     "\n       -D              Do not assign a password" \
+     "\n       -H              Do not create home directory" \
+
+#define adjtimex_trivial_usage \
+       "[-q] [-o offset] [-f frequency] [-p timeconstant] [-t tick]"
+#define adjtimex_full_usage "\n\n" \
+       "Read and optionally set system timebase parameters. See adjtimex(2).\n" \
+     "\nOptions:" \
+     "\n       -q              Quiet" \
+     "\n       -o offset       Time offset, microseconds" \
+     "\n       -f frequency    Frequency adjust, integer kernel units (65536 is 1ppm)" \
+     "\n                       (positive values make clock run faster)" \
+     "\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 "\n\n" \
+       "Extract or list FILES from an ar archive\n" \
+     "\nOptions:" \
+     "\n       -o      Preserve original dates" \
+     "\n       -p      Extract to stdout" \
+     "\n       -t      List" \
+     "\n       -x      Extract" \
+     "\n       -v      Verbose" \
+
+#define arp_trivial_usage \
+     "\n[-vn]  [-H type] [-i if] -a [hostname]" \
+     "\n[-v]             [-i if] -d hostname [pub]" \
+     "\n[-v]   [-H type] [-i if] -s hostname hw_addr [temp]" \
+     "\n[-v]   [-H type] [-i if] -s hostname hw_addr [netmask nm] pub" \
+     "\n[-v]   [-H type] [-i if] -Ds hostname ifa [netmask nm] pub"
+#define arp_full_usage "\n\n" \
+       "Manipulate ARP cache\n" \
+     "\nOptions:" \
+       "\n     -a              Display (all) hosts" \
+       "\n     -s              Set new ARP entry" \
+       "\n     -d              Delete a specified entry" \
+       "\n     -v              Verbose" \
+       "\n     -n              Don't resolve names" \
+       "\n     -i IF           Network interface" \
+       "\n     -D              Read <hwaddr> from given device" \
+       "\n     -A, -p AF       Protocol family" \
+       "\n     -H HWTYPE       Hardware address type" \
+
+#define arping_trivial_usage \
+       "[-fqbDUA] [-c count] [-w timeout] [-I dev] [-s sender] target"
+#define arping_full_usage "\n\n" \
+       "Send ARP requests/replies\n" \
+     "\nOptions:" \
+     "\n       -f              Quit on first ARP reply" \
+     "\n       -q              Quiet" \
+     "\n       -b              Keep broadcasting, don't go unicast" \
+     "\n       -D              Duplicated address detection mode" \
+     "\n       -U              Unsolicited ARP mode, update your neighbors" \
+     "\n       -A              ARP answer mode, update your neighbors" \
+     "\n       -c N            Stop after sending N ARP requests" \
+     "\n       -w timeout      Time to wait for ARP reply, in seconds" \
+     "\n       -I dev          Interface to use (default eth0)" \
+     "\n       -s sender       Sender IP address" \
+     "\n       target          Target IP address" \
+
+#define sh_trivial_usage NOUSAGE_STR
+#define sh_full_usage ""
+#define ash_trivial_usage NOUSAGE_STR
+#define ash_full_usage ""
+#define hush_trivial_usage NOUSAGE_STR
+#define hush_full_usage ""
+#define msh_trivial_usage NOUSAGE_STR
+#define msh_full_usage ""
+
+#define awk_trivial_usage \
+       "[OPTION]... [program-text] [FILE...]"
+#define awk_full_usage "\n\n" \
+       "Options:" \
+     "\n       -v var=val      Set variable" \
+     "\n       -F sep          Use sep as field separator" \
+     "\n       -f file         Read program from file" \
+
+#define basename_trivial_usage \
+       "FILE [SUFFIX]"
+#define basename_full_usage "\n\n" \
+       "Strip directory path and suffixes from FILE.\n" \
+       "If specified, also remove 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 fbsplash_trivial_usage \
+       "-s IMGFILE [-c] [-d DEV] [-i INIFILE] [-f CMD]"
+#define fbsplash_full_usage "\n\n" \
+       "Options:\n" \
+     "\n       -s      Image" \
+     "\n       -c      Hide cursor" \
+     "\n       -d      Framebuffer device (default /dev/fb0)" \
+     "\n       -i      Config file (var=value):" \
+     "\n                       BAR_LEFT,BAR_TOP,BAR_WIDTH,BAR_HEIGHT" \
+     "\n                       BAR_R,BAR_G,BAR_B" \
+     "\n       -f      Control pipe (else exit after drawing image)" \
+     "\n                       commands: 'NN' (% for progress bar) or 'exit'" \
+
+#define brctl_trivial_usage \
+       "COMMAND [BRIDGE [INTERFACE]]"
+#define brctl_full_usage "\n\n" \
+       "Manage ethernet bridges.\n" \
+     "\nCommands:" \
+       USE_FEATURE_BRCTL_SHOW( \
+     "\n       show                    Show a list of bridges" \
+       ) \
+     "\n       addbr BRIDGE            Create BRIDGE" \
+     "\n       delbr BRIDGE            Delete BRIDGE" \
+     "\n       addif BRIDGE IFACE      Add IFACE to BRIDGE" \
+     "\n       delif BRIDGE IFACE      Delete IFACE from BRIDGE" \
+       USE_FEATURE_BRCTL_FANCY( \
+     "\n       setageing BRIDGE TIME           Set ageing time" \
+     "\n       setfd BRIDGE TIME               Set bridge forward delay" \
+     "\n       sethello BRIDGE TIME            Set hello time" \
+     "\n       setmaxage BRIDGE TIME           Set max message age" \
+     "\n       setpathcost BRIDGE COST         Set path cost" \
+     "\n       setportprio BRIDGE PRIO         Set port priority" \
+     "\n       setbridgeprio BRIDGE PRIO       Set bridge priority" \
+     "\n       stp BRIDGE [1|0]                STP on/off" \
+       ) \
+
+#define bunzip2_trivial_usage \
+       "[OPTION]... [FILE]"
+#define bunzip2_full_usage "\n\n" \
+       "Uncompress FILE (or standard input if FILE is '-' or omitted)\n" \
+     "\nOptions:" \
+     "\n       -c      Write to standard output" \
+     "\n       -f      Force" \
+
+#define bzip2_trivial_usage \
+       "[OPTION]... [FILE]..."
+#define bzip2_full_usage "\n\n" \
+       "Compress FILE(s) with bzip2 algorithm.\n" \
+       "When FILE is '-' or unspecified, reads standard input. Implies -c.\n" \
+     "\nOptions:" \
+     "\n       -c      Write to standard output" \
+     "\n       -d      Decompress" \
+     "\n       -f      Force" \
+     "\n       -1..-9  Compression level" \
+
+#define busybox_notes_usage \
+       "Hello world!\n"
+
+#define bzcat_trivial_usage \
+       "FILE"
+#define bzcat_full_usage "\n\n" \
+       "Uncompress to stdout"
+
+#define unlzma_trivial_usage \
+       "[OPTION]... [FILE]"
+#define unlzma_full_usage "\n\n" \
+       "Uncompress FILE (or standard input if FILE is '-' or omitted)\n" \
+     "\nOptions:" \
+     "\n       -c      Write to standard output" \
+     "\n       -f      Force" \
+
+#define lzmacat_trivial_usage \
+       "FILE"
+#define lzmacat_full_usage "\n\n" \
+       "Uncompress to stdout"
+
+#define cal_trivial_usage \
+       "[-jy] [[month] year]"
+#define cal_full_usage "\n\n" \
+       "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 "\n\n" \
+       "Concatenate FILE(s) and print them to stdout\n" \
+     "\nOptions:" \
+     "\n       -u      Use unbuffered i/o (ignored)" \
+
+#define cat_example_usage \
+       "$ cat /proc/uptime\n" \
+       "110716.72 17.67"
+
+#define catv_trivial_usage \
+       "[-etv] [FILE]..."
+#define catv_full_usage "\n\n" \
+       "Display nonprinting characters as ^x or M-x\n" \
+     "\nOptions:" \
+     "\n       -e      End each line with $" \
+     "\n       -t      Show tabs as ^I" \
+     "\n       -v      Don't use ^x or M-x escapes" \
+
+#define chat_trivial_usage \
+       "EXPECT [SEND [EXPECT [SEND...]]]"
+#define chat_full_usage "\n\n" \
+       "Useful for interacting with a modem connected to stdin/stdout.\n" \
+       "A script consists of one or more \"expect-send\" pairs of strings,\n" \
+       "each pair is a pair of arguments. Example:\n" \
+       "chat '' ATZ OK ATD123456 CONNECT '' ogin: pppuser word: ppppass '~'" \
+
+#define chattr_trivial_usage \
+       "[-R] [-+=AacDdijsStTu] [-v version] files..."
+#define chattr_full_usage "\n\n" \
+       "Change file attributes on an ext2 fs\n" \
+     "\nModifiers:" \
+     "\n       -       Remove attributes" \
+     "\n       +       Add attributes" \
+     "\n       =       Set attributes" \
+     "\nAttributes:" \
+     "\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" \
+     "\nOptions:" \
+     "\n       -R      Recursively list subdirectories" \
+     "\n       -v      Set the file's version/generation number" \
+
+#define chcon_trivial_usage \
+       "[OPTIONS] CONTEXT FILE..." \
+       "\n     chcon [OPTIONS] [-u USER] [-r ROLE] [-l RANGE] [-t TYPE] FILE..." \
+       USE_FEATURE_CHCON_LONG_OPTIONS( \
+       "\n     chcon [OPTIONS] --reference=RFILE FILE..." \
+       )
+#define chcon_full_usage "\n\n" \
+       "Change the security context of each FILE to CONTEXT\n" \
+       USE_FEATURE_CHCON_LONG_OPTIONS( \
+     "\n       -v,--verbose            Verbose" \
+     "\n       -c,--changes            Report changes made" \
+     "\n       -h,--no-dereference     Affect symlinks instead of their targets" \
+     "\n       -f,--silent,--quiet     Suppress most error messages" \
+     "\n       --reference=RFILE       Use RFILE's group instead of using a CONTEXT value" \
+     "\n       -u,--user=USER          Set user/role/type/range in the target" \
+     "\n       -r,--role=ROLE          security context" \
+     "\n       -t,--type=TYPE" \
+     "\n       -l,--range=RANGE" \
+     "\n       -R,--recursive          Recurse subdirectories" \
+       ) \
+       SKIP_FEATURE_CHCON_LONG_OPTIONS( \
+     "\n       -v      Verbose" \
+     "\n       -c      Report changes made" \
+     "\n       -h      Affect symlinks instead of their targets" \
+     "\n       -f      Suppress most error messages" \
+     "\n       -u USER Set user/role/type/range in the target security context" \
+     "\n       -r ROLE" \
+     "\n       -t TYPE" \
+     "\n       -l RNG" \
+     "\n       -R      Recurse subdirectories" \
+       )
+
+#define chmod_trivial_usage \
+       "[-R"USE_DESKTOP("cvf")"] MODE[,MODE]... FILE..."
+#define chmod_full_usage "\n\n" \
+       "Each MODE is one or more of the letters ugoa, one of the\n" \
+       "symbols +-= and one or more of the letters rwxst\n" \
+     "\nOptions:" \
+     "\n       -R      Recurse directories" \
+       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 chgrp_trivial_usage \
+       "[-RhLHP"USE_DESKTOP("cvf")"]... GROUP FILE..."
+#define chgrp_full_usage "\n\n" \
+       "Change the group membership of each FILE to GROUP\n" \
+     "\nOptions:" \
+     "\n       -R      Recurse directories" \
+     "\n       -h      Affect symlinks instead of symlink targets" \
+     "\n       -L      Traverse all symlinks to directories" \
+     "\n       -H      Traverse symlinks on command line only" \
+     "\n       -P      Do not traverse symlinks (default)" \
+       USE_DESKTOP( \
+     "\n       -c      List changed files" \
+     "\n       -v      Verbose" \
+     "\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 chown_trivial_usage \
+       "[-RhLHP"USE_DESKTOP("cvf")"]... OWNER[<.|:>[GROUP]] FILE..."
+#define chown_full_usage "\n\n" \
+       "Change the owner and/or group of each FILE to OWNER and/or GROUP\n" \
+     "\nOptions:" \
+     "\n       -R      Recurse directories" \
+     "\n       -h      Affect symlinks instead of symlink targets" \
+     "\n       -L      Traverse all symlinks to directories" \
+     "\n       -H      Traverse symlinks on command line only" \
+     "\n       -P      Do not traverse symlinks (default)" \
+       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[:GRP]] [-U USER[:GRP]] [-e DIR]\n" \
+       "       [-/ DIR] [-n NICE] [-m BYTES] [-d BYTES] [-o N]\n" \
+       "       [-p N] [-f BYTES] [-c BYTES] PROG ARGS"
+#define chpst_full_usage "\n\n" \
+       "Change the process state and run PROG\n" \
+     "\nOptions:" \
+     "\n       -u USER[:GRP]   Set uid and gid" \
+     "\n       -U USER[:GRP]   Set $UID and $GID in environment" \
+     "\n       -e DIR          Set environment variables as specified by files" \
+     "\n                       in DIR: file=1st_line_of_file" \
+     "\n       -/ DIR          Chroot to DIR" \
+     "\n       -n NICE         Add NICE to nice value" \
+     "\n       -m BYTES        Same as -d BYTES -s BYTES -l BYTES" \
+     "\n       -d BYTES        Limit data segment" \
+     "\n       -o N            Limit number of open files per process" \
+     "\n       -p N            Limit number of processes per uid" \
+     "\n       -f BYTES        Limit output file sizes" \
+     "\n       -c BYTES        Limit core file size" \
+     "\n       -v              Verbose" \
+     "\n       -P              Create 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 "\n\n" \
+       "Set uid and gid to account's uid and gid, removing all supplementary\n" \
+       "groups and run PROG"
+#define envuidgid_trivial_usage \
+       "account prog args"
+#define envuidgid_full_usage "\n\n" \
+       "Set $UID to account's uid and $GID to account's gid and run PROG"
+#define envdir_trivial_usage \
+       "dir prog args"
+#define envdir_full_usage "\n\n" \
+       "Set various environment variables as specified by files\n" \
+       "in the directory dir and run PROG"
+#define softlimit_trivial_usage \
+       "[-a BYTES] [-m BYTES] [-d BYTES] [-s BYTES] [-l BYTES]\n" \
+       "       [-f BYTES] [-c BYTES] [-r BYTES] [-o N] [-p N] [-t N]\n" \
+       "       PROG ARGS"
+#define softlimit_full_usage "\n\n" \
+       "Set soft resource limits, then run PROG\n" \
+     "\nOptions:" \
+     "\n       -a BYTES        Limit total size of all segments" \
+     "\n       -m BYTES        Same as -d BYTES -s BYTES -l BYTES -a BYTES" \
+     "\n       -d BYTES        Limit data segment" \
+     "\n       -s BYTES        Limit stack segment" \
+     "\n       -l BYTES        Limit locked memory size" \
+     "\n       -o N            Limit number of open files per process" \
+     "\n       -p N            Limit number of processes per uid" \
+     "\nOptions controlling file sizes:" \
+     "\n       -f BYTES        Limit output file sizes" \
+     "\n       -c BYTES        Limit core file size" \
+     "\nEfficiency opts:" \
+     "\n       -r BYTES        Limit resident set size" \
+     "\n       -t N            Limit CPU time, process receives" \
+     "\n                       a SIGXCPU after N seconds" \
+
+#define chroot_trivial_usage \
+       "NEWROOT [COMMAND...]"
+#define chroot_full_usage "\n\n" \
+       "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 "\n\n" \
+       "Change the foreground virtual terminal to /dev/ttyN"
+
+#define cksum_trivial_usage \
+       "FILES..."
+#define cksum_full_usage "\n\n" \
+       "Calculate the CRC32 checksums of FILES"
+
+#define clear_trivial_usage \
+       ""
+#define clear_full_usage "\n\n" \
+       "Clear screen"
+
+#define cmp_trivial_usage \
+       "[-l] [-s] FILE1 [FILE2" USE_DESKTOP(" [SKIP1 [SKIP2]") "]]"
+#define cmp_full_usage "\n\n" \
+       "Compares FILE1 vs stdin if FILE2 is not specified\n" \
+     "\nOptions:" \
+     "\n       -l      Write the byte numbers (decimal) and values (octal)" \
+     "\n               for all differing bytes" \
+     "\n       -s      Quiet" \
+
+#define comm_trivial_usage \
+       "[-123] FILE1 FILE2"
+#define comm_full_usage "\n\n" \
+       "Compare FILE1 to FILE2, or to stdin if - is specified\n" \
+     "\nOptions:" \
+     "\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 "\n\n" \
+       "Print the config file which built busybox"
+
+#define bbsh_trivial_usage \
+       "[FILE]...\n" \
+       "or: bbsh -c command [args]..."
+#define bbsh_full_usage "\n\n" \
+       "The bbsh shell (command interpreter)"
+
+#define chrt_trivial_usage \
+       "[OPTION]... [prio] [pid | command [arg]...]"
+#define chrt_full_usage "\n\n" \
+       "Manipulate real-time attributes of a process\n" \
+     "\nOptions:" \
+     "\n       -p      Operate on pid" \
+     "\n       -r      Set scheduling policy to SCHED_RR" \
+     "\n       -f      Set scheduling policy to SCHED_FIFO" \
+     "\n       -o      Set scheduling policy to SCHED_OTHER" \
+     "\n       -m      Show min and max priorities" \
+
+#define chrt_example_usage \
+       "$ chrt -r 4 sleep 900; x=$!\n" \
+       "$ chrt -f -p 3 $x\n" \
+       "You need CAP_SYS_NICE privileges to set scheduling attributes of a process"
+
+#define cp_trivial_usage \
+       "[OPTION]... SOURCE DEST"
+#define cp_full_usage "\n\n" \
+       "Copy SOURCE to DEST, or multiple SOURCE(s) to DIRECTORY\n" \
+     "\nOptions:" \
+     "\n       -a      Same as -dpR" \
+       USE_SELINUX( \
+     "\n       -c      Preserve security context" \
+       ) \
+     "\n       -d,-P   Preserve links" \
+     "\n       -H,-L   Dereference all symlinks (default)" \
+     "\n       -p      Preserve file attributes if possible" \
+     "\n       -f      Force overwrite" \
+     "\n       -i      Prompt before overwrite" \
+     "\n       -R,-r   Recurse directories" \
+     "\n       -l,-s   Create (sym)links" \
+
+#define cpio_trivial_usage \
+       "-[ti" USE_FEATURE_CPIO_O("o") USE_FEATURE_CPIO_P("p") "dmvu] [-F FILE]" \
+       USE_FEATURE_CPIO_O( " [-H newc]" )
+#define cpio_full_usage "\n\n" \
+       "Extract or list files from a cpio archive" \
+       USE_FEATURE_CPIO_O( ", or create a cpio archive" ) \
+     "\nMain operation mode:" \
+     "\n       -t      List" \
+     "\n       -i      Extract" \
+       USE_FEATURE_CPIO_O( \
+     "\n       -o      Create" \
+       ) \
+       USE_FEATURE_CPIO_P( \
+     "\n       -p      Passthrough" \
+       ) \
+     "\nOptions:" \
+     "\n       -d      Make leading directories" \
+     "\n       -m      Preserve mtime" \
+     "\n       -v      Verbose" \
+     "\n       -u      Overwrite" \
+     "\n       -F      Input file" \
+       USE_FEATURE_CPIO_O( \
+     "\n       -H      Define format" \
+       ) \
+
+#define crond_trivial_usage \
+       "-fbS -l N " USE_FEATURE_CROND_D("-d N ") "-L LOGFILE -c DIR"
+#define crond_full_usage "\n\n" \
+       "       -f      Foreground" \
+     "\n       -b      Background (default)" \
+     "\n       -S      Log to syslog (default)" \
+     "\n       -l      Set log level. 0 is the most verbose, default 8" \
+       USE_FEATURE_CROND_D( \
+     "\n       -d      Set log level, log to stderr" \
+       ) \
+     "\n       -L      Log to file" \
+     "\n       -c      Working dir" \
+
+#define crontab_trivial_usage \
+       "[-c DIR] [-u USER] [-ler]|[FILE]"
+#define crontab_full_usage "\n\n" \
+       "       -c      Crontab directory" \
+     "\n       -u      User" \
+     "\n       -l      List crontab" \
+     "\n       -e      Edit crontab" \
+     "\n       -r      Delete crontab" \
+     "\n       FILE    Replace crontab by FILE ('-': stdin)" \
+
+#define cryptpw_trivial_usage \
+       "[OPTIONS] [PASSWORD] [SALT]"
+/* We do support -s, we just don't mention it */
+#define cryptpw_full_usage "\n\n" \
+       "Crypt the PASSWORD using crypt(3)\n" \
+     "\nOptions:" \
+       USE_GETOPT_LONG( \
+     "\n       -P,--password-fd=NUM    Read password from fd NUM" \
+/*   "\n       -s,--stdin              Use stdin; like -P0" */ \
+     "\n       -m,--method=TYPE        Encryption method TYPE" \
+     "\n       -S,--salt=SALT" \
+       ) \
+       SKIP_GETOPT_LONG( \
+     "\n       -P NUM  Read password from fd NUM" \
+/*   "\n       -s      Use stdin; like -P0" */ \
+     "\n       -m TYPE Encryption method TYPE" \
+     "\n       -S SALT" \
+       ) \
+
+/* mkpasswd is an alias to cryptpw */
+
+#define mkpasswd_trivial_usage \
+       "[OPTIONS] [PASSWORD] [SALT]"
+/* We do support -s, we just don't mention it */
+#define mkpasswd_full_usage "\n\n" \
+       "Crypt the PASSWORD using crypt(3)\n" \
+     "\nOptions:" \
+       USE_GETOPT_LONG( \
+     "\n       -P,--password-fd=NUM    Read password from fd NUM" \
+/*   "\n       -s,--stdin              Use stdin; like -P0" */ \
+     "\n       -m,--method=TYPE        Encryption method TYPE" \
+     "\n       -S,--salt=SALT" \
+       ) \
+       SKIP_GETOPT_LONG( \
+     "\n       -P NUM  Read password from fd NUM" \
+/*   "\n       -s      Use stdin; like -P0" */ \
+     "\n       -m TYPE Encryption method TYPE" \
+     "\n       -S SALT" \
+       ) \
+
+#define cttyhack_trivial_usage NOUSAGE_STR
+#define cttyhack_full_usage ""
+
+#define cut_trivial_usage \
+       "[OPTION]... [FILE]..."
+#define cut_full_usage "\n\n" \
+       "Print selected fields from each input FILE to standard output\n" \
+     "\nOptions:" \
+     "\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]... [+FMT] [TIME]"
+#define date_full_usage "\n\n" \
+       "Display time (using +FMT), or set time\n" \
+     "\nOptions:" \
+     "\n       -u              Work in UTC (don't convert to local time)" \
+     "\n       -R              Output RFC-822 compliant date string" \
+       USE_FEATURE_DATE_ISOFMT( \
+     "\n       -I[SPEC]        Output ISO-8601 compliant date string" \
+     "\n                       SPEC='date' (default) for date only," \
+     "\n                       'hours', 'minutes', or 'seconds' for date and" \
+     "\n                       time to the indicated precision" \
+       ) \
+     "\n       -d TIME         Display TIME, not 'now'" \
+     "\n       -r FILE         Display last modification time of FILE" \
+     "\n       [-s] TIME       Set time to TIME" \
+       USE_FEATURE_DATE_ISOFMT( \
+     "\n       -D FMT          Use FMT for str->date conversion" \
+       ) \
+     "\n" \
+     "\nRecognized formats for TIME:" \
+     "\n       hh:mm[:ss]" \
+     "\n       [YYYY.]MM.DD-hh:mm[:ss]" \
+     "\n       YYYY-MM-DD hh:mm[:ss]" \
+     "\n       MMDDhhmm[[YY]YY][.ss]" \
+
+#define date_example_usage \
+       "$ date\n" \
+       "Wed Apr 12 18:52:41 MDT 2000\n"
+
+#define dc_trivial_usage \
+       "expression..."
+#define dc_full_usage "\n\n" \
+       "Tiny RPN calculator. Operations:\n" \
+       "+, add, -, sub, *, mul, /, div, %, mod, **, exp, and, or, not, eor,\n" \
+       "p - print top of the stack (without altering the stack),\n" \
+       "f - print entire stack, o - pop the value and set output radix\n" \
+       "(value must be 10 or 16).\n" \
+       "Examples: 'dc 2 2 add' -> 4, 'dc 8 8 * 2 2 + /' -> 16.\n" \
+
+#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|fsync]")
+#define dd_full_usage "\n\n" \
+       "Copy a file with converting and formatting\n" \
+     "\nOptions:" \
+     "\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" \
+       USE_FEATURE_DD_IBS_OBS( \
+     "\n       ibs=N           Read N bytes at a time" \
+       ) \
+       USE_FEATURE_DD_IBS_OBS( \
+     "\n       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" \
+       USE_FEATURE_DD_IBS_OBS( \
+     "\n       conv=notrunc    Don't truncate output file" \
+     "\n       conv=noerror    Continue after read errors" \
+     "\n       conv=sync       Pad blocks with zeros" \
+     "\n       conv=fsync      Physically write data out before finishing" \
+       ) \
+     "\n" \
+     "\nNumbers may be suffixed by c (x1), w (x2), b (x512), kD (x1000), k (x1024)," \
+     "\nMD (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 "\n\n" \
+       "Deallocate unused virtual terminal /dev/ttyN"
+
+#define delgroup_trivial_usage \
+       USE_FEATURE_DEL_USER_FROM_GROUP("[USER] ")"GROUP"
+#define delgroup_full_usage "\n\n" \
+       "Delete group GROUP from the system" \
+       USE_FEATURE_DEL_USER_FROM_GROUP(" or user USER from group GROUP")
+
+#define deluser_trivial_usage \
+       "USER"
+#define deluser_full_usage "\n\n" \
+       "Delete USER from the system"
+
+#define depmod_trivial_usage NOUSAGE_STR
+#define depmod_full_usage ""
+
+#define devmem_trivial_usage \
+       "ADDRESS [WIDTH [VALUE]]"
+
+#define devmem_full_usage "\n\n" \
+       "Read/write from physical address\n" \
+     "\n       ADDRESS Address to act upon" \
+     "\n       WIDTH   Width (8/16/...)" \
+     "\n       VALUE   Data to be written" \
+
+#define devfsd_trivial_usage \
+       "mntpnt [-v]" USE_DEVFSD_FG_NP("[-fg][-np]")
+#define devfsd_full_usage "\n\n" \
+       "Manage devfs permissions and old device name symlinks\n" \
+     "\nOptions:" \
+     "\n       mntpnt  The mount point where devfs is mounted" \
+     "\n       -v      Print the protocol version numbers for devfsd" \
+     "\n               and the kernel-side protocol version and exit" \
+       USE_DEVFSD_FG_NP( \
+     "\n       -fg     Run in foreground" \
+     "\n       -np     Exit after parsing the configuration file" \
+     "\n               and processing synthetic REGISTER events," \
+     "\n               do not poll for events" \
+       )
+
+#define df_trivial_usage \
+       "[-Pk" \
+       USE_FEATURE_HUMAN_READABLE("mh") \
+       USE_FEATURE_DF_FANCY("ai] [-B SIZE") \
+       "] [FILESYSTEM...]"
+#define df_full_usage "\n\n" \
+       "Print filesystem usage statistics\n" \
+     "\nOptions:" \
+     "\n       -P      POSIX output format" \
+     "\n       -k      1024-byte blocks (default)" \
+       USE_FEATURE_HUMAN_READABLE( \
+     "\n       -m      1M-byte blocks" \
+     "\n       -h      Human readable (e.g. 1K 243M 2G)" \
+       ) \
+       USE_FEATURE_DF_FANCY( \
+     "\n       -a      Show all filesystems" \
+     "\n       -i      Inodes" \
+     "\n       -B SIZE Blocksize" \
+       ) \
+
+#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" \
+       "$ POSIXLY_CORRECT=sure df /dev/sda3\n" \
+       "Filesystem         512B-blocks      Used Available Use% Mounted on\n" \
+       "/dev/sda3             17381728  17107080    274648  98% /\n" \
+       "$ POSIXLY_CORRECT=yep df -P /dev/sda3\n" \
+       "Filesystem          512-blocks      Used Available Capacity Mounted on\n" \
+       "/dev/sda3             17381728  17107080    274648      98% /\n"
+
+#define dhcprelay_trivial_usage \
+       "CLIENT_IFACE[,CLIENT_IFACE2...] SERVER_IFACE [SERVER_IP]"
+#define dhcprelay_full_usage "\n\n" \
+       "Relay DHCP requests between clients and server" \
+
+#define diff_trivial_usage \
+       "[-abdiNqrTstw] [-L LABEL] [-S FILE] [-U LINES] FILE1 FILE2"
+#define diff_full_usage "\n\n" \
+       "Compare files line by line and output the differences between them.\n" \
+       "This implementation supports unified diffs only.\n" \
+     "\nOptions:" \
+     "\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 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" \
+
+#define dirname_trivial_usage \
+       "FILENAME"
+#define dirname_full_usage "\n\n" \
+       "Strip 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 "\n\n" \
+       "Print or control the kernel ring buffer\n" \
+     "\nOptions:" \
+     "\n       -c              Clear ring buffer after printing" \
+     "\n       -n LEVEL        Set console logging level" \
+     "\n       -s SIZE         Buffer size" \
+
+#define dnsd_trivial_usage \
+       "[-c config] [-t seconds] [-p port] [-i iface-ip] [-d]"
+#define dnsd_full_usage "\n\n" \
+       "Small static DNS server daemon\n" \
+     "\nOptions:" \
+     "\n       -c      Config filename" \
+     "\n       -t      TTL in seconds" \
+     "\n       -p      Listening port" \
+     "\n       -i      Listening ip (default all)" \
+     "\n       -d      Daemonize" \
+
+#define dos2unix_trivial_usage \
+       "[option] [FILE]"
+#define dos2unix_full_usage "\n\n" \
+       "Convert FILE from dos to unix format.\n" \
+       "When no file is given, use stdin/stdout.\n" \
+     "\nOptions:" \
+     "\n       -u      dos2unix" \
+     "\n       -d      unix2dos" \
+
+#define dpkg_trivial_usage \
+       "[-ilCPru] [-F option] package_name"
+#define dpkg_full_usage "\n\n" \
+       "Install, remove and manage Debian packages\n" \
+     "\nOptions:" \
+     "\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 "\n\n" \
+       "Perform actions on Debian packages (.debs)\n" \
+     "\nOptions:" \
+     "\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 "\n\n" \
+       "Summarize 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" \
+     "\nOptions:" \
+     "\n       -a      Show file sizes too" \
+     "\n       -H      Follow symlinks on command line" \
+     "\n       -L      Follow all symlinks" \
+     "\n       -d N    Limit output to directories (and files with -a) of depth < N" \
+     "\n       -c      Show 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" \
+       USE_FEATURE_HUMAN_READABLE( \
+     "\n       -h      Sizes in human readable format (e.g., 1K 243M 2G )" \
+     "\n       -m      Sizes in megabytes" \
+       ) \
+     "\n       -k      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 "\n\n" \
+       "Print 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 "\n\n" \
+       "Display DHCP leases granted by udhcpd\n" \
+     "\nOptions:" \
+       USE_GETOPT_LONG( \
+     "\n       -f,--file=FILE  Leases file to load" \
+     "\n       -r,--remaining  Interpret lease times as time remaining" \
+     "\n       -a,--absolute   Interpret lease times as expire time" \
+       ) \
+       SKIP_GETOPT_LONG( \
+     "\n       -f FILE Leases file to load" \
+     "\n       -r      Interpret lease times as time remaining" \
+     "\n       -a      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 "\n\n" \
+       "Check ext2/ext3 file system\n" \
+     "\nOptions:" \
+     "\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              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 "\n\n" \
+       "Print the specified ARGs to stdout" \
+       USE_FEATURE_FANCY_ECHO( "\n" \
+     "\nOptions:" \
+     "\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 "\n\n" \
+       "Eject specified DEVICE (or default /dev/cdrom)\n" \
+     "\nOptions:" \
+       USE_FEATURE_EJECT_SCSI( \
+     "\n       -s      SCSI device" \
+       ) \
+     "\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 "\n\n" \
+       "Print the current environment or run a program after setting\n" \
+       "up the specified environment\n" \
+     "\nOptions:" \
+     "\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 "\n\n" \
+       "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" \
+     "\nOptions:" \
+     "\n       -b              Send wake-up packet to the broadcast address" \
+     "\n       -i iface        Interface to use (default eth0)" \
+     "\n       -p pass         Append four or six byte password PW to the packet" \
+
+#define expand_trivial_usage \
+       "[-i] [-t NUM] [FILE|-]"
+#define expand_full_usage "\n\n" \
+       "Convert tabs to spaces, writing to standard output.\n" \
+     "\nOptions:" \
+       USE_FEATURE_EXPAND_LONG_OPTIONS( \
+     "\n       -i,--initial    Do not convert tabs after non blanks" \
+     "\n       -t,--tabs=N     Tabstops every N chars" \
+       ) \
+       SKIP_FEATURE_EXPAND_LONG_OPTIONS( \
+     "\n       -i      Do not convert tabs after non blanks" \
+     "\n       -t      Tabstops every N chars" \
+       )
+
+#define expr_trivial_usage \
+       "EXPRESSION"
+#define expr_full_usage "\n\n" \
+       "Print 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     1 if ARG1 is less than ARG2, else 0. Similarly:\n" \
+       "       ARG1 <= ARG2\n" \
+       "       ARG1 = ARG2\n" \
+       "       ARG1 != ARG2\n" \
+       "       ARG1 >= ARG2\n" \
+       "       ARG1 > ARG2\n" \
+       "       ARG1 + ARG2     Sum of ARG1 and ARG2. Similarly:\n" \
+       "       ARG1 - ARG2\n" \
+       "       ARG1 * ARG2\n" \
+       "       ARG1 / ARG2\n" \
+       "       ARG1 % 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, 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 \
+       "[-fiw] [-b ADDR] [STRING]"
+#define fakeidentd_full_usage "\n\n" \
+       "Provide fake ident (auth) service\n" \
+     "\nOptions:" \
+     "\n       -f      Run in foreground" \
+     "\n       -i      Inetd mode" \
+     "\n       -w      Inetd 'wait' mode" \
+     "\n       -b ADDR Bind to specified address" \
+     "\n       STRING  Ident answer string (default is 'nobody')" \
+
+#define false_trivial_usage \
+       ""
+#define false_full_usage "\n\n" \
+       "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 "\n\n" \
+       "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 "\n\n" \
+       "Force floppy disk drive to detect disk change"
+
+#define fdformat_trivial_usage \
+       "[-n] DEVICE"
+#define fdformat_full_usage "\n\n" \
+       "Format floppy disk\n" \
+     "\nOptions:" \
+     "\n       -n      Don't verify after format" \
+
+/* 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 fdisk_trivial_usage \
+       "[-ul" USE_FEATURE_FDISK_BLKSIZE("s") "] " \
+       "[-C CYLINDERS] [-H HEADS] [-S SECTORS] [-b SSZ] DISK"
+#define fdisk_full_usage "\n\n" \
+       "Change partition table\n" \
+     "\nOptions:" \
+     "\n       -u              Start and End are in sectors (instead of cylinders)" \
+     "\n       -l              Show partition table for each DISK, then exit" \
+       USE_FEATURE_FDISK_BLKSIZE( \
+     "\n       -s              Show partition sizes in kb for each DISK, then exit" \
+       ) \
+     "\n       -b 2048         (for certain MO disks) use 2048-byte sectors" \
+     "\n       -C CYLINDERS    Set number of cylinders/heads/sectors" \
+     "\n       -H HEADS\n" \
+     "\n       -S SECTORS" \
+
+#define blkid_trivial_usage \
+       ""
+#define blkid_full_usage "\n\n" \
+       "Print UUIDs of all filesystems."
+
+#define findfs_trivial_usage \
+       "LABEL=label or UUID=uuid"
+#define findfs_full_usage "\n\n" \
+       "Find a filesystem device based on a label or UUID."
+#define findfs_example_usage \
+       "$ findfs LABEL=MyDevice"
+
+#define find_trivial_usage \
+       "[PATH...] [EXPRESSION]"
+#define find_full_usage "\n\n" \
+       "Search for files. The default PATH is the current directory,\n" \
+       "default EXPRESSION is '-print'\n" \
+     "\nEXPRESSION may consist of:" \
+     "\n       -follow         Dereference symlinks" \
+       USE_FEATURE_FIND_XDEV( \
+     "\n       -xdev           Don't descend directories on other filesystems") \
+       USE_FEATURE_FIND_MAXDEPTH( \
+     "\n       -maxdepth N     Descend at most N levels. -maxdepth 0 applies" \
+     "\n                       tests/actions to command line arguments only") \
+     "\n       -mindepth N     Do not act on first N levels" \
+     "\n       -name PATTERN   File name (w/o directory name) matches PATTERN" \
+     "\n       -iname PATTERN  Case insensitive -name" \
+       USE_FEATURE_FIND_PATH( \
+     "\n       -path PATTERN   Path matches PATTERN") \
+       USE_FEATURE_FIND_REGEX( \
+     "\n       -regex PATTERN  Path matches regex PATTERN") \
+       USE_FEATURE_FIND_TYPE( \
+     "\n       -type X         File type is X (X is one of: f,d,l,b,c,...)") \
+       USE_FEATURE_FIND_PERM( \
+     "\n       -perm NNN       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_USER( \
+     "\n       -user NAME      File is owned by user NAME (numeric user ID allowed)") \
+       USE_FEATURE_FIND_GROUP( \
+     "\n       -group NAME     File belongs to group NAME (numeric group ID allowed)") \
+       USE_FEATURE_FIND_DEPTH( \
+     "\n       -depth          Process directory name after traversing it") \
+       USE_FEATURE_FIND_SIZE( \
+     "\n       -size N[bck]    File size is N (c:bytes,k:kbytes,b:512 bytes(def.))." \
+     "\n                       +/-N: file size is bigger/smaller than N") \
+     "\n       -print          Print (default and assumed)" \
+       USE_FEATURE_FIND_PRINT0( \
+     "\n       -print0         Delimit output with null characters rather than" \
+     "\n                       newlines") \
+       USE_FEATURE_FIND_CONTEXT ( \
+     "\n       -context        File has specified security context") \
+       USE_FEATURE_FIND_EXEC( \
+     "\n       -exec CMD ARG ; Execute CMD with all instances of {} replaced by the" \
+     "\n                       matching files") \
+       USE_FEATURE_FIND_PRUNE( \
+     "\n       -prune          Stop traversing current subtree") \
+       USE_FEATURE_FIND_DELETE( \
+     "\n       -delete         Delete files, turns on -depth option") \
+       USE_FEATURE_FIND_PAREN( \
+     "\n       (EXPR)          Group an expression") \
+
+#define find_example_usage \
+       "$ find / -name passwd\n" \
+       "/etc/passwd\n"
+
+#define flash_eraseall_trivial_usage \
+       "[-jq] MTD_DEVICE"
+#define flash_eraseall_full_usage "\n\n" \
+       "Erase an MTD device\n" \
+     "\nOptions:" \
+     "\n       -j      format the device for jffs2" \
+     "\n       -q      don't display progress messages"
+
+#define fold_trivial_usage \
+       "[-bs] [-w WIDTH] [FILE]"
+#define fold_full_usage "\n\n" \
+       "Wrap input lines in each FILE (standard input by default), writing to\n" \
+       "standard output\n" \
+     "\nOptions:" \
+     "\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 "\n\n" \
+       "Display 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 "\n\n" \
+       "Free 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 "\n\n" \
+       "Check and repair filesystems\n" \
+     "\nOptions:" \
+     "\n       -A      Walk /etc/fstab and check all filesystems" \
+     "\n       -N      Don't execute, just show what would be done" \
+     "\n       -P      With -A, check filesystems in parallel" \
+     "\n       -R      With -A, skip the root filesystem" \
+     "\n       -T      Don't show title on startup" \
+     "\n       -V      Verbose" \
+     "\n       -C n    Write status information to specified filedescriptor" \
+     "\n       -t type List of filesystem types to check" \
+
+#define fsck_minix_trivial_usage \
+       "[-larvsmf] /dev/name"
+#define fsck_minix_full_usage "\n\n" \
+       "Check MINIX filesystem\n" \
+     "\nOptions:" \
+     "\n       -l      List all filenames" \
+     "\n       -r      Perform interactive repairs" \
+     "\n       -a      Perform automatic repairs" \
+     "\n       -v      Verbose" \
+     "\n       -s      Output superblock information" \
+     "\n       -m      Show \"mode not cleared\" warnings" \
+     "\n       -f      Force file system check" \
+
+#define ftpd_trivial_usage \
+       "[-wvS] [-t N] [-T N] [DIR]"
+#define ftpd_full_usage "\n\n" \
+       "FTP server\n" \
+       "\n" \
+       "ftpd should be used as an inetd service.\n" \
+       "ftpd's line for inetd.conf:\n" \
+       "       21 stream tcp nowait root ftpd ftpd /files/to/serve\n" \
+       "It also can be ran from tcpsvd:\n" \
+       "       tcpsvd -vE 0.0.0.0 21 ftpd /files/to/serve\n" \
+     "\nOptions:" \
+     "\n       -w      Allow upload" \
+     "\n       -v      Log to stderr" \
+     "\n       -S      Log to syslog" \
+     "\n       -t,-T   Idle and absolute timeouts" \
+     "\n       DIR     Change root to this directory" \
+
+#define ftpget_trivial_usage \
+       "[options] remote-host local-file remote-file"
+#define ftpget_full_usage "\n\n" \
+       "Retrieve a remote file via FTP\n" \
+     "\nOptions:" \
+       USE_FEATURE_FTPGETPUT_LONG_OPTIONS( \
+     "\n       -c,--continue   Continue previous transfer" \
+     "\n       -v,--verbose    Verbose" \
+     "\n       -u,--username   Username" \
+     "\n       -p,--password   Password" \
+     "\n       -P,--port       Port number" \
+       ) \
+       SKIP_FEATURE_FTPGETPUT_LONG_OPTIONS( \
+     "\n       -c      Continue previous transfer" \
+     "\n       -v      Verbose" \
+     "\n       -u      Username" \
+     "\n       -p      Password" \
+     "\n       -P      Port number" \
+       )
+
+#define ftpput_trivial_usage \
+       "[options] remote-host remote-file local-file"
+#define ftpput_full_usage "\n\n" \
+       "Store a local file on a remote machine via FTP\n" \
+     "\nOptions:" \
+       USE_FEATURE_FTPGETPUT_LONG_OPTIONS( \
+     "\n       -v,--verbose    Verbose" \
+     "\n       -u,--username   Username" \
+     "\n       -p,--password   Password" \
+     "\n       -P,--port       Port number" \
+       ) \
+       SKIP_FEATURE_FTPGETPUT_LONG_OPTIONS( \
+     "\n       -v      Verbose" \
+     "\n       -u      Username" \
+     "\n       -p      Password" \
+     "\n       -P      Port number" \
+       )
+
+#define fuser_trivial_usage \
+       "[options] FILE or PORT/PROTO"
+#define fuser_full_usage "\n\n" \
+       "Find processes which use FILEs or PORTs\n" \
+     "\nOptions:" \
+     "\n       -m      Find processes which use same fs as FILEs" \
+     "\n       -4      Search only IPv4 space" \
+     "\n       -6      Search only IPv6 space" \
+     "\n       -s      Silent: just exit with 0 if any processes are found" \
+     "\n       -k      Kill found processes (otherwise display PIDs)" \
+     "\n       -SIGNAL Signal to send (default: TERM)" \
+
+#define getenforce_trivial_usage NOUSAGE_STR
+#define getenforce_full_usage ""
+
+#define getopt_trivial_usage \
+       "[OPTIONS]..."
+#define getopt_full_usage "\n\n" \
+       "Parse command options\n" \
+       USE_GETOPT_LONG( \
+     "\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                   Don't quote the output" \
+       ) \
+       SKIP_GETOPT_LONG( \
+     "\n       -a              Allow long options starting with single -" \
+     "\n       -l longopts     Long options to be recognized" \
+     "\n       -n progname     The name under which errors are reported" \
+     "\n       -o optstring    Short options to be recognized" \
+     "\n       -q              Disable error reporting by getopt(3)" \
+     "\n       -Q              No normal output" \
+     "\n       -s shell        Set shell quoting conventions" \
+     "\n       -T              Test for getopt(1) version" \
+     "\n       -u              Don't 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 getsebool_trivial_usage \
+       "-a or getsebool boolean..."
+#define getsebool_full_usage "\n\n" \
+       "       -a      Show all SELinux booleans"
+
+#define getty_trivial_usage \
+       "[OPTIONS] BAUD_RATE TTY [TERMTYPE]"
+#define getty_full_usage "\n\n" \
+       "Open a tty, prompt for a login name, then invoke /bin/login\n" \
+     "\nOptions:" \
+     "\n       -h              Enable hardware (RTS/CTS) flow control" \
+     "\n       -i              Do not display /etc/issue before running login" \
+     "\n       -L              Local line, 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   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 \
+       "[-HhrilLnqvso" \
+       USE_DESKTOP("w") \
+       "eF" \
+       USE_FEATURE_GREP_EGREP_ALIAS("E") \
+       USE_FEATURE_GREP_CONTEXT("ABC") \
+       USE_EXTRA_COMPAT("z") \
+       "] PATTERN [FILEs...]"
+#define grep_full_usage "\n\n" \
+       "Search for PATTERN in each FILE or standard input\n" \
+     "\nOptions:" \
+     "\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      Quiet. Return 0 if PATTERN is 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       -o      Show only the part of a line that matches PATTERN" \
+     "\n       -m MAX  Match up to MAX times per file" \
+       USE_DESKTOP( \
+     "\n       -w      Match whole words only") \
+     "\n       -F      PATTERN is a set of newline-separated strings" \
+       USE_FEATURE_GREP_EGREP_ALIAS( \
+     "\n       -E      PATTERN is an extended regular expression") \
+     "\n       -e PTRN Pattern to match" \
+     "\n       -f FILE Read pattern from file" \
+       USE_FEATURE_GREP_CONTEXT( \
+     "\n       -A      Print NUM lines of trailing context" \
+     "\n       -B      Print NUM lines of leading context" \
+     "\n       -C      Print NUM lines of output context") \
+       USE_EXTRA_COMPAT( \
+     "\n       -z      Input is NUL terminated") \
+
+#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 egrep_trivial_usage NOUSAGE_STR
+#define egrep_full_usage ""
+
+#define fgrep_trivial_usage NOUSAGE_STR
+#define fgrep_full_usage ""
+
+#define gunzip_trivial_usage \
+       "[OPTION]... [FILE]..."
+#define gunzip_full_usage "\n\n" \
+       "Uncompress FILEs (or standard input)\n" \
+     "\nOptions:" \
+     "\n       -c      Write to standard output" \
+     "\n       -f      Force" \
+     "\n       -t      Test 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 "\n\n" \
+       "Compress FILEs (or standard input)\n" \
+     "\nOptions:" \
+     "\n       -c      Write to standard output" \
+     "\n       -d      Decompress" \
+     "\n       -f      Force" \
+
+#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 delay] [-n] [-f]" USE_FEATURE_WTMP(" [-w]")
+#define halt_full_usage "\n\n" \
+       "Halt the system\n" \
+     "\nOptions:" \
+     "\n       -d      Delay interval for halting" \
+     "\n       -n      No call to sync()" \
+     "\n       -f      Force halt (don't go through init)" \
+       USE_FEATURE_WTMP( \
+     "\n       -w      Only write a wtmp record" \
+       )
+
+#define hdparm_trivial_usage \
+       "[options] [device] .."
+#define hdparm_full_usage "\n\n" \
+       "Options:" \
+     "\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" \
+       USE_FEATURE_HDPARM_HDIO_GETSET_DMA( \
+     "\n       -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" \
+       USE_FEATURE_HDPARM_GET_IDENTITY( \
+     "\n       -i      Display drive identification") \
+       USE_FEATURE_HDPARM_GET_IDENTITY( \
+     "\n       -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" - not supported ib bbox */ \
+     "\n       -Q      Get/set DMA tagged-queuing depth (if supported)" \
+     "\n       -r      Get/set readonly flag (DANGEROUS to set)" \
+       USE_FEATURE_HDPARM_HDIO_SCAN_HWIF( \
+     "\n       -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)" \
+       USE_FEATURE_HDPARM_HDIO_UNREGISTER_HWIF( \
+     "\n       -U      Un-register an IDE interface (DANGEROUS)") \
+     "\n       -v      Defaults; same as -mcudkrag for IDE drives" \
+     "\n       -V      Display program version and exit immediately" \
+       USE_FEATURE_HDPARM_HDIO_DRIVE_RESET( \
+     "\n       -w      Perform device reset (DANGEROUS)") \
+     "\n       -W      Set drive write-caching flag (0/1) (DANGEROUS)" \
+       USE_FEATURE_HDPARM_HDIO_TRISTATE_HWIF( \
+     "\n       -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 "\n\n" \
+       "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" \
+     "\nOptions:" \
+     "\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" USE_FEATURE_HEXDUMP_REVERSE("R") "] FILE..."
+#define hexdump_full_usage "\n\n" \
+       "Display file(s) or standard input in a user specified format\n" \
+     "\nOptions:" \
+     "\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 bytes" \
+     "\n       -v              Display all input data" \
+     "\n       -x              Two-byte hexadecimal display" \
+       USE_FEATURE_HEXDUMP_REVERSE( \
+     "\n       -R              Reverse of 'hexdump -Cv'") \
+
+#define hd_trivial_usage \
+       "FILE..."
+#define hd_full_usage "\n\n" \
+       "hd is an alias for hexdump -C"
+
+#define hostid_trivial_usage \
+       ""
+#define hostid_full_usage "\n\n" \
+       "Print out a unique 32-bit identifier for the machine"
+
+#define hostname_trivial_usage \
+       "[OPTION] [hostname | -F FILE]"
+#define hostname_full_usage "\n\n" \
+       "Get or set hostname or DNS domain name\n" \
+     "\nOptions:" \
+     "\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 conffile]" \
+       " [-p [ip:]port]" \
+       " [-i] [-f] [-v[v]]" \
+       USE_FEATURE_HTTPD_SETUID(" [-u user[:grp]]") \
+       USE_FEATURE_HTTPD_BASIC_AUTH(" [-r realm]") \
+       USE_FEATURE_HTTPD_AUTH_MD5(" [-m pass]") \
+       " [-h home]" \
+       " [-d/-e string]"
+#define httpd_full_usage "\n\n" \
+       "Listen for incoming HTTP requests\n" \
+     "\nOptions:" \
+     "\n       -c FILE         Configuration file (default httpd.conf)" \
+     "\n       -p [IP:]PORT    Bind to ip:port (default *:80)" \
+     "\n       -i              Inetd mode" \
+     "\n       -f              Do not daemonize" \
+     "\n       -v[v]           Verbose" \
+       USE_FEATURE_HTTPD_SETUID( \
+     "\n       -u USER[:GRP]   Set uid/gid after binding to port") \
+       USE_FEATURE_HTTPD_BASIC_AUTH( \
+     "\n       -r REALM        Authentication Realm for Basic Authentication") \
+       USE_FEATURE_HTTPD_AUTH_MD5( \
+     "\n       -m PASS         Crypt PASS with md5 algorithm") \
+     "\n       -h HOME         Home directory (default .)" \
+     "\n       -e STRING       HTML encode STRING" \
+     "\n       -d STRING       URL decode STRING" \
+
+#define hwclock_trivial_usage \
+       USE_FEATURE_HWCLOCK_LONG_OPTIONS( \
+       "[-r|--show] [-s|--hctosys] [-w|--systohc]" \
+       " [-l|--localtime] [-u|--utc]" \
+       " [-f FILE]" \
+       ) \
+       SKIP_FEATURE_HWCLOCK_LONG_OPTIONS( \
+       "[-r] [-s] [-w] [-l] [-u] [-f FILE]" \
+       )
+#define hwclock_full_usage "\n\n" \
+       "Query and set hardware clock (RTC)\n" \
+     "\nOptions:" \
+     "\n       -r      Show hardware clock time" \
+     "\n       -s      Set system time from hardware clock" \
+     "\n       -w      Set hardware clock to system time" \
+     "\n       -u      Hardware clock is in UTC" \
+     "\n       -l      Hardware clock is in local time" \
+     "\n       -f FILE Use specified device (e.g. /dev/rtc2)" \
+
+#define id_trivial_usage \
+       "[OPTIONS]... [USER]"
+#define id_full_usage "\n\n" \
+       "Print information about USER or the current user\n" \
+     "\nOptions:" \
+       USE_SELINUX( \
+     "\n       -Z      Print the security context" \
+       ) \
+     "\n       -u      Print user ID" \
+     "\n       -g      Print group ID" \
+     "\n       -G      Print supplementary group IDs" \
+     "\n       -n      Print name instead of a number" \
+     "\n       -r      Print real user ID instead of effective ID" \
+
+#define id_example_usage \
+       "$ id\n" \
+       "uid=1000(andersen) gid=1000(andersen)\n"
+
+#define ifconfig_trivial_usage \
+       USE_FEATURE_IFCONFIG_STATUS("[-a]") " interface [address]"
+#define ifconfig_full_usage "\n\n" \
+       "Configure a network interface\n" \
+     "\nOptions:" \
+     "\n" \
+       USE_FEATURE_IPV6( \
+       "       [add ADDRESS[/PREFIXLEN]]\n") \
+       USE_FEATURE_IPV6( \
+       "       [del ADDRESS[/PREFIXLEN]]\n") \
+       "       [[-]broadcast [ADDRESS]] [[-]pointopoint [ADDRESS]]\n" \
+       "       [netmask ADDRESS] [dstaddr ADDRESS]\n" \
+       USE_FEATURE_IFCONFIG_SLIP( \
+       "       [outfill NN] [keepalive NN]\n") \
+       "       " USE_FEATURE_IFCONFIG_HW("[hw ether" USE_FEATURE_HWIB("|infiniband")" ADDRESS] ") "[metric NN] [mtu NN]\n" \
+       "       [[-]trailers] [[-]arp] [[-]allmulti]\n" \
+       "       [multicast] [[-]promisc] [txqueuelen NN] [[-]dynamic]\n" \
+       USE_FEATURE_IFCONFIG_MEMSTART_IOADDR_IRQ( \
+       "       [mem_start NN] [io_addr NN] [irq NN]\n") \
+       "       [up|down] ..."
+
+#define ifenslave_trivial_usage \
+       "[-cdf] master-iface <slave-iface...>"
+#define ifenslave_full_usage "\n\n" \
+       "Configure network interfaces for parallel routing\n" \
+     "\nOptions:" \
+     "\n       -c, --change-active     Change active slave" \
+     "\n       -d, --detach            Remove slave interface from bonding device" \
+     "\n       -f, --force             Force, even if interface is not Ethernet" \
+/*   "\n       -r, --receive-slave     Create a receive-only slave" */
+
+#define ifenslave_example_usage \
+       "To create a bond device, simply follow these three steps :\n" \
+       "- ensure that the required drivers are properly loaded :\n" \
+       "  # modprobe bonding ; modprobe <3c59x|eepro100|pcnet32|tulip|...>\n" \
+       "- assign an IP address to the bond device :\n" \
+       "  # ifconfig bond0 <addr> netmask <mask> broadcast <bcast>\n" \
+       "- attach all the interfaces you need to the bond device :\n" \
+       "  # ifenslave bond0 eth0 eth1 eth2\n" \
+       "  If bond0 didn't have a MAC address, it will take eth0's. Then, all\n" \
+       "  interfaces attached AFTER this assignment will get the same MAC addr.\n\n" \
+       "  To detach a dead interface without setting the bond device down :\n" \
+       "   # ifenslave -d bond0 eth1\n\n" \
+       "  To set the bond device down and automatically release all the slaves :\n" \
+       "   # ifconfig bond0 down\n\n" \
+       "  To change active slave :\n" \
+       "   # ifenslave -c bond0 eth0\n" \
+
+#define ifup_trivial_usage \
+       "[-ain"USE_FEATURE_IFUPDOWN_MAPPING("m")"vf] ifaces..."
+#define ifup_full_usage "\n\n" \
+       "Options:" \
+     "\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" \
+       USE_FEATURE_IFUPDOWN_MAPPING( \
+     "\n               (note: doesn't disable mappings)" \
+     "\n       -m      Don't run any mappings" \
+       ) \
+     "\n       -v      Print out what would happen before doing it" \
+     "\n       -f      Force de/configuration" \
+
+#define ifdown_trivial_usage \
+       "[-ain"USE_FEATURE_IFUPDOWN_MAPPING("m")"vf] ifaces..."
+#define ifdown_full_usage "\n\n" \
+       "Options:" \
+     "\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" \
+       USE_FEATURE_IFUPDOWN_MAPPING( \
+     "\n               (note: doesn't disable mappings)" \
+     "\n       -m      Don't run any mappings" \
+       ) \
+     "\n       -v      Print out what would happen before doing it" \
+     "\n       -f      Force de/configuration" \
+
+#define inetd_trivial_usage \
+       "[-fe] [-q N] [-R N] [CONFFILE]"
+#define inetd_full_usage "\n\n" \
+       "Listen for network connections and launch programs\n" \
+     "\nOptions:" \
+     "\n       -f      Run in foreground" \
+     "\n       -e      Log to stderr" \
+     "\n       -q N    Socket listen queue (default: 128)" \
+     "\n       -R N    Pause services after N connects/min" \
+     "\n               (default: 0 - disabled)" \
+
+#define init_trivial_usage \
+       ""
+#define init_full_usage "\n\n" \
+       "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" \
+"      <id>:<runlevels>:<action>:<process>\n" \
+"\n" \
+"      <id>:\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" \
+"      <runlevels>:\n" \
+"\n" \
+"              The runlevels field is completely ignored.\n" \
+"\n" \
+"      <action>:\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" \
+"      <process>:\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 inotifyd_trivial_usage \
+       "PROG FILE1[:MASK] ..."
+#define inotifyd_full_usage "\n\n" \
+       "Run PROG on filesystem changes." \
+     "\nWhen a filesystem event matching MASK occurs on FILEn," \
+     "\nPROG <actual_event(s)> <FILEn> [<subfile_name>] is run." \
+     "\nEvents:" \
+     "\n       a       File is accessed" \
+     "\n       c       File is modified" \
+     "\n       e       Metadata changed" \
+     "\n       w       Writtable file is closed" \
+     "\n       0       Unwrittable file is closed" \
+     "\n       r       File is opened" \
+     "\n       D       File is deleted" \
+     "\n       M       File is moved" \
+     "\n       u       Backing fs is unmounted" \
+     "\n       o       Event queue overflowed" \
+     "\n       x       File can't be watched anymore" \
+     "\nIf watching a directory:" \
+     "\n       m       Subfile is moved into dir" \
+     "\n       y       Subfile is moved out of dir" \
+     "\n       n       Subfile is created" \
+     "\n       d       Subfile is deleted" \
+     "\n" \
+     "\ninotifyd waits for PROG to exit." \
+     "\nWhen x event happens for all FILEs, inotifyd exits" \
+
+/* 2.6 style insmod has no options and required filename
+ * (not module name - .ko can't be omitted) */
+#define insmod_trivial_usage \
+       USE_FEATURE_2_4_MODULES("[OPTION]... MODULE ") \
+       SKIP_FEATURE_2_4_MODULES("FILE ") \
+       "[symbol=value]..."
+#define insmod_full_usage "\n\n" \
+       "Load the specified kernel modules into the kernel" \
+       USE_FEATURE_2_4_MODULES( "\n" \
+     "\nOptions:" \
+     "\n       -f      Force module to load into the wrong kernel version" \
+     "\n       -k      Make module autoclean-able" \
+     "\n       -v      Verbose" \
+     "\n       -q      Quiet" \
+     "\n       -L      Lock to prevent simultaneous loads of a module" \
+       USE_FEATURE_INSMOD_LOAD_MAP( \
+     "\n       -m      Output load map to stdout" \
+       ) \
+     "\n       -o NAME Set internal module name to NAME" \
+     "\n       -x      Do not export externs" \
+       )
+
+/* -v, -b, -c are ignored */
+#define install_trivial_usage \
+       "[-cdDsp] [-o USER] [-g GRP] [-m MODE] [source] dest|directory"
+#define install_full_usage "\n\n" \
+       "Copy files and set attributes\n" \
+     "\nOptions:" \
+     "\n       -c      Just copy (default)" \
+     "\n       -d      Create directories" \
+     "\n       -D      Create leading target directories" \
+     "\n       -s      Strip symbol table" \
+     "\n       -p      Preserve date" \
+     "\n       -o USER Set ownership" \
+     "\n       -g GRP  Set group ownership" \
+     "\n       -m MODE Set permissions" \
+       USE_SELINUX( \
+     "\n       -Z      Set security context" \
+       )
+
+#define ionice_trivial_usage \
+       "[-c 1-3] [-n 0-7] [-p PID] [PROG]"
+#define ionice_full_usage "\n\n" \
+       "Change I/O scheduling class and priority\n" \
+     "\nOptions:" \
+     "\n       -c      Class. 1:realtime 2:best-effort 3:idle" \
+     "\n       -n      Priority" \
+
+/* would need to make the " | " optional depending on more than one selected: */
+#define ip_trivial_usage \
+       "[OPTIONS] {" \
+       USE_FEATURE_IP_ADDRESS("address | ") \
+       USE_FEATURE_IP_ROUTE("route | ") \
+       USE_FEATURE_IP_LINK("link | ") \
+       USE_FEATURE_IP_TUNNEL("tunnel | ") \
+       USE_FEATURE_IP_RULE("rule") \
+       "} {COMMAND}"
+#define ip_full_usage "\n\n" \
+       "ip [OPTIONS] OBJECT {COMMAND}\n" \
+       "where OBJECT := {" \
+       USE_FEATURE_IP_ADDRESS("address | ") \
+       USE_FEATURE_IP_ROUTE("route | ") \
+       USE_FEATURE_IP_LINK("link | ") \
+       USE_FEATURE_IP_TUNNEL("tunnel | ") \
+       USE_FEATURE_IP_RULE("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 "\n\n" \
+       "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]... ADDRESS[[/]NETMASK] [NETMASK]"
+#define ipcalc_full_usage "\n\n" \
+       "Calculate IP network settings from a IP address\n" \
+     "\nOptions:" \
+       USE_FEATURE_IPCALC_LONG_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" \
+       ) \
+       ) \
+       SKIP_FEATURE_IPCALC_LONG_OPTIONS( \
+     "\n       -b      Display calculated broadcast address" \
+     "\n       -n      Display calculated network address" \
+     "\n       -m      Display default netmask for IP" \
+       USE_FEATURE_IPCALC_FANCY( \
+     "\n       -p      Display the prefix for IP/NETMASK" \
+     "\n       -h      Display first resolved host name" \
+     "\n       -s      Don't ever display error messages" \
+       ) \
+       )
+
+#define ipcrm_trivial_usage \
+       "[-MQS key] [-mqs id]"
+#define ipcrm_full_usage "\n\n" \
+       "Upper-case options MQS remove an object by shmkey value.\n" \
+       "Lower-case options remove an object by shmid value.\n" \
+     "\nOptions:" \
+     "\n       -mM     Remove memory segment after last detach" \
+     "\n       -qQ     Remove message queue" \
+     "\n       -sS     Remove semaphore" \
+
+#define ipcs_trivial_usage \
+       "[[-smq] -i shmid] | [[-asmq] [-tcplu]]"
+#define ipcs_full_usage "\n\n" \
+       "       -i      Show specific resource" \
+     "\nResource specification:" \
+     "\n       -m      Shared memory segments" \
+     "\n       -q      Message queues" \
+     "\n       -s      Semaphore arrays" \
+     "\n       -a      All (default)" \
+     "\nOutput 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 "\n\n" \
+       "iplink set DEVICE { up | down | arp | multicast { 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 "\n\n" \
+       "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]\n" \
+       "                               [metric METRIC]" \
+
+#define iprule_trivial_usage \
+       "{[list | add | del] RULE}"
+#define iprule_full_usage "\n\n" \
+       "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 "\n\n" \
+       "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 kbd_mode_trivial_usage \
+       "[-a|k|s|u] [-C TTY]"
+#define kbd_mode_full_usage "\n\n" \
+       "Report or set the keyboard mode\n" \
+     "\nOptions set mode:" \
+     "\n       -a      Default (ASCII)" \
+     "\n       -k      Medium-raw (keyboard)" \
+     "\n       -s      Raw (scancode)" \
+     "\n       -u      Unicode (utf-8)" \
+     "\n       -C TTY  Affect TTY instead of /dev/tty" \
+
+#define kill_trivial_usage \
+       "[-l] [-SIG] PID..."
+#define kill_full_usage "\n\n" \
+       "Send a signal (default is TERM) to given PIDs\n" \
+     "\nOptions:" \
+     "\n       -l      List all signal names and numbers" \
+/*   "\n       -s SIG  Yet another way of specifying SIG" */ \
+
+#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] [-SIG] process-name..."
+#define killall_full_usage "\n\n" \
+       "Send a signal (default is TERM) to given processes\n" \
+     "\nOptions:" \
+     "\n       -l      List all signal names and numbers" \
+/*   "\n       -s SIG  Yet another way of specifying SIG" */ \
+     "\n       -q      Do not complain if no processes were killed" \
+
+#define killall_example_usage \
+       "$ killall apache\n"
+
+#define killall5_trivial_usage \
+       "[-l] [-SIG] [-o PID]..."
+#define killall5_full_usage "\n\n" \
+       "Send a signal (default is TERM) to all processes outside current session\n" \
+     "\nOptions:" \
+     "\n       -l      List all signal names and numbers" \
+     "\n       -o PID  Do not signal this PID" \
+/*   "\n       -s SIG  Yet another way of specifying SIG" */ \
+
+#define klogd_trivial_usage \
+       "[-c N] [-n]"
+#define klogd_full_usage "\n\n" \
+       "Kernel logger\n" \
+     "\nOptions:" \
+     "\n       -c N    Only messages with level < N are printed to console" \
+     "\n       -n      Run in foreground" \
+
+#define length_trivial_usage \
+       "STRING"
+#define length_full_usage "\n\n" \
+       "Print STRING's length"
+
+#define length_example_usage \
+       "$ length Hello\n" \
+       "5\n"
+
+#define less_trivial_usage \
+       "[-EMNmh~I?] [FILE...]"
+#define less_full_usage "\n\n" \
+       "View a file or list of files. The position within files can be\n" \
+       "changed, and files can be manipulated in various ways.\n" \
+     "\nOptions:" \
+     "\n       -E      Quit once the end of a file is reached" \
+     "\n       -M,-m   Display a status line containing the line numbers" \
+     "\n               and percentage through the file" \
+     "\n       -N      Prefix line numbers to each line" \
+     "\n       -I      Ignore case in all searches" \
+     "\n       -~      Suppress ~s displayed past the end of the file" \
+
+#define linux32_trivial_usage NOUSAGE_STR
+#define linux32_full_usage ""
+#define linux64_trivial_usage NOUSAGE_STR
+#define linux64_full_usage ""
+
+#define linuxrc_trivial_usage NOUSAGE_STR
+#define linuxrc_full_usage ""
+
+#define setarch_trivial_usage \
+       "personality program [args...]"
+#define setarch_full_usage "\n\n" \
+       "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 "\n\n" \
+       "Create a link named LINK_NAME or DIRECTORY to the specified TARGET.\n" \
+       "Use '--' to indicate that all following arguments are non-options.\n" \
+     "\nOptions:" \
+     "\n       -s      Make symlinks instead of hardlinks" \
+     "\n       -f      Remove existing destination files" \
+     "\n       -n      Don't dereference symlinks - treat like normal file" \
+     "\n       -b      Make a backup of the target (if exists) before link operation" \
+     "\n       -S suf  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 load_policy_trivial_usage NOUSAGE_STR
+#define load_policy_full_usage ""
+
+#define loadfont_trivial_usage \
+       "< font"
+#define loadfont_full_usage "\n\n" \
+       "Load a console font from standard input" \
+/*   "\n       -C TTY  Affect TTY instead of /dev/tty" */ \
+
+#define loadfont_example_usage \
+       "$ loadfont < /etc/i18n/fontname\n"
+
+#define loadkmap_trivial_usage \
+       "< keymap"
+#define loadkmap_full_usage "\n\n" \
+       "Load a binary keyboard translation table from standard input\n" \
+/*   "\n       -C TTY  Affect TTY instead of /dev/tty" */ \
+
+#define loadkmap_example_usage \
+       "$ loadkmap < /etc/i18n/lang-keymap\n"
+
+#define logger_trivial_usage \
+       "[OPTION]... [MESSAGE]"
+#define logger_full_usage "\n\n" \
+       "Write MESSAGE to the system log. If MESSAGE is omitted, log stdin.\n" \
+     "\nOptions:" \
+     "\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 PRIO Priority (numeric or facility.level pair)" \
+
+#define logger_example_usage \
+       "$ logger \"hello\"\n"
+
+#define login_trivial_usage \
+       "[-p] [-h HOST] [[-f] USER]"
+#define login_full_usage "\n\n" \
+       "Begin a new session on the system\n" \
+     "\nOptions:" \
+     "\n       -f      Do not authenticate (user already authenticated)" \
+     "\n       -h      Name of the remote host" \
+     "\n       -p      Preserve environment" \
+
+#define logname_trivial_usage \
+       ""
+#define logname_full_usage "\n\n" \
+       "Print the name of the current user"
+#define logname_example_usage \
+       "$ logname\n" \
+       "root\n"
+
+#define logread_trivial_usage \
+       "[OPTION]..."
+#define logread_full_usage "\n\n" \
+       "Show messages in syslogd's circular buffer\n" \
+     "\nOptions:" \
+     "\n       -f      Output data as log grows" \
+
+#define losetup_trivial_usage \
+       "[-o OFS] LOOPDEV FILE - associate loop devices\n" \
+       "       losetup -d LOOPDEV - disassociate\n" \
+       "       losetup [-f] - show"
+#define losetup_full_usage "\n\n" \
+       "Options:" \
+     "\n       -o OFS  Start OFS bytes into FILE" \
+     "\n       -f      Show first free loop device" \
+
+#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" \
+       "losetup -f will show the first loop free loop device\n\n"
+
+#define lpd_trivial_usage \
+       "SPOOLDIR [HELPER [ARGS...]]"
+#define lpd_full_usage "\n\n" \
+       "SPOOLDIR must contain (symlinks to) device nodes or directories" \
+     "\nwith names matching print queue names. In the first case, jobs are" \
+     "\nsent directly to the device. Otherwise each job is stored in queue" \
+     "\ndirectory and HELPER program is called. Name of file to print" \
+     "\nis passed in $DATAFILE variable." \
+     "\nExample:" \
+     "\n       tcpsvd -E 0 515 softlimit -m 999999 lpd /var/spool ./print" \
+
+#define lpq_trivial_usage \
+       "[-P queue[@host[:port]]] [-U USERNAME] [-d JOBID...] [-fs]"
+#define lpq_full_usage "\n\n" \
+       "Options:" \
+     "\n       -P      lp service to connect to (else uses $PRINTER)" \
+     "\n       -d      Delete jobs" \
+     "\n       -f      Force any waiting job to be printed" \
+     "\n       -s      Short display" \
+
+#define lpr_trivial_usage \
+       "-P queue[@host[:port]] -U USERNAME -J TITLE -Vmh [FILE...]"
+/* -C CLASS exists too, not shown.
+ * CLASS is supposed to be printed on banner page, if one is requested */
+#define lpr_full_usage "\n\n" \
+       "Options:" \
+     "\n       -P      lp service to connect to (else uses $PRINTER)"\
+     "\n       -m      Send mail on completion" \
+     "\n       -h      Print banner page too" \
+     "\n       -V      Verbose" \
+
+#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 "\n\n" \
+       "List directory contents\n" \
+     "\nOptions:" \
+     "\n       -1      List in a single column" \
+     "\n       -A      Don't list . and .." \
+     "\n       -a      Don't hide entries starting with ." \
+     "\n       -C      List by columns" \
+       USE_FEATURE_LS_TIMESTAMPS( \
+     "\n       -c      With -l: sort by ctime") \
+       USE_FEATURE_LS_COLOR( \
+     "\n       --color[={always,never,auto}]   Control coloring") \
+     "\n       -d      List directory entries instead of contents" \
+       USE_FEATURE_LS_TIMESTAMPS( \
+     "\n       -e      List full date and time") \
+       USE_FEATURE_LS_FILETYPES( \
+     "\n       -F      Append indicator (one of */=@|) to entries") \
+     "\n       -i      List inode numbers" \
+     "\n       -l      Long listing format" \
+     "\n       -n      List numeric UIDs and GIDs instead of names" \
+       USE_FEATURE_LS_FILETYPES( \
+     "\n       -p      Append indicator (one of /=@|) to entries") \
+       USE_FEATURE_LS_FOLLOWLINKS( \
+     "\n       -L      List entries pointed to by symlinks") \
+       USE_FEATURE_LS_RECURSIVE( \
+     "\n       -R      List subdirectories recursively") \
+       USE_FEATURE_LS_SORTFILES( \
+     "\n       -r      Sort in reverse order") \
+       USE_FEATURE_LS_SORTFILES( \
+     "\n       -S      Sort by file size") \
+     "\n       -s      List the size of each file, in blocks" \
+       USE_FEATURE_AUTOWIDTH( \
+     "\n       -T NUM  Assume tabstop every NUM columns") \
+       USE_FEATURE_LS_TIMESTAMPS( \
+     "\n       -t      With -l: sort by modification time") \
+       USE_FEATURE_LS_TIMESTAMPS( \
+     "\n       -u      With -l: sort by access time") \
+       USE_FEATURE_LS_SORTFILES( \
+     "\n       -v      Sort by version") \
+       USE_FEATURE_AUTOWIDTH( \
+     "\n       -w NUM  Assume the terminal is NUM columns wide") \
+     "\n       -x      List by lines" \
+       USE_FEATURE_LS_SORTFILES( \
+     "\n       -X      Sort by extension") \
+       USE_FEATURE_HUMAN_READABLE( \
+     "\n       -h      List sizes in human readable format (1K 243M 2G)") \
+       USE_SELINUX( \
+     "\n       -k      List security context") \
+       USE_SELINUX( \
+     "\n       -K      List security context in long format") \
+       USE_SELINUX( \
+     "\n       -Z      List security context and permission") \
+
+#define lsattr_trivial_usage \
+       "[-Radlv] [files...]"
+#define lsattr_full_usage "\n\n" \
+       "List file attributes on an ext2 fs\n" \
+     "\nOptions:" \
+     "\n       -R      Recursively list subdirectories" \
+     "\n       -a      Do not hide entries starting with ." \
+     "\n       -d      List directory entries instead of contents" \
+     "\n       -l      List long flag names" \
+     "\n       -v      List the file's version/generation number" \
+
+#define lsmod_trivial_usage \
+       ""
+#define lsmod_full_usage "\n\n" \
+       "List the currently loaded kernel modules"
+
+#if ENABLE_FEATURE_MAKEDEVS_LEAF
+#define makedevs_trivial_usage \
+       "NAME TYPE MAJOR MINOR FIRST LAST [s]"
+#define makedevs_full_usage "\n\n" \
+       "Create a range of block or character special files" \
+     "\n" \
+     "\nTYPE is:" \
+     "\n       b       Block device" \
+     "\n       c       Character device" \
+     "\n       f       FIFO, MAJOR and MINOR are ignored" \
+     "\n" \
+     "\nFIRST..LAST specify numbers appended to NAME." \
+     "\nIf 's' is the last argument, the base device is created as well." \
+     "\n" \
+     "\nExamples:" \
+     "\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
+
+#if ENABLE_FEATURE_MAKEDEVS_TABLE
+#define makedevs_trivial_usage \
+       "[-d device_table] rootdir"
+#define makedevs_full_usage "\n\n" \
+       "Create a range of special files as specified in a device table.\n" \
+       "Device table entries take the form of:\n" \
+       "<type> <mode> <uid> <gid> <major> <minor> <start> <inc> <count>\n" \
+       "Where name is the file name, type can be one of:\n" \
+       "       f       Regular file\n" \
+       "       d       Directory\n" \
+       "       c       Character device\n" \
+       "       b       Block device\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" \
+       "<name>    <type> <mode><uid><gid><major><minor><start><inc><count>\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 makemime_trivial_usage \
+       "[OPTION]... [FILE]..."
+#define makemime_full_usage "\n\n" \
+       "Create MIME-encoded message\n" \
+     "\nOptions:" \
+     "\n       -C      Charset" \
+     "\n       -e      Transfer encoding. Ignored. base64 is assumed" \
+     "\n" \
+     "\nOther options are silently ignored." \
+
+#define man_trivial_usage \
+       "[OPTION]... [MANPAGE]..."
+#define man_full_usage "\n\n" \
+       "Format and display manual page\n" \
+     "\nOptions:" \
+     "\n       -a      Display all pages" \
+     "\n       -w      Show page locations" \
+
+#define matchpathcon_trivial_usage \
+       "[-n] [-N] [-f file_contexts_file] [-p prefix] [-V]"
+#define matchpathcon_full_usage "\n\n" \
+       "       -n      Do not display path" \
+     "\n       -N      Do not use translations" \
+     "\n       -f      Use alternate file_context file" \
+     "\n       -p      Use prefix to speed translations" \
+     "\n       -V      Verify file context on disk matches defaults" \
+
+#define md5sum_trivial_usage \
+       "[OPTION] [FILEs...]" \
+       USE_FEATURE_MD5_SHA1_SUM_CHECK("\n   or: md5sum [OPTION] -c [FILE]")
+#define md5sum_full_usage "\n\n" \
+       "Print" USE_FEATURE_MD5_SHA1_SUM_CHECK(" or check") " MD5 checksums" \
+       USE_FEATURE_MD5_SHA1_SUM_CHECK( "\n" \
+     "\nOptions:" \
+     "\n       -c      Check sums against given list" \
+     "\n       -s      Don't output anything, status code shows success" \
+     "\n       -w      Warn about improperly formatted 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 sha1sum_trivial_usage \
+       "[OPTION] [FILEs...]" \
+       USE_FEATURE_MD5_SHA1_SUM_CHECK("\n   or: sha1sum [OPTION] -c [FILE]")
+#define sha1sum_full_usage "\n\n" \
+       "Print" USE_FEATURE_MD5_SHA1_SUM_CHECK(" or check") " SHA1 checksums." \
+       USE_FEATURE_MD5_SHA1_SUM_CHECK( "\n" \
+     "\nOptions:" \
+     "\n       -c      Check sums against given list" \
+     "\n       -s      Don't output anything, status code shows success" \
+     "\n       -w      Warn about improperly formatted checksum lines" \
+       )
+
+#define sha256sum_trivial_usage \
+       "[OPTION] [FILEs...]" \
+       USE_FEATURE_MD5_SHA1_SUM_CHECK("\n   or: sha256sum [OPTION] -c [FILE]")
+#define sha256sum_full_usage "\n\n" \
+       "Print" USE_FEATURE_MD5_SHA1_SUM_CHECK(" or check") " SHA1 checksums." \
+       USE_FEATURE_MD5_SHA1_SUM_CHECK( "\n" \
+     "\nOptions:" \
+     "\n       -c      Check sums against given list" \
+     "\n       -s      Don't output anything, status code shows success" \
+     "\n       -w      Warn about improperly formatted checksum lines" \
+       )
+
+#define sha512sum_trivial_usage \
+       "[OPTION] [FILEs...]" \
+       USE_FEATURE_MD5_SHA1_SUM_CHECK("\n   or: sha512sum [OPTION] -c [FILE]")
+#define sha512sum_full_usage "\n\n" \
+       "Print" USE_FEATURE_MD5_SHA1_SUM_CHECK(" or check") " SHA1 checksums." \
+       USE_FEATURE_MD5_SHA1_SUM_CHECK( "\n" \
+     "\nOptions:" \
+     "\n       -c      Check sums against given list" \
+     "\n       -s      Don't output anything, status code shows success" \
+     "\n       -w      Warn about improperly formatted checksum lines" \
+       )
+
+#define mdev_trivial_usage \
+       "[-s]"
+#define mdev_full_usage "\n\n" \
+       "       -s      Scan /sys and populate /dev during system boot\n" \
+       "\n" \
+       "It can be run by kernel as a hotplug helper. To activate it:\n" \
+       " echo /bin/mdev >/proc/sys/kernel/hotplug\n" \
+       USE_FEATURE_MDEV_CONF( \
+       "It uses /etc/mdev.conf with lines\n" \
+       "[-]DEVNAME UID:GID PERM" \
+                       USE_FEATURE_MDEV_RENAME(" [>|=PATH]") \
+                       USE_FEATURE_MDEV_EXEC(" [@|$|*COMMAND]") \
+       ) \
+
+#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 "\n\n" \
+       "Control write access to your terminal\n" \
+       "       y       Allow write access to your terminal\n" \
+       "       n       Disallow write access to your terminal"
+
+#define microcom_trivial_usage \
+       "[-d DELAY] [-t TIMEOUT] [-s SPEED] [-X] TTY"
+#define microcom_full_usage "\n\n" \
+       "Copy bytes for stdin to TTY and from TTY to stdout\n" \
+     "\nOptions:" \
+     "\n       -d      Wait up to DELAY ms for TTY output before sending every" \
+     "\n               next byte to it" \
+     "\n       -t      Exit if both stdin and TTY are silent for TIMEOUT ms" \
+     "\n       -s      Set serial line to SPEED" \
+     "\n       -X      Disable special meaning of NUL and Ctrl-X from stdin" \
+
+#define mkdir_trivial_usage \
+       "[OPTION] DIRECTORY..."
+#define mkdir_full_usage "\n\n" \
+       "Create DIRECTORY\n" \
+     "\nOptions:" \
+     "\n       -m      Set permission mode (as in chmod), not rwxrwxrwx - umask" \
+     "\n       -p      No error if existing, make parent directories as needed" \
+       USE_SELINUX( \
+     "\n       -Z      Set security context" \
+       )
+
+#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 "\n\n" \
+       "       -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" \
+     "\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" \
+
+#define mkfifo_trivial_usage \
+       "[OPTIONS] name"
+#define mkfifo_full_usage "\n\n" \
+       "Create named pipe (identical to 'mknod name p')\n" \
+     "\nOptions:" \
+     "\n       -m MODE Mode (default a=rw)" \
+       USE_SELINUX( \
+     "\n       -Z      Set security context" \
+       )
+
+#define mkfs_minix_trivial_usage \
+       "[-c | -l filename] [-nXX] [-iXX] /dev/name [blocks]"
+#define mkfs_minix_full_usage "\n\n" \
+       "Make a MINIX filesystem\n" \
+     "\nOptions:" \
+     "\n       -c              Check device for bad blocks" \
+     "\n       -n [14|30]      Maximum length of filenames" \
+     "\n       -i INODES       Number of inodes for the filesystem" \
+     "\n       -l FILENAME     Read bad blocks list from FILENAME" \
+     "\n       -v              Make version 2 filesystem" \
+
+#define mkfs_vfat_trivial_usage \
+       "[-v] [-n LABEL] FILE_OR_DEVICE [SIZE_IN_KB]"
+/* Accepted but ignored:
+       "[-c] [-C] [-I] [-l bad-block-file] [-b backup-boot-sector] "
+       "[-m boot-msg-file] [-i volume-id] "
+       "[-s sectors-per-cluster] [-S logical-sector-size] [-f number-of-FATs] "
+       "[-h hidden-sectors] [-F fat-size] [-r root-dir-entries] [-R reserved-sectors] "
+*/
+#define mkfs_vfat_full_usage "\n\n" \
+       "Make a FAT32 filesystem\n" \
+     "\nOptions:" \
+/*   "\n       -c      Check device for bad blocks" */ \
+     "\n       -v      Verbose" \
+/*   "\n       -I      Allow to use entire disk device (e.g. /dev/hda)" */ \
+     "\n       -n LBL  Volume label" \
+
+#define mknod_trivial_usage \
+       "[OPTIONS] NAME TYPE MAJOR MINOR"
+#define mknod_full_usage "\n\n" \
+       "Create a special file (block, character, or pipe)\n" \
+     "\nOptions:" \
+     "\n       -m      Create the special file using the specified mode (default a=rw)" \
+     "\nTYPEs include:" \
+     "\n       b:      Make a block device" \
+     "\n       c or u: Make a character device" \
+     "\n       p:      Make a named pipe (MAJOR and MINOR are ignored)" \
+       USE_SELINUX( \
+     "\n       -Z      Set security context" \
+       )
+
+#define mknod_example_usage \
+       "$ mknod /dev/fd0 b 2 0\n" \
+       "$ mknod -m 644 /tmp/pipe p\n"
+
+#define mkswap_trivial_usage \
+       "DEVICE"
+#define mkswap_full_usage "\n\n" \
+       "Prepare block device to be used as swap partition"
+#if 0
+       "[-c] [-v0|-v1] DEVICE [BLOCKS]"
+     "\nOptions:"
+     "\n       -c      Check for readability"
+     "\n       -v0     Make swap version 0 (max 128M)"
+     "\n       -v1     Make swap version 1 (default for kernels > 2.1.117)"
+     "\n       BLOCKS  Number of blocks to use (default is entire partition)"
+#endif
+
+#define mktemp_trivial_usage \
+       "[-dt] [-p DIR] [TEMPLATE]"
+#define mktemp_full_usage "\n\n" \
+       "Create a temporary file with name based on TEMPLATE and print its name.\n" \
+       "TEMPLATE must end with XXXXXX (e.g. [/dir/]nameXXXXXX).\n" \
+     "\nOptions:" \
+     "\n       -d      Make a directory instead of a file" \
+/*   "\n       -q      Fail silently if an error occurs" - we ignore it */ \
+     "\n       -t      Generate a path rooted in temporary directory" \
+     "\n       -p DIR  Use DIR as a temporary directory (implies -t)" \
+     "\n" \
+     "\nFor -t or -p, directory is chosen as follows:" \
+     "\n$TMPDIR if set, else -p DIR, else /tmp" \
+
+#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 "\n\n" \
+       "Options:" \
+       USE_FEATURE_2_4_MODULES( \
+     "\n       -k      Make module autoclean-able" \
+       ) \
+     "\n       -n      Dry run" \
+     "\n       -q      Quiet" \
+     "\n       -r      Remove module (stacks) or do autoclean" \
+     "\n       -s      Report via syslog instead of stderr" \
+     "\n       -v      Verbose" \
+       USE_FEATURE_MODPROBE_BLACKLIST( \
+     "\n       -b      Apply blacklist to module names too" \
+        )
+
+#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 this order):\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 <alias_name> <mod_name>\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 <mod_name|alias_name> <symbol=value...>\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 "\n\n" \
+       "View FILE or standard input one screenful at a time"
+
+#define more_example_usage \
+       "$ dmesg | more\n"
+
+#define mount_trivial_usage \
+       "[flags] DEVICE NODE [-o OPT,OPT]"
+#define mount_full_usage "\n\n" \
+       "Mount a filesystem. Filesystem autodetection requires /proc be mounted.\n" \
+     "\nOptions:" \
+     "\n       -a              Mount all filesystems in fstab" \
+       USE_FEATURE_MOUNT_FAKE( \
+       USE_FEATURE_MTAB_SUPPORT( \
+     "\n       -f              Update /etc/mtab, but don't mount" \
+       ) \
+       SKIP_FEATURE_MTAB_SUPPORT( \
+     "\n       -f              Dry run" \
+       ) \
+       ) \
+       USE_FEATURE_MTAB_SUPPORT( \
+     "\n       -n              Don't update /etc/mtab" \
+       ) \
+     "\n       -r              Read-only mount" \
+     "\n       -w              Read-write mount (default)" \
+     "\n       -t FSTYPE       Filesystem type" \
+     "\n       -O OPT          Mount only filesystems with option OPT (-a only)" \
+     "\n-o OPT:" \
+       USE_FEATURE_MOUNT_LOOP( \
+     "\n       loop            Ignored (loop devices are autodetected)" \
+       ) \
+       USE_FEATURE_MOUNT_FLAGS( \
+     "\n       [a]sync         Writes are [a]synchronous" \
+     "\n       [no]atime       Disable/enable updates to inode access times" \
+     "\n       [no]diratime    Disable/enable atime updates to directories" \
+     "\n       [no]relatime    Disable/enable atime updates relative to modification time" \
+     "\n       [no]dev         (Dis)allow use of special device files" \
+     "\n       [no]exec        (Dis)allow use of executable files" \
+     "\n       [no]suid        (Dis)allow set-user-id-root programs" \
+     "\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         Remount a mounted filesystem, changing its flags" \
+     "\n       ro/rw           Read-only/read-write mount" \
+     "\n" \
+     "\nThere are EVEN MORE flags that are specific to each filesystem" \
+     "\nYou'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] <[-dn] DIR | -x DEVICE>"
+#define mountpoint_full_usage "\n\n" \
+       "Check if the directory is a mountpoint\n" \
+     "\nOptions:" \
+     "\n       -q      Quiet" \
+     "\n       -d      Print major/minor device number of the filesystem" \
+     "\n       -n      Print device name 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 "\n\n" \
+       "Control magnetic tape drive operation\n" \
+       "\n" \
+       "Available 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 "\n\n" \
+       "Rename SOURCE to DEST, or move SOURCE(s) to DIRECTORY\n" \
+     "\nOptions:" \
+     "\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 "\n\n" \
+       "Rename network interface while it in the down state\n" \
+     "\nOptions:" \
+     "\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_DESKTOP
+
+#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 "\n\n" \
+       "Open 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" \
+       USE_NC_EXTRA( \
+     "\n               (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"
+
+#else /* DESKTOP nc - much more compatible with nc 1.10 */
+
+#define nc_trivial_usage \
+       "[-options] hostname port  - connect" \
+       USE_NC_SERVER("\n" \
+       "nc [-options] -l -p port [hostname] [port]  - listen")
+#define nc_full_usage "\n\n" \
+       "Options:" \
+     "\n       -e prog [args]  Program to exec after connect (must be last)" \
+       USE_NC_SERVER( \
+     "\n       -l              Listen mode, for inbound connects" \
+       ) \
+     "\n       -n              Don't do DNS resolution" \
+     "\n       -s addr         Local address" \
+     "\n       -p port         Local port" \
+     "\n       -u              UDP mode" \
+     "\n       -v              Verbose (cumulative: -vv)" \
+     "\n       -w secs         Timeout for connects and final net reads" \
+       USE_NC_EXTRA( \
+     "\n       -i sec          Delay interval for lines sent" /* ", ports scanned" */ \
+     "\n       -o file         Hex dump of traffic" \
+     "\n       -z              Zero-I/O mode (scanning)" \
+       ) \
+/*   "\n       -r              Randomize local and remote ports" */
+/*   "\n       -g gateway      Source-routing hop point[s], up to 8" */
+/*   "\n       -G num          Source-routing pointer: 4, 8, 12, ..." */
+/*   "\nport numbers can be individual or ranges: lo-hi [inclusive]" */
+
+#endif
+
+#define netstat_trivial_usage \
+       "[-laentuwxr"USE_FEATURE_NETSTAT_WIDE("W")USE_FEATURE_NETSTAT_PRG("p")"]"
+#define netstat_full_usage "\n\n" \
+       "Display networking information\n" \
+     "\nOptions:" \
+     "\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       -t      Tcp sockets" \
+     "\n       -u      Udp sockets" \
+     "\n       -w      Raw sockets" \
+     "\n       -x      Unix sockets" \
+     "\n       -r      Display routing table" \
+       USE_FEATURE_NETSTAT_WIDE( \
+     "\n       -W      Display with no column truncation" \
+       ) \
+       USE_FEATURE_NETSTAT_PRG( \
+     "\n       -p      Display PID/Program name for sockets" \
+       )
+
+#define nice_trivial_usage \
+       "[-n ADJUST] [COMMAND [ARG]...]"
+#define nice_full_usage "\n\n" \
+       "Run a program with modified scheduling priority\n" \
+     "\nOptions:" \
+     "\n       -n ADJUST       Adjust the scheduling priority by ADJUST" \
+
+#define nmeter_trivial_usage \
+       "format_string"
+#define nmeter_full_usage "\n\n" \
+       "Monitor 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 <cr> instead of <lf> 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 "\n\n" \
+       "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 "\n\n" \
+       "Query 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] " USE_DESKTOP("[-t TYPE] ") "[FILE]"
+#define od_full_usage "\n\n" \
+       "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 \
+       "[-c NUM] [-sw] [COMMAND [ARGS]]"
+#define openvt_full_usage "\n\n" \
+       "Start COMMAND on a new virtual terminal\n" \
+     "\nOptions:" \
+     "\n       -c      Use specified VT" \
+     "\n       -s      Switch to the VT" \
+/*   "\n       -l      Run COMMAND as login shell (by prepending '-')" */ \
+     "\n       -w      Wait for COMMAND to exit" \
+
+#define openvt_example_usage \
+       "openvt 2 /bin/ash\n"
+
+#define parse_trivial_usage \
+       "[-n maxtokens] [-m mintokens] [-d delims] [-f flags] file ..."
+#define parse_full_usage "\n\n" \
+       "[-n maxtokens] [-m mintokens] [-d delims] [-f flags] file ..."
+
+#define passwd_trivial_usage \
+       "[OPTION] [name]"
+#define passwd_full_usage "\n\n" \
+       "Change user's password. If no name is specified,\n" \
+       "changes the password for the current user.\n" \
+     "\nOptions:" \
+     "\n       -a      Algorithm to use for password (choices: des, md5)" /* ", sha1)" */ \
+     "\n       -d      Delete password for the account" \
+     "\n       -l      Lock (disable) account" \
+     "\n       -u      Unlock (re-enable) account" \
+
+#define chpasswd_trivial_usage \
+       USE_GETOPT_LONG("[--md5|--encrypted]") SKIP_GETOPT_LONG("[-m|-e]")
+#define chpasswd_full_usage "\n\n" \
+       "Read user:password information from stdin " \
+       "and update /etc/passwd accordingly.\n" \
+     "\nOptions:" \
+       USE_GETOPT_LONG( \
+     "\n       -e,--encrypted  Supplied passwords are in encrypted form" \
+     "\n       -m,--md5        Use MD5 encryption instead of DES" \
+       ) \
+       SKIP_GETOPT_LONG( \
+     "\n       -e      Supplied passwords are in encrypted form" \
+     "\n       -m      Use MD5 encryption instead of DES" \
+       )
+
+#define patch_trivial_usage \
+       "[-p NUM] [-i DIFF] [-R]"
+#define patch_full_usage "\n\n" \
+       "       -p NUM  Strip NUM leading components from file names" \
+     "\n       -i DIFF Read DIFF instead of stdin" \
+     "\n       -R      Reverse patch" \
+
+#define patch_example_usage \
+       "$ patch -p1 < example.diff\n" \
+       "$ patch -p0 -i example.diff"
+
+#define pgrep_trivial_usage \
+       "[-flnovx] pattern"
+#define pgrep_full_usage "\n\n" \
+       "Display process(es) selected by regex pattern\n" \
+     "\nOptions:" \
+     "\n       -l      Show command name too" \
+     "\n       -f      Match against entire command line" \
+     "\n       -n      Show the newest process only" \
+     "\n       -o      Show the oldest process only" \
+     "\n       -v      Negate the matching" \
+     "\n       -x      Match whole name (not substring)" \
+
+#if (ENABLE_FEATURE_PIDOF_SINGLE || ENABLE_FEATURE_PIDOF_OMIT)
+#define pidof_trivial_usage \
+       "[OPTION] [NAME...]"
+#define USAGE_PIDOF "\n\nOptions:"
+#else
+#define pidof_trivial_usage \
+       "[NAME...]"
+#define USAGE_PIDOF /* none */
+#endif
+#define pidof_full_usage "\n\n" \
+       "List PIDs of all processes with names that match NAMEs" \
+       USAGE_PIDOF \
+       USE_FEATURE_PIDOF_SINGLE( \
+     "\n       -s      Show only one PID") \
+       USE_FEATURE_PIDOF_OMIT( \
+     "\n       -o PID  Omit given pid" \
+     "\n               Use %PPID to omit pid of pidof's parent") \
+
+#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")
+
+#if !ENABLE_FEATURE_FANCY_PING
+#define ping_trivial_usage \
+       "host"
+#define ping_full_usage "\n\n" \
+       "Send ICMP ECHO_REQUEST packets to network hosts"
+#define ping6_trivial_usage \
+       "host"
+#define ping6_full_usage "\n\n" \
+       "Send ICMP ECHO_REQUEST packets to network hosts"
+#else
+#define ping_trivial_usage \
+       "[OPTION]... host"
+#define ping_full_usage "\n\n" \
+       "Send ICMP ECHO_REQUEST packets to network hosts\n" \
+     "\nOptions:" \
+     "\n       -4, -6          Force IPv4 or IPv6 hostname resolution" \
+     "\n       -c CNT          Send only CNT pings" \
+     "\n       -s SIZE         Send SIZE data bytes in packets (default=56)" \
+     "\n       -I iface/IP     Use interface or IP address as source" \
+     "\n       -W timeout      Seconds to wait for the first response (default:10)" \
+     "\n                       (after all -c CNT packets are sent)" \
+     "\n       -w deadline     Seconds until ping exits (default:infinite)" \
+     "\n                       (can exit earlier with -c CNT)" \
+     "\n       -q              Quiet, only displays output at start" \
+     "\n                       and when finished" \
+
+#define ping6_trivial_usage \
+       "[OPTION]... host"
+#define ping6_full_usage "\n\n" \
+       "Send ICMP ECHO_REQUEST packets to network hosts\n" \
+     "\nOptions:" \
+     "\n       -c CNT          Send only CNT pings" \
+     "\n       -s SIZE         Send SIZE data bytes in packets (default=56)" \
+     "\n       -I iface/IP     Use interface or IP address as source" \
+     "\n       -q              Quiet, 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"
+#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 pipe_progress_trivial_usage NOUSAGE_STR
+#define pipe_progress_full_usage ""
+
+#define pivot_root_trivial_usage \
+       "NEW_ROOT PUT_OLD"
+#define pivot_root_full_usage "\n\n" \
+       "Move the current root file system to PUT_OLD and make NEW_ROOT\n" \
+       "the new root file system"
+
+#define pkill_trivial_usage \
+       "[-l] | [-fnovx] [-signal] pattern"
+#define pkill_full_usage "\n\n" \
+       "Send a signal to process(es) selected by regex pattern\n" \
+     "\nOptions:" \
+     "\n       -l      List all signals" \
+     "\n       -f      Match against entire command line" \
+     "\n       -n      Signal the newest process only" \
+     "\n       -o      Signal the oldest process only" \
+     "\n       -v      Negate the matching" \
+     "\n       -x      Match whole name (not substring)" \
+
+#define popmaildir_trivial_usage \
+       "[OPTIONS] Maildir [connection-helper ...]"
+#define popmaildir_full_usage "\n\n" \
+       "Fetch content of remote mailbox to local maildir\n" \
+     "\nOptions:" \
+     "\n       -b              Binary mode. Ignored" \
+     "\n       -d              Debug. Ignored" \
+     "\n       -m              Show used memory. Ignored" \
+     "\n       -V              Show version. Ignored" \
+     "\n       -c              Use tcpclient. Ignored" \
+     "\n       -a              Use APOP protocol. Implied. If server supports APOP -> use it" \
+     "\n       -s              Skip authorization" \
+     "\n       -T              Get messages with TOP instead with RETR" \
+     "\n       -k              Keep retrieved messages on the server" \
+     "\n       -t timeout      Set network timeout" \
+     USE_FEATURE_POPMAILDIR_DELIVERY( \
+     "\n       -F \"program arg1 arg2 ...\"    Filter by program. May be multiple" \
+     "\n       -M \"program arg1 arg2 ...\"    Deliver by program" \
+     ) \
+     "\n       -R size         Remove old messages on the server >= size (in bytes). Ignored" \
+     "\n       -Z N1-N2        Remove messages from N1 to N2 (dangerous). Ignored" \
+     "\n       -L size         Do not retrieve new messages >= size (in bytes). Ignored" \
+     "\n       -H lines        Type specified number of lines of a message. Ignored"
+#define popmaildir_example_usage \
+       "$ popmaildir -k ~/Maildir -- nc pop.drvv.ru 110 [<password_file]\n" \
+       "$ popmaildir ~/Maildir -- openssl s_client -quiet -connect pop.gmail.com:995 [<password_file]\n"
+
+#define poweroff_trivial_usage \
+       "[-d delay] [-n] [-f]"
+#define poweroff_full_usage "\n\n" \
+       "Halt and shut off power\n" \
+     "\nOptions:" \
+     "\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 "\n\n" \
+       "Print all or part of environment.\n" \
+       "If no environment VARIABLE specified, print them all."
+
+#define printf_trivial_usage \
+       "FORMAT [ARGUMENT...]"
+#define printf_full_usage "\n\n" \
+       "Format and print 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 "\n\n" \
+       "Report process status\n" \
+     "\nOptions:" \
+     "\n       -o col1,col2=header     Select columns for display" \
+
+#else /* !ENABLE_DESKTOP */
+
+#if !ENABLE_SELINUX && !ENABLE_FEATURE_PS_WIDE
+#define USAGE_PS "\nThis version of ps accepts no options"
+#else
+#define USAGE_PS "\nOptions:"
+#endif
+
+#define ps_trivial_usage \
+       ""
+#define ps_full_usage "\n\n" \
+       "Report process status\n" \
+       USAGE_PS \
+       USE_SELINUX( \
+     "\n       -Z      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 pscan_trivial_usage \
+       "[-cb] [-p MIN_PORT] [-P MAX_PORT] [-t TIMEOUT] [-T MIN_RTT] HOST"
+#define pscan_full_usage "\n\n" \
+       "Scan a host, print all open ports\n" \
+     "\nOptions:" \
+     "\n       -c      Show closed ports too" \
+     "\n       -b      Show blocked ports too" \
+     "\n       -p      Scan from this port (default 1)" \
+     "\n       -P      Scan up to this port (default 1024)" \
+     "\n       -t      Timeout (default 5000 ms)" \
+     "\n       -T      Minimum rtt (default 5 ms, increase for congested hosts)" \
+
+#define pwd_trivial_usage \
+       ""
+#define pwd_full_usage "\n\n" \
+       "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 "\n\n" \
+       "Tell 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 "\n\n" \
+       "Get and possibly set the system date and time from a remote HOST\n" \
+     "\nOptions:" \
+     "\n       -s      Set the system date and time (default)" \
+     "\n       -p      Print the date and time" \
+
+#define rdev_trivial_usage \
+       ""
+#define rdev_full_usage "\n\n" \
+       "Print the device node associated with the filesystem mounted at '/'"
+#define rdev_example_usage \
+       "$ rdev\n" \
+       "/dev/mtdblock9 /\n"
+
+#define readahead_trivial_usage \
+       "[FILE]..."
+#define readahead_full_usage "\n\n" \
+       "Preload 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("[-fnv] ") "FILE"
+#define readlink_full_usage "\n\n" \
+       "Display the value of a symlink" \
+       USE_FEATURE_READLINK_FOLLOW( "\n" \
+     "\nOptions:" \
+     "\n       -f      Canonicalize by following all symlinks" \
+     "\n       -n      Don't add newline" \
+     "\n       -v      Verbose" \
+       ) \
+
+#define readprofile_trivial_usage \
+       "[OPTIONS]..."
+#define readprofile_full_usage "\n\n" \
+       "Options:" \
+     "\n       -m mapfile      (Default: /boot/System.map)" \
+     "\n       -p profile      (Default: /proc/profile)" \
+     "\n       -M mult         Set the profiling multiplier to mult" \
+     "\n       -i              Print only info about the sampling step" \
+     "\n       -v              Verbose" \
+     "\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 "\n\n" \
+       "Return the absolute pathnames of given argument"
+
+#define reboot_trivial_usage \
+       "[-d delay] [-n] [-f]"
+#define reboot_full_usage "\n\n" \
+       "Reboot the system\n" \
+     "\nOptions:" \
+     "\n       -d      Delay interval for rebooting" \
+     "\n       -n      No call to sync()" \
+     "\n       -f      Force reboot (don't go through init)" \
+
+#define reformime_trivial_usage \
+       "[OPTION]... [FILE]..."
+#define reformime_full_usage "\n\n" \
+       "Parse MIME-encoded message\n" \
+     "\nOptions:" \
+     "\n       -x prefix       Extract content of MIME sections to files" \
+     "\n       -X prog [args]  Filter content of MIME sections through prog." \
+     "\n                       Must be the last option" \
+     "\n" \
+     "\nOther options are silently ignored." \
+
+#define renice_trivial_usage \
+       "{{-n INCREMENT} | PRIORITY} [[-p | -g | -u] ID...]"
+#define renice_full_usage "\n\n" \
+       "Change priority of running processes\n" \
+     "\nOptions:" \
+     "\n       -n      Adjust 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 "\n\n" \
+       "Reset the screen"
+
+#define resize_trivial_usage \
+       ""
+#define resize_full_usage "\n\n" \
+       "Resize the screen"
+
+#define restorecon_trivial_usage \
+       "[-iFnrRv] [-e excludedir]... [-o filename] [-f filename | pathname]"
+#define restorecon_full_usage "\n\n" \
+       "Reset security contexts of files in pathname\n" \
+     "\n       -i              Ignore files that do not exist" \
+     "\n       -f file         File with list of files to process. Use - for stdin" \
+     "\n       -e directory    Directory to exclude" \
+     "\n       -R,-r           Recurse directories" \
+     "\n       -n              Don't change any file labels" \
+     "\n       -o file         Save list of files with incorrect context" \
+     "\n       -v              Verbose" \
+     "\n       -vv             Show changed labels" \
+     "\n       -F              Force reset of context to match file_context" \
+     "\n                       for customizable files, or the user section," \
+     "\n                       if it has changed" \
+
+#define rm_trivial_usage \
+       "[OPTION]... FILE..."
+#define rm_full_usage "\n\n" \
+       "Remove (unlink) the FILE(s). Use '--' to\n" \
+       "indicate that all following arguments are non-options.\n" \
+     "\nOptions:" \
+     "\n       -i      Always prompt before removing" \
+     "\n       -f      Never prompt" \
+     "\n       -r,-R   Remove directories recursively" \
+
+#define rm_example_usage \
+       "$ rm -rf /tmp/foo\n"
+
+#define rmdir_trivial_usage \
+       "[OPTION]... DIRECTORY..."
+#define rmdir_full_usage "\n\n" \
+       "Remove the DIRECTORY, if it is empty.\n" \
+     "\nOptions:" \
+     USE_FEATURE_RMDIR_LONG_OPTIONS( \
+     "\n       -p|--parents    Include parents" \
+     "\n       -ignore-fail-on-non-empty" \
+     ) \
+     SKIP_FEATURE_RMDIR_LONG_OPTIONS( \
+     "\n       -p      Include parents" \
+     )
+
+#define rmdir_example_usage \
+       "# rmdir /tmp/foo\n"
+
+#define rmmod_trivial_usage \
+       "[OPTION]... [MODULE]..."
+#define rmmod_full_usage "\n\n" \
+       "Unload the specified kernel modules from the kernel\n" \
+     "\nOptions:" \
+     "\n       -w      Wait until the module is no longer used" \
+     "\n       -f      Force unloading" \
+     "\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 "\n\n" \
+       "Edit kernel routing tables\n" \
+     "\nOptions:" \
+     "\n       -n      Don't 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 "\n\n" \
+       "Manipulate 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 "\n\n" \
+       "Output a cpio archive of the rpm file"
+
+#define rtcwake_trivial_usage \
+       "[-a | -l | -u] [-d DEV] [-m MODE] [-s SEC | -t TIME]"
+#define rtcwake_full_usage "\n\n" \
+       "Enter a system sleep state until specified wakeup time\n" \
+       USE_GETOPT_LONG( \
+     "\n       -a,--auto       Read clock mode from adjtime" \
+     "\n       -l,--local      Clock is set to local time" \
+     "\n       -u,--utc        Clock is set to UTC time" \
+     "\n       -d,--device=DEV Specify the RTC device" \
+     "\n       -m,--mode=MODE  Set the sleep state (default: standby)" \
+     "\n       -s,--seconds=SEC Set the timeout in SEC seconds from now" \
+     "\n       -t,--time=TIME  Set the timeout to TIME seconds from epoch" \
+       ) \
+       SKIP_GETOPT_LONG( \
+     "\n       -a      Read clock mode from adjtime" \
+     "\n       -l      Clock is set to local time" \
+     "\n       -u      Clock is set to UTC time" \
+     "\n       -d DEV  Specify the RTC device" \
+     "\n       -m MODE Set the sleep state (default: standby)" \
+     "\n       -s SEC  Set the timeout in SEC seconds from now" \
+     "\n       -t TIME Set the timeout to TIME seconds from epoch" \
+       )
+
+#define runcon_trivial_usage \
+       "[-c] [-u USER] [-r ROLE] [-t TYPE] [-l RANGE] COMMAND [args]\n" \
+       "       runcon CONTEXT COMMAND [args]"
+#define runcon_full_usage "\n\n" \
+       "Run a program in a different security context\n" \
+     "\n       CONTEXT         Complete security context\n" \
+       USE_FEATURE_RUNCON_LONG_OPTIONS( \
+     "\n       -c,--compute    Compute process transition context before modifying" \
+     "\n       -t,--type=TYPE  Type (for same role as parent)" \
+     "\n       -u,--user=USER  User identity" \
+     "\n       -r,--role=ROLE  Role" \
+     "\n       -l,--range=RNG  Levelrange" \
+       ) \
+       SKIP_FEATURE_RUNCON_LONG_OPTIONS( \
+     "\n       -c      Compute process transition context before modifying" \
+     "\n       -t TYPE Type (for same role as parent)" \
+     "\n       -u USER User identity" \
+     "\n       -r ROLE Role" \
+     "\n       -l RNG  Levelrange" \
+       )
+
+#define run_parts_trivial_usage \
+       "[-t] "USE_FEATURE_RUN_PARTS_FANCY("[-l] ")"[-a ARG] [-u MASK] DIRECTORY"
+#define run_parts_full_usage "\n\n" \
+       "Run a bunch of scripts in a directory\n" \
+     "\nOptions:" \
+     "\n       -t      Print what would be run, but don't actually run anything" \
+     "\n       -a ARG  Pass ARG as argument for every program" \
+     "\n       -u MASK Set the umask to MASK before running every program" \
+       USE_FEATURE_RUN_PARTS_FANCY( \
+     "\n       -l      Print names of all matching files even if they are not executable" \
+       )
+
+#define run_parts_example_usage \
+       "$ run-parts -a start /etc/init.d\n" \
+       "$ run-parts -a stop=now /etc/init.d\n\n" \
+       "Let's assume you have a script foo/dosomething:\n" \
+       "#!/bin/sh\n" \
+       "for i in $*; do eval $i; done; unset i\n" \
+       "case \"$1\" in\n" \
+       "start*) echo starting something;;\n" \
+       "stop*) set -x; shutdown -h $stop;;\n" \
+       "esac\n\n" \
+       "Running this yields:\n" \
+       "$run-parts -a stop=+4m foo/\n" \
+       "+ shutdown -h +4m"
+
+#define runlevel_trivial_usage \
+       "[utmp]"
+#define runlevel_full_usage "\n\n" \
+       "Find the current and previous system runlevel.\n\n" \
+       "If no utmp file exists or if no runlevel record can be found,\n" \
+       "print \"unknown\""
+#define runlevel_example_usage \
+       "$ runlevel /var/run/utmp\n" \
+       "N 2"
+
+#define runsv_trivial_usage \
+       "dir"
+#define runsv_full_usage "\n\n" \
+       "Start and monitor a service and optionally an appendant log service"
+
+#define runsvdir_trivial_usage \
+       "[-P] [-s SCRIPT] dir"
+#define runsvdir_full_usage "\n\n" \
+       "Start a runsv process for each subdirectory. If it exits, restart it.\n" \
+     "\n       -P              Put each runsv in a new session" \
+     "\n       -s SCRIPT       Run SCRIPT <signo> after signal is processed" \
+
+#define rx_trivial_usage \
+       "FILE"
+#define rx_full_usage "\n\n" \
+       "Receive a file using the xmodem protocol"
+#define rx_example_usage \
+       "$ rx /tmp/foo\n"
+
+#define script_trivial_usage \
+       "[-afq] [-c COMMAND] [OUTFILE]"
+#define script_full_usage "\n\n" \
+       "Options:" \
+     "\n       -a      Append output" \
+     "\n       -c      Run COMMAND, not shell" \
+     "\n       -f      Flush output after each write" \
+     "\n       -q      Quiet" \
+
+#define sed_trivial_usage \
+       "[-efinr] pattern [files...]"
+#define sed_full_usage "\n\n" \
+       "Options:" \
+     "\n       -e script       Add the script to the commands to be executed" \
+     "\n       -f scriptfile   Add scriptfile 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" \
+     "\nIf no -e or -f is given, the first non-option argument is taken as the sed" \
+     "\nscript to interpret. All remaining arguments are names of input files; if no" \
+     "\ninput files are specified, then the standard input is read. Source files" \
+     "\nwill 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 selinuxenabled_trivial_usage NOUSAGE_STR
+#define selinuxenabled_full_usage ""
+
+#define sendmail_trivial_usage \
+       "[OPTIONS] [rcpt]..."
+#define sendmail_full_usage "\n\n" \
+       "Send an email\n" \
+     "\nStandard options:" \
+     "\n       -t              Read recipients from message body, add them to those on cmdline" \
+     "\n       -f sender       Sender. REQUIRED!" \
+     "\n       -o options      various options. -oi IMPLIED! others are IGNORED!" \
+     "\n" \
+     "\nBusybox specific options:" \
+     "\n       -w seconds      Network timeout" \
+     "\n       -H 'prog args'  Run connection helper" \
+     "\n                       Examples:" \
+     "\n                       -H 'exec openssl s_client -quiet -tls1 -starttls smtp" \
+     "\n                               -connect smtp.gmail.com:25' <email.txt" \
+     "\n                               [4<username_and_passwd.txt | -au<username> -ap<password>]" \
+     "\n                       -H 'exec openssl s_client -quiet -tls1" \
+     "\n                               -connect smtp.gmail.com:465' <email.txt" \
+     "\n                               [4<username_and_passwd.txt | -au<username> -ap<password>]" \
+     "\n       -S server[:port] Server" \
+     "\n       -au<username>   Username for AUTH LOGIN" \
+     "\n       -ap<password>   Password for AUTH LOGIN" \
+     "\n       -am<method>     Authentication method. Ignored. login is implied." \
+     "\n" \
+     "\nOther options are silently ignored; -oi -t is implied" \
+
+#define seq_trivial_usage \
+       "[-w] [-s SEP] [FIRST [INC]] LAST"
+#define seq_full_usage "\n\n" \
+       "Print numbers from FIRST to LAST, in steps of INC.\n" \
+       "FIRST, INC default to 1\n" \
+     "\nOptions:" \
+     "\n       -w      Pad to last with leading zeros" \
+     "\n       -s SEP  String separator" \
+
+#define sestatus_trivial_usage \
+       "[-vb]"
+#define sestatus_full_usage "\n\n" \
+       "       -v      Verbose" \
+     "\n       -b      Display current state of booleans" \
+
+#define setconsole_trivial_usage \
+       "[-r" USE_FEATURE_SETCONSOLE_LONG_OPTIONS("|--reset") "] [DEVICE]"
+#define setconsole_full_usage "\n\n" \
+       "Redirect system console output to DEVICE (default: /dev/tty)\n" \
+     "\nOptions:" \
+     "\n       -r      Reset output to /dev/console" \
+
+#define setenforce_trivial_usage \
+       "[Enforcing | Permissive | 1 | 0]"
+#define setenforce_full_usage ""
+
+#define setfiles_trivial_usage \
+       "[-dnpqsvW] [-e dir]... [-o file] [-r alt_root_path]" \
+       USE_FEATURE_SETFILES_CHECK_OPTION( \
+       " [-c policyfile] spec_file" \
+       ) \
+       " pathname"
+#define setfiles_full_usage "\n\n" \
+       "Reset file contexts under pathname according to spec_file\n" \
+       USE_FEATURE_SETFILES_CHECK_OPTION( \
+     "\n       -c file Check the validity of the contexts against the specified binary policy" \
+       ) \
+     "\n       -d      Show which specification matched each file" \
+     "\n       -l      Log changes in file labels to syslog" \
+     "\n       -n      Don't change any file labels" \
+     "\n       -q      Suppress warnings" \
+     "\n       -r dir  Use an altenate root path" \
+     "\n       -e dir  Exclude directory" \
+     "\n       -F      Force reset of context to match file_context for customizable files" \
+     "\n       -o file Save list of files with incorrect context" \
+     "\n       -s      Take a list of files from standard input (instead of command line)" \
+     "\n       -v      Show changes in file labels, if type or role are changing" \
+     "\n       -vv     Show changes in file labels, if type, role, or user are changing" \
+     "\n       -W      Display warnings about entries that had no matching files" \
+
+#define setfont_trivial_usage \
+       "FONT [-m MAPFILE] [-C TTY]"
+#define setfont_full_usage "\n\n" \
+       "Load a console font\n" \
+     "\nOptions:" \
+     "\n       -m MAPFILE      Load console screen map" \
+     "\n       -C TTY          Affect TTY instead of /dev/tty" \
+
+#define setfont_example_usage \
+       "$ setfont -m koi8-r /etc/i18n/fontname\n"
+
+#define setkeycodes_trivial_usage \
+       "SCANCODE KEYCODE..."
+#define setkeycodes_full_usage "\n\n" \
+       "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 "\n\n" \
+       "Redirect the kernel output to console N (0 for current)"
+
+#define setsebool_trivial_usage \
+       "boolean value"
+
+#define setsebool_full_usage "\n\n" \
+       "Change boolean setting"
+
+#define setsid_trivial_usage \
+       "PROG [ARG...]"
+#define setsid_full_usage "\n\n" \
+       "Run PROG in a new session. PROG will have no controlling terminal\n" \
+       "and will not be affected by keyboard signals (Ctrl-C etc).\n" \
+       "See setsid(2) for details." \
+
+#define lash_trivial_usage \
+       "[FILE]...\n" \
+       "or: sh -c command [args]..."
+#define lash_full_usage "\n\n" \
+       "lash is deprecated, please use hush"
+
+#define last_trivial_usage \
+       ""USE_FEATURE_LAST_FANCY("[-HW] [-f file]")
+#define last_full_usage "\n\n" \
+       "Show listing of the last users that logged into the system" \
+       USE_FEATURE_LAST_FANCY( "\n" \
+     "\nOptions:" \
+/*   "\n       -H      Show header line" */ \
+     "\n       -W      Display with no host column truncation" \
+     "\n       -f file Read from file instead of /var/log/wtmp" \
+       )
+
+#define showkey_trivial_usage \
+       "[-a | -k | -s]"
+#define showkey_full_usage "\n\n" \
+       "Show keys pressed\n" \
+     "\nOptions:" \
+     "\n       -a      Display decimal/octal/hex values of the keys" \
+     "\n       -k      Display interpreted keycodes (default)" \
+     "\n       -s      Display raw scan-codes" \
+
+#define slattach_trivial_usage \
+       "[-cehmLF] [-s speed] [-p protocol] DEVICEs"
+#define slattach_full_usage "\n\n" \
+       "Attach network interface(s) to serial line(s)\n" \
+     "\nOptions:" \
+     "\n       -p      Set protocol (slip, cslip, slip6, clisp6 or adaptive)" \
+     "\n       -s      Set line speed" \
+     "\n       -e      Exit after initializing device" \
+     "\n       -h      Exit when the carrier is lost" \
+     "\n       -c      Execute a command when the line is hung up" \
+     "\n       -m      Do NOT initialize the line in raw 8 bits mode" \
+     "\n       -L      Enable 3-wire operation" \
+     "\n       -F      Disable RTS/CTS flow control" \
+
+#define sleep_trivial_usage \
+       USE_FEATURE_FANCY_SLEEP("[") "N" USE_FEATURE_FANCY_SLEEP("]...")
+#define sleep_full_usage "\n\n" \
+       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 FILE] [-k start[.offset][opts][,end[.offset][opts]] [-t CHAR") \
+       "] [FILE]..."
+#define sort_full_usage "\n\n" \
+       "Sort lines of text\n" \
+     "\nOptions:" \
+       USE_FEATURE_SORT_BIG( \
+     "\n       -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      Sort key" \
+     "\n       -M      Sort month" \
+       ) \
+     "\n       -n      Sort numbers" \
+       USE_FEATURE_SORT_BIG( \
+     "\n       -o      Output to file" \
+     "\n       -k      Sort by key" \
+     "\n       -t CHAR Key separator" \
+       ) \
+     "\n       -r      Reverse sort order" \
+       USE_FEATURE_SORT_BIG( \
+     "\n       -s      Stable (don't sort ties alphabetically)" \
+       ) \
+     "\n       -u      Suppress duplicate lines" \
+       USE_FEATURE_SORT_BIG( \
+     "\n       -z      Lines are terminated by NUL, not newline" \
+     "\n       -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 split_trivial_usage \
+       "[OPTION] [INPUT [PREFIX]]"
+#define split_full_usage "\n\n" \
+       "Options:" \
+     "\n       -b n[k|m]       Split by bytes" \
+     "\n       -l n            Split by lines" \
+     "\n       -a n            Use n letters as suffix" \
+
+#define split_example_usage \
+       "$ split TODO foo\n" \
+       "$ cat TODO | split -a 2 -l 2 TODO_\n"
+
+#define start_stop_daemon_trivial_usage \
+       "[OPTIONS] [-S|-K] ... [-- arguments...]"
+#define start_stop_daemon_full_usage "\n\n" \
+       "Search for matching processes, and then\n" \
+       "-K: stop all matching processes.\n" \
+       "-S: start a process unless a matching process is found.\n" \
+       USE_FEATURE_START_STOP_DAEMON_LONG_OPTIONS( \
+     "\nProcess matching:" \
+     "\n       -u,--user USERNAME|UID  Match only this user's processes" \
+     "\n       -n,--name NAME          Match processes with NAME" \
+     "\n                               in comm field in /proc/PID/stat" \
+     "\n       -x,--exec EXECUTABLE    Match processes with this command" \
+     "\n                               in /proc/PID/cmdline" \
+     "\n       -p,--pidfile FILE       Match a process with PID from the file" \
+     "\n       All specified conditions must match" \
+     "\n-S only:" \
+     "\n       -x,--exec EXECUTABLE    Program to run" \
+     "\n       -a,--startas NAME       Zeroth argument" \
+     "\n       -b,--background         Background" \
+       USE_FEATURE_START_STOP_DAEMON_FANCY( \
+     "\n       -N,--nicelevel N        Change nice level" \
+       ) \
+     "\n       -c,--chuid USER[:[GRP]] Change to user/group" \
+     "\n       -m,--make-pidfile       Write PID to the pidfile specified by -p" \
+     "\n-K only:" \
+     "\n       -s,--signal SIG         Signal to send" \
+     "\n       -t,--test               Match only, exit with 0 if a process is found" \
+     "\nOther:" \
+       USE_FEATURE_START_STOP_DAEMON_FANCY( \
+     "\n       -o,--oknodo             Exit with status 0 if nothing is done" \
+     "\n       -v,--verbose            Verbose" \
+       ) \
+     "\n       -q,--quiet              Quiet" \
+       ) \
+       SKIP_FEATURE_START_STOP_DAEMON_LONG_OPTIONS( \
+     "\nProcess matching:" \
+     "\n       -u USERNAME|UID Match only this user's processes" \
+     "\n       -n NAME         Match processes with NAME" \
+     "\n                       in comm field in /proc/PID/stat" \
+     "\n       -x EXECUTABLE   Match processes with this command" \
+     "\n                       command in /proc/PID/cmdline" \
+     "\n       -p FILE         Match a process with PID from the file" \
+     "\n       All specified conditions must match" \
+     "\n-S only:" \
+     "\n       -x EXECUTABLE   Program to run" \
+     "\n       -a NAME         Zeroth argument" \
+     "\n       -b              Background" \
+       USE_FEATURE_START_STOP_DAEMON_FANCY( \
+     "\n       -N N            Change nice level" \
+       ) \
+     "\n       -c USER[:[GRP]] Change to user/group" \
+     "\n       -m              Write PID to the pidfile specified by -p" \
+     "\n-K only:" \
+     "\n       -s SIG          Signal to send" \
+     "\n       -t              Match only, exit with 0 if a process is found" \
+     "\nOther:" \
+       USE_FEATURE_START_STOP_DAEMON_FANCY( \
+     "\n       -o              Exit with status 0 if nothing is done" \
+     "\n       -v              Verbose" \
+       ) \
+     "\n       -q              Quiet" \
+       ) \
+
+#define stat_trivial_usage \
+       "[OPTION] FILE..."
+#define stat_full_usage "\n\n" \
+       "Display file (default) or filesystem status\n" \
+     "\nOptions:" \
+       USE_FEATURE_STAT_FORMAT( \
+     "\n       -c fmt  Use the specified format" \
+       ) \
+     "\n       -f      Display filesystem status" \
+     "\n       -L      Dereference links" \
+     "\n       -t      Display info in terse form" \
+       USE_SELINUX( \
+     "\n       -Z      Print security context" \
+       ) \
+       USE_FEATURE_STAT_FORMAT( \
+       "\n\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 symlink\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" \
+       USE_SELINUX( \
+       " %C    Security context in SELinux\n" \
+       ) \
+       " %i    File System ID in hex\n" \
+       " %l    Maximum length of filenames\n" \
+       " %n    File name\n" \
+       " %s    Block size (for faster transfer)\n" \
+       " %S    Fundamental block size (for block counts)\n" \
+       " %t    Type in hex\n" \
+       " %T    Type in human readable form" \
+       ) \
+
+#define strings_trivial_usage \
+       "[-afo] [-n length] [file...]"
+#define strings_full_usage "\n\n" \
+       "Display printable strings in a binary file\n" \
+     "\nOptions:" \
+     "\n       -a      Scan whole file (default)" \
+     "\n       -f      Precede strings with filenames" \
+     "\n       -n N    At least N characters form a string (default 4)" \
+     "\n       -o      Precede strings with decimal offsets" \
+
+#define stty_trivial_usage \
+       "[-a|g] [-F DEVICE] [SETTING]..."
+#define stty_full_usage "\n\n" \
+       "Without arguments, prints baud rate, line discipline,\n" \
+       "and 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 "\n\n" \
+       "Change user id or become root\n" \
+     "\nOptions:" \
+     "\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 "\n\n" \
+       "Single user login\n" \
+     "\nOptions:" \
+     "\n       -t      Timeout" \
+
+#define sum_trivial_usage \
+       "[rs] [files...]"
+#define sum_full_usage "\n\n" \
+       "Checksum and count the blocks in a file\n" \
+     "\nOptions:" \
+     "\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 "\n\n" \
+       "Control services monitored by runsv supervisor.\n" \
+       "Commands (only first character is enough):\n" \
+       "\n" \
+       "status: query service status\n" \
+       "up: if service isn't running, start it. If service stops, restart it\n" \
+       "once: like 'up', but if service stops, don't restart it\n" \
+       "down: send TERM and CONT signals. If ./run exits, start ./finish\n" \
+       "       if it exists. After it stops, do not restart service\n" \
+       "exit: send TERM and CONT signals to service and log service. If they exit,\n" \
+       "       runsv exits too\n" \
+       "pause, cont, hup, alarm, interrupt, quit, 1, 2, term, kill: send\n" \
+       "STOP, CONT, HUP, ALRM, INT, QUIT, USR1, USR2, TERM, KILL signal to service" \
+
+#define svlogd_trivial_usage \
+       "[-ttv] [-r c] [-R abc] [-l len] [-b buflen] dir..."
+#define svlogd_full_usage "\n\n" \
+       "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 "\n\n" \
+       "Stop swapping on DEVICE\n" \
+     "\nOptions:" \
+     "\n       -a      Stop swapping on all swap devices" \
+
+#define swapon_trivial_usage \
+       "[-a]" USE_FEATURE_SWAPON_PRI(" [-p pri]") " [DEVICE]"
+#define swapon_full_usage "\n\n" \
+       "Start swapping on DEVICE\n" \
+     "\nOptions:" \
+     "\n       -a      Start swapping on all swap devices" \
+       USE_FEATURE_SWAPON_PRI( \
+     "\n       -p pri  Set swap device priority" \
+       ) \
+
+#define switch_root_trivial_usage \
+       "[-c /dev/console] NEW_ROOT NEW_INIT [ARGUMENTS_TO_INIT]"
+#define switch_root_full_usage "\n\n" \
+       "Use from PID 1 under initramfs to free initramfs, chroot to NEW_ROOT,\n" \
+       "and exec NEW_INIT\n" \
+     "\nOptions:" \
+     "\n       -c      Redirect console to device on new root" \
+
+#define sync_trivial_usage \
+       ""
+#define sync_full_usage "\n\n" \
+       "Write all buffered filesystem blocks to disk"
+
+#define sysctl_trivial_usage \
+       "[OPTIONS]... [VALUE]..."
+#define sysctl_full_usage "\n\n" \
+       "Configure kernel parameters at runtime\n" \
+     "\nOptions:" \
+     "\n       -n      Disable printing of key names" \
+     "\n       -e      Don't warn about unknown keys" \
+     "\n       -w      Change sysctl setting" \
+     "\n       -p FILE Load sysctl settings from FILE (default /etc/sysctl.conf)" \
+     "\n       -a      Display all values" \
+     "\n       -A      Display all values in table form" \
+
+#define sysctl_example_usage \
+       "sysctl [-n] [-e] variable...\n" \
+       "sysctl [-n] [-e] -w variable=value...\n" \
+       "sysctl [-n] [-e] -a\n" \
+       "sysctl [-n] [-e] -p file       (default /etc/sysctl.conf)\n" \
+       "sysctl [-n] [-e] -A\n"
+
+#define syslogd_trivial_usage \
+       "[OPTION]..."
+#define syslogd_full_usage "\n\n" \
+       "System logging utility.\n" \
+       "Note that this version of syslogd ignores /etc/syslog.conf.\n" \
+     "\nOptions:" \
+     "\n       -n              Run in foreground" \
+     "\n       -O FILE         Log to given file (default=/var/log/messages)" \
+     "\n       -l n            Set local log level" \
+     "\n       -S              Smaller logging output" \
+       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 (default is network only if -R)") \
+       USE_FEATURE_SYSLOGD_DUP( \
+     "\n       -D              Drop duplicates") \
+       USE_FEATURE_IPC_SYSLOG( \
+     "\n       -C[size(KiB)]   Log to shared mem buffer (read it using logread)") \
+       /* NB: -Csize shouldn't have space (because size is optional) */
+/*   "\n       -m MIN          Minutes between MARK lines (default=20, 0=off)" */
+
+#define syslogd_example_usage \
+       "$ syslogd -R masterlog:514\n" \
+       "$ syslogd -R 192.168.1.1:601\n"
+
+#define tac_trivial_usage \
+       "[FILE]..."
+#define tac_full_usage "\n\n" \
+       "Concatenate FILE(s) and print them in reverse"
+
+#define tail_trivial_usage \
+       "[OPTION]... [FILE]..."
+#define tail_full_usage "\n\n" \
+       "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" \
+     "\nOptions:" \
+       USE_FEATURE_FANCY_TAIL( \
+     "\n       -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" \
+     "\nIf the first character of N (bytes or lines) is a '+', output begins with" \
+     "\nthe Nth item from the start of each file, otherwise, print the last N items" \
+     "\nin 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_SEAMLESS_GZ("z") \
+       USE_FEATURE_SEAMLESS_BZ2("j") USE_FEATURE_SEAMLESS_LZMA("a") \
+       USE_FEATURE_SEAMLESS_Z("Z") "xtvO] " \
+       USE_FEATURE_TAR_FROM("[-X FILE] ") \
+       "[-f TARFILE] [-C DIR] [FILE(s)]..."
+#define tar_full_usage "\n\n" \
+       "Create, extract, or list files from a tar file\n" \
+     "\nOptions:" \
+       USE_FEATURE_TAR_CREATE( \
+     "\n       c       Create") \
+     "\n       x       Extract" \
+     "\n       t       List" \
+     "\nArchive format selection:" \
+       USE_FEATURE_SEAMLESS_GZ( \
+     "\n       z       Filter the archive through gzip" \
+       ) \
+       USE_FEATURE_SEAMLESS_BZ2( \
+     "\n       j       Filter the archive through bzip2" \
+       ) \
+       USE_FEATURE_SEAMLESS_LZMA( \
+     "\n       a       Filter the archive through lzma" \
+       ) \
+       USE_FEATURE_SEAMLESS_Z( \
+     "\n       Z       Filter the archive through compress" \
+       ) \
+     "\nFile selection:" \
+     "\n       f       Name of TARFILE or \"-\" for stdin" \
+     "\n       O       Extract to stdout" \
+       USE_FEATURE_TAR_FROM( \
+     "\n       exclude File to exclude" \
+     "\n       X       File with names to exclude" \
+       ) \
+     "\n       C       Change to directory DIR before operation" \
+     "\n       v       Verbose" \
+
+#define tar_example_usage \
+       "$ zcat /tmp/tarball.tar.gz | tar -xf -\n" \
+       "$ tar -cf /tmp/tarball.tar /usr/local\n"
+
+#define taskset_trivial_usage \
+       "[-p] [mask] [pid | command [arg]...]"
+#define taskset_full_usage "\n\n" \
+       "Set or get CPU affinity\n" \
+     "\nOptions:" \
+     "\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 "\n\n" \
+       "Copy standard input to each FILE, and also to standard output\n" \
+     "\nOptions:" \
+     "\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"
+
+#if ENABLE_FEATURE_TELNET_AUTOLOGIN
+#define telnet_trivial_usage \
+       "[-a] [-l USER] HOST [PORT]"
+#define telnet_full_usage "\n\n" \
+       "Connect to telnet server\n" \
+     "\nOptions:" \
+     "\n       -a      Automatic login with $USER variable" \
+     "\n       -l USER Automatic login as USER" \
+
+#else
+#define telnet_trivial_usage \
+       "HOST [PORT]"
+#define telnet_full_usage "\n\n" \
+       "Connect to telnet server"
+#endif
+
+#define telnetd_trivial_usage \
+       "[OPTION]"
+#define telnetd_full_usage "\n\n" \
+       "Handle incoming telnet connections" \
+       SKIP_FEATURE_TELNETD_STANDALONE(" via inetd") "\n" \
+     "\nOptions:" \
+     "\n       -l LOGIN        Exec LOGIN on connect" \
+     "\n       -f issue_file   Display issue_file instead of /etc/issue" \
+     "\n       -K              Close connection as soon as login exits" \
+     "\n                       (normally wait until all programs close slave pty)" \
+       USE_FEATURE_TELNETD_STANDALONE( \
+     "\n       -p PORT         Port to listen on" \
+     "\n       -b ADDR         Address to bind to" \
+     "\n       -F              Run in foreground" \
+     "\n       -i              Run as inetd subservice" \
+       )
+
+/* "test --help" does not print help (POSIX compat), only "[ --help" does.
+ * We display "<applet> EXPRESSION ]" here (not "<applet> EXPRESSION") */
+#define test_trivial_usage \
+       "EXPRESSION ]"
+#define test_full_usage "\n\n" \
+       "Check file types, compare values etc. Return a 0/1 exit code\n" \
+       "depending on logical 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 tc_trivial_usage \
+       /*"[OPTIONS] "*/"OBJECT CMD [dev STRING]"
+#define tc_full_usage "\n\n" \
+       "OBJECT: {qdisc|class|filter}\n" \
+       "CMD: {add|del|change|replace|show}\n" \
+       "\n" \
+       "qdisc [ handle QHANDLE ] [ root |"USE_FEATURE_TC_INGRESS(" ingress |")" parent CLASSID ]\n" \
+       /* "\t[ estimator INTERVAL TIME_CONSTANT ]\n" */ \
+       "\t[ [ QDISC_KIND ] [ help | OPTIONS ] ]\n" \
+       "\tQDISC_KIND := { [p|b]fifo | tbf | prio | cbq | red | etc. }\n" \
+       "qdisc show [ dev STRING ]"USE_FEATURE_TC_INGRESS(" [ingress]")"\n" \
+       "class [ classid CLASSID ] [ root | parent CLASSID ]\n" \
+       "\t[ [ QDISC_KIND ] [ help | OPTIONS ] ]\n" \
+       "class show [ dev STRING ] [ root | parent CLASSID ]\n" \
+       "filter [ pref PRIO ] [ protocol PROTO ]\n" \
+       /* "\t[ estimator INTERVAL TIME_CONSTANT ]\n" */ \
+       "\t[ root | classid CLASSID ] [ handle FILTERID ]\n" \
+       "\t[ [ FILTER_TYPE ] [ help | OPTIONS ] ]\n" \
+       "filter show [ dev STRING ] [ root | parent CLASSID ]"
+
+#define tcpsvd_trivial_usage \
+       "[-hEv] [-c n] [-C n:msg] [-b n] [-u user] [-l name] ip port prog..."
+/* with not-implemented options: */
+/*     "[-hpEvv] [-c n] [-C n:msg] [-b n] [-u user] [-l name] [-i dir|-x cdb] [-t sec] ip port prog..." */
+#define tcpsvd_full_usage "\n\n" \
+       "Create TCP socket, bind it to ip:port and listen\n" \
+       "for incoming connection. Run PROG for each connection.\n" \
+     "\nip             IP to listen on. '0' = all" \
+     "\nport           Port to listen on" \
+     "\nprog [arg]     Program to run" \
+     "\n-l name                Local hostname (else looks up local hostname in DNS)" \
+     "\n-u user[:group]        Change to user/group after bind" \
+     "\n-c n           Handle up to n connections simultaneously" \
+     "\n-b n           Allow a backlog of approximately n TCP SYNs" \
+     "\n-C n[:msg]     Allow only up to n connections from the same IP" \
+     "\n               New connections from this IP address are closed" \
+     "\n               immediately. 'msg' is written to the peer before close" \
+     "\n-h             Look up peer's hostname" \
+     "\n-E             Do not set up environment variables" \
+     "\n-v             Verbose" \
+
+#define udpsvd_trivial_usage \
+       "[-hEv] [-c n] [-u user] [-l name] ip port prog"
+#define udpsvd_full_usage "\n\n" \
+       "Create UDP socket, bind it to ip:port and wait\n" \
+       "for incoming packets. Run PROG for each packet,\n" \
+       "redirecting all further packets with same peer ip:port to it\n" \
+     "\nip             IP to listen on. '0' = all" \
+     "\nport           Port to listen on" \
+     "\nprog [arg]     Program to run" \
+     "\n-l name                Local hostname (else looks up local hostname in DNS)" \
+     "\n-u user[:group]        Change to user/group after bind" \
+     "\n-c n           Handle up to n connections simultaneously" \
+     "\n-h             Look up peer's hostname" \
+     "\n-E             Do not set up environment variables" \
+     "\n-v             Verbose" \
+
+#define tftp_trivial_usage \
+       "[OPTION]... HOST [PORT]"
+#define tftp_full_usage "\n\n" \
+       "Transfer a file from/to tftp server\n" \
+     "\nOptions:" \
+     "\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 tftpd_trivial_usage \
+       "[-cr] [-u USER] [DIR]"
+#define tftpd_full_usage "\n\n" \
+       "Transfer a file on tftp client's request.\n" \
+       "\n" \
+       "tftpd should be used as an inetd service.\n" \
+       "tftpd's line for inetd.conf:\n" \
+       "       69 dgram udp nowait root tftpd tftpd /files/to/serve\n" \
+       "It also can be ran from udpsvd:\n" \
+       "       udpsvd -vE 0.0.0.0 69 tftpd /files/to/serve\n" \
+     "\nOptions:" \
+     "\n       -r      Prohibit upload" \
+     "\n       -c      Allow file creation via upload" \
+     "\n       -u      Access files as USER" \
+
+#define time_trivial_usage \
+       "[OPTION]... COMMAND [ARGS...]"
+#define time_full_usage "\n\n" \
+       "Run the program COMMAND with arguments ARGS. When COMMAND finishes,\n" \
+       "COMMAND's resource usage information is displayed.\n" \
+     "\nOptions:" \
+     "\n       -v      Verbose" \
+
+#define timeout_trivial_usage \
+       "[-t SECS] [-s SIG] PROG [ARGS]"
+#define timeout_full_usage "\n\n" \
+       "Runs PROG. Sends SIG to it if it is not gone in SECS seconds.\n" \
+       "Defaults: SECS: 10, SIG: TERM." \
+
+#define top_trivial_usage \
+       "[-b] [-nCOUNT] [-dSECONDS]"
+#define top_full_usage "\n\n" \
+       "Provide a view of process activity in real time.\n" \
+       "Read the status of all processes from /proc each SECONDS\n" \
+       "and show the status for however many processes will fit on the screen." \
+
+#define touch_trivial_usage \
+       "[-c] FILE [FILE...]"
+#define touch_full_usage "\n\n" \
+       "Update the last-modified date on the given FILE[s]\n" \
+     "\nOptions:" \
+     "\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 "\n\n" \
+       "Translate, squeeze, and/or delete characters from\n" \
+       "standard input, writing to standard output\n" \
+     "\nOptions:" \
+     "\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 "\n\n" \
+       "Trace the route to HOST\n" \
+     "\nOptions:" \
+     "\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" \
+     "\n       -m max_ttl      Max time-to-live (max number of hops)" \
+     "\n       -p port#        Base UDP port number used in probes" \
+     "\n                       (default is 33434)" \
+     "\n       -q nqueries     Number of probes per 'ttl' (default 3)" \
+     "\n       -s src_addr     IP address to use as the source address" \
+     "\n       -t tos          Type-of-service in probe packets (default 0)" \
+     "\n       -w wait         Time in seconds to wait for a response" \
+     "\n                       (default 3 sec)" \
+     "\n       -g              Loose source route gateway (8 max)" \
+
+#define true_trivial_usage \
+       ""
+#define true_full_usage "\n\n" \
+       "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 "\n\n" \
+       "Print file name of standard input's terminal" \
+       USE_INCLUDE_SUSv2( "\n" \
+     "\nOptions:" \
+     "\n       -s      Print nothing, only return exit status" \
+       )
+#define tty_example_usage \
+       "$ tty\n" \
+       "/dev/tty2\n"
+
+#define ttysize_trivial_usage \
+       "[w] [h]"
+#define ttysize_full_usage "\n\n" \
+       "Print dimension(s) of standard input's terminal, on error return 80x25"
+
+#define tunctl_trivial_usage \
+       "[-f device] ([-t name] | -d name)" USE_FEATURE_TUNCTL_UG(" [-u owner] [-g group] [-b]")
+#define tunctl_full_usage "\n\n" \
+       "Create or delete tun interfaces" \
+     "\nOptions:" \
+     "\n       -f name         tun device (/dev/net/tun)" \
+     "\n       -t name         Create iface 'name'" \
+     "\n       -d name         Delete iface 'name'" \
+USE_FEATURE_TUNCTL_UG( \
+     "\n       -u owner        Set iface owner" \
+     "\n       -g group        Set iface group" \
+     "\n       -b              Brief output" \
+)
+#define tunctl_example_usage \
+       "# tunctl\n" \
+       "# tunctl -d tun0\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 "\n\n" \
+       "Adjust filesystem options on ext[23] filesystems"
+
+#define udhcpc_trivial_usage \
+       "[-Cfbnqtvo] [-c CID] [-V VCLS] [-H HOSTNAME] [-i INTERFACE]\n" \
+       "       [-p pidfile] [-r IP] [-s script] [-O dhcp-option]..." USE_FEATURE_UDHCP_PORT(" [-P N]")
+#define udhcpc_full_usage "\n\n" \
+       USE_GETOPT_LONG( \
+       "       -V,--vendorclass=CLASSID        Vendor class identifier" \
+     "\n       -i,--interface=INTERFACE        Interface to use (default eth0)" \
+     "\n       -H,-h,--hostname=HOSTNAME       Client hostname" \
+     "\n       -c,--clientid=CLIENTID  Client identifier" \
+     "\n       -C,--clientid-none      Suppress default client identifier" \
+     "\n       -p,--pidfile=file       Create pidfile" \
+     "\n       -r,--request=IP         IP address to request" \
+     "\n       -s,--script=file        Run file at DHCP events (default "CONFIG_UDHCPC_DEFAULT_SCRIPT")" \
+     "\n       -t,--retries=N          Send up to N request packets" \
+     "\n       -T,--timeout=N          Try to get a lease for N seconds (default 3)" \
+     "\n       -A,--tryagain=N         Wait N seconds (default 20) after failure" \
+     "\n       -O,--request-option=OPT Request DHCP option OPT (cumulative)" \
+     "\n       -o,--no-default-options Do not request any options (unless -O is also given)" \
+     "\n       -f,--foreground Run in foreground" \
+       USE_FOR_MMU( \
+     "\n       -b,--background Background if lease is not immediately obtained" \
+       ) \
+     "\n       -S,--syslog     Log to syslog too" \
+     "\n       -n,--now        Exit with failure if lease is not immediately obtained" \
+     "\n       -q,--quit       Quit after obtaining lease" \
+     "\n       -R,--release    Release IP on quit" \
+       USE_FEATURE_UDHCP_PORT( \
+     "\n       -P,--client-port N  Use port N instead of default 68" \
+       ) \
+       USE_FEATURE_UDHCPC_ARPING( \
+     "\n       -a,--arping     Use arping to validate offered address" \
+       ) \
+       ) \
+       SKIP_GETOPT_LONG( \
+       "       -V CLASSID      Vendor class identifier" \
+     "\n       -i INTERFACE    Interface to use (default: eth0)" \
+     "\n       -H,-h HOSTNAME  Client hostname" \
+     "\n       -c CLIENTID     Client identifier" \
+     "\n       -C              Suppress default client identifier" \
+     "\n       -p file         Create pidfile" \
+     "\n       -r IP           IP address to request" \
+     "\n       -s file         Run file at DHCP events (default "CONFIG_UDHCPC_DEFAULT_SCRIPT")" \
+     "\n       -t N            Send up to N request packets" \
+     "\n       -T N            Try to get a lease for N seconds (default 3)" \
+     "\n       -A N            Wait N seconds (default 20) after failure" \
+     "\n       -O OPT          Request DHCP option OPT (cumulative)" \
+     "\n       -o              Do not request any options (unless -O is also given)" \
+     "\n       -f              Run in foreground" \
+       USE_FOR_MMU( \
+     "\n       -b              Background if lease is not immediately obtained" \
+       ) \
+     "\n       -S              Log to syslog too" \
+     "\n       -n              Exit with failure if lease is not immediately obtained" \
+     "\n       -q              Quit after obtaining lease" \
+     "\n       -R              Release IP on quit" \
+       USE_FEATURE_UDHCP_PORT( \
+     "\n       -P N            Use port N instead of default 68" \
+       ) \
+       USE_FEATURE_UDHCPC_ARPING( \
+     "\n       -a              Use arping to validate offered address" \
+       ) \
+       )
+
+#define udhcpd_trivial_usage \
+       "[-fS]" USE_FEATURE_UDHCP_PORT(" [-P N]") " [configfile]" \
+
+#define udhcpd_full_usage "\n\n" \
+       "DHCP server\n" \
+     "\n       -f      Run in foreground" \
+     "\n       -S      Log to syslog too" \
+       USE_FEATURE_UDHCP_PORT( \
+     "\n       -P N    Use port N instead of default 67" \
+       )
+
+#define umount_trivial_usage \
+       "[flags] FILESYSTEM|DIRECTORY"
+#define umount_full_usage "\n\n" \
+       "Unmount file systems\n" \
+     "\nOptions:" \
+       USE_FEATURE_UMOUNT_ALL( \
+     "\n       -a      Unmount all file systems" USE_FEATURE_MTAB_SUPPORT(" in /etc/mtab") \
+       ) \
+       USE_FEATURE_MTAB_SUPPORT( \
+     "\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      Free loop device if it has been used" \
+       )
+
+#define umount_example_usage \
+       "$ umount /dev/hdc1\n"
+
+#define uname_trivial_usage \
+       "[-amnrspv]"
+#define uname_full_usage "\n\n" \
+       "Print system information.\n" \
+     "\nOptions:" \
+     "\n       -a      Print all" \
+     "\n       -m      The machine (hardware) type" \
+     "\n       -n      Hostname" \
+     "\n       -r      OS release" \
+     "\n       -s      OS name (default)" \
+     "\n       -p      Processor type" \
+     "\n       -v      OS 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 "\n\n" \
+       "Uncompress .Z file[s]\n" \
+     "\nOptions:" \
+     "\n       -c      Extract to stdout" \
+     "\n       -f      Overwrite an existing file" \
+
+#define unexpand_trivial_usage \
+       "[-f][-a][-t NUM] [FILE|-]"
+#define unexpand_full_usage "\n\n" \
+       "Convert spaces to tabs, writing to standard output.\n" \
+     "\nOptions:" \
+       USE_FEATURE_UNEXPAND_LONG_OPTIONS( \
+     "\n       -a,--all        Convert all blanks" \
+     "\n       -f,--first-only Convert only leading blanks" \
+     "\n       -t,--tabs=N     Tabstops every N chars" \
+       ) \
+       SKIP_FEATURE_UNEXPAND_LONG_OPTIONS( \
+     "\n       -a      Convert all blanks" \
+     "\n       -f      Convert only leading blanks" \
+     "\n       -t N    Tabstops every N chars" \
+       )
+
+#define uniq_trivial_usage \
+       "[-fscduw]... [INPUT [OUTPUT]]"
+#define uniq_full_usage "\n\n" \
+       "Discard duplicate lines\n" \
+     "\nOptions:" \
+     "\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 first N fields" \
+     "\n       -s N    Skip first N chars (after any skipped fields)" \
+     "\n       -w N    Compare N characters in line" \
+
+#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 "\n\n" \
+       "Convert FILE from unix to dos format.\n" \
+       "When no file is given, use stdin/stdout.\n" \
+     "\nOptions:" \
+     "\n       -u      dos2unix" \
+     "\n       -d      unix2dos" \
+
+#define unzip_trivial_usage \
+       "[-opts[modifiers]] file[.zip] [list] [-x xlist] [-d exdir]"
+#define unzip_full_usage "\n\n" \
+       "Extract files from ZIP archives\n" \
+     "\nOptions:" \
+     "\n       -l      List archive contents (with -q for short form)" \
+     "\n       -n      Never overwrite existing files (default)" \
+     "\n       -o      Overwrite files without prompting" \
+     "\n       -p      Send output to stdout" \
+     "\n       -q      Quiet" \
+     "\n       -x      Exclude these files" \
+     "\n       -d      Extract files into this directory" \
+
+#define uptime_trivial_usage \
+       ""
+#define uptime_full_usage "\n\n" \
+       "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 "\n\n" \
+       "Pause for N microseconds"
+
+#define usleep_example_usage \
+       "$ usleep 1000000\n" \
+       "[pauses for 1 second]\n"
+
+#define uudecode_trivial_usage \
+       "[-o outfile] [infile]"
+#define uudecode_full_usage "\n\n" \
+       "Uudecode a file\n" \
+       "Finds outfile name in uuencoded source unless -o is given"
+
+#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 \
+       "[-m] [infile] stored_filename"
+#define uuencode_full_usage "\n\n" \
+       "Uuencode a file to stdout\n" \
+     "\nOptions:" \
+     "\n       -m      Use base64 encoding per RFC1521" \
+
+#define uuencode_example_usage \
+       "$ uuencode busybox busybox\n" \
+       "begin 755 busybox\n" \
+       "<encoded file snipped>\n" \
+       "$ uudecode busybox busybox > busybox.uu\n" \
+       "$\n"
+
+#define vconfig_trivial_usage \
+       "COMMAND [OPTIONS]..."
+#define vconfig_full_usage "\n\n" \
+       "Create and remove virtual ethernet devices\n" \
+     "\nOptions:" \
+     "\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 "\n\n" \
+       "Edit FILE\n" \
+     "\nOptions:" \
+       USE_FEATURE_VI_COLON( \
+     "\n       -c      Initial command to run ($EXINIT also available)") \
+       USE_FEATURE_VI_READONLY( \
+     "\n       -R      Read-only - do not write to the file") \
+     "\n       -H      Short help regarding available features" \
+
+#define vlock_trivial_usage \
+       "[OPTIONS]"
+#define vlock_full_usage "\n\n" \
+       "Lock a virtual terminal. A password is required to unlock.\n" \
+     "\nOptions:" \
+     "\n       -a      Lock all VTs" \
+
+#define watch_trivial_usage \
+       "[-n seconds] [-t] COMMAND..."
+#define watch_full_usage "\n\n" \
+       "Execute a program periodically\n" \
+     "\nOptions:" \
+     "\n       -n      Loop period in seconds (default 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 N[ms]] [-T N[ms]] [-F] DEV"
+#define watchdog_full_usage "\n\n" \
+       "Periodically write to watchdog device DEV\n" \
+     "\nOptions:" \
+     "\n       -T N    Reboot after N seconds if not reset (default 60)" \
+     "\n       -t N    Reset every N seconds (default 30)" \
+     "\n       -F      Run in foreground" \
+     "\n" \
+     "\nUse 500ms to specify period in milliseconds" \
+
+#define wc_trivial_usage \
+       "[OPTION]... [FILE]..."
+#define wc_full_usage "\n\n" \
+       "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" \
+     "\nOptions:" \
+     "\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 \
+       USE_FEATURE_WGET_LONG_OPTIONS( \
+       "[-c|--continue] [-s|--spider] [-q|--quiet] [-O|--output-document file]\n" \
+       "       [--header 'header: value'] [-Y|--proxy on/off] [-P DIR]\n" \
+       "       [-U|--user-agent agent] url" \
+       ) \
+       SKIP_FEATURE_WGET_LONG_OPTIONS( \
+       "[-csq] [-O file] [-Y on/off] [-P DIR] [-U agent] url" \
+       )
+#define wget_full_usage "\n\n" \
+       "Retrieve files via HTTP or FTP\n" \
+     "\nOptions:" \
+     "\n       -s      Spider mode - only check file existence" \
+     "\n       -c      Continue retrieval of aborted transfer" \
+     "\n       -q      Quiet" \
+     "\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 "\n\n" \
+       "Locate a COMMAND"
+#define which_example_usage \
+       "$ which login\n" \
+       "/bin/login\n"
+
+#define who_trivial_usage \
+       "[-a]"
+#define who_full_usage "\n\n" \
+       "Show who is logged on\n" \
+     "\nOptions:" \
+     "\n       -a      show all" \
+
+#define whoami_trivial_usage \
+       ""
+#define whoami_full_usage "\n\n" \
+       "Print the user name associated with the current effective user id"
+
+#define xargs_trivial_usage \
+       "[OPTIONS] [COMMAND] [ARGS...]"
+#define xargs_full_usage "\n\n" \
+       "Execute COMMAND on every item given by standard input\n" \
+     "\nOptions:" \
+       USE_FEATURE_XARGS_SUPPORT_CONFIRMATION( \
+     "\n       -p      Ask user whether to run each command") \
+     "\n       -r      Do not run command if input is empty" \
+       USE_FEATURE_XARGS_SUPPORT_ZERO_TERM( \
+     "\n       -0      Input is separated by NUL characters") \
+     "\n       -t      Print the command on stderr before execution" \
+     "\n       -e[STR] STR stops input processing" \
+     "\n       -n N    Pass no more than N args to COMMAND" \
+     "\n       -s N    Pass command line of no more than N bytes" \
+       USE_FEATURE_XARGS_SUPPORT_TERMOPT( \
+     "\n       -x      Exit if size is exceeded") \
+
+#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 "\n\n" \
+       "Repeatedly output a line with all specified STRING(s), or 'y'"
+
+#define zcat_trivial_usage \
+       "FILE"
+#define zcat_full_usage "\n\n" \
+       "Uncompress to stdout"
+
+#define zcip_trivial_usage \
+       "[OPTIONS] ifname script"
+#define zcip_full_usage "\n\n" \
+       "Manage a ZeroConf IPv4 link-local address\n" \
+     "\nOptions:" \
+     "\n       -f              Run in foreground" \
+     "\n       -q              Quit after obtaining address" \
+     "\n       -r 169.254.x.x  Request this address first" \
+     "\n       -v              Verbose" \
+     "\n" \
+     "\nWith no -q, runs continuously monitoring for ARP conflicts," \
+     "\nexits only on I/O errors (link down etc)" \
+
+
+#endif
diff --git a/include/volume_id.h b/include/volume_id.h
new file mode 100644 (file)
index 0000000..bba32c0
--- /dev/null
@@ -0,0 +1,23 @@
+/*
+ * volume_id - reads filesystem label and uuid
+ *
+ * Copyright (C) 2005 Kay Sievers <kay.sievers@vrfy.org>
+ *
+ *     This 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.
+ *
+ *     This 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 this library; if not, write to the Free Software
+ *     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+char *get_devname_from_label(const char *spec);
+char *get_devname_from_uuid(const char *spec);
+void display_uuid_cache(void);
diff --git a/include/xatonum.h b/include/xatonum.h
new file mode 100644 (file)
index 0000000..ee816ef
--- /dev/null
@@ -0,0 +1,172 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * ascii-to-numbers implementations for busybox
+ *
+ * Copyright (C) 2003  Manuel Novoa III  <mjn3@codepoet.org>
+ *
+ * Licensed under GPLv2, see file LICENSE in this tarball for details.
+ */
+
+PUSH_AND_SET_FUNCTION_VISIBILITY_TO_HIDDEN
+
+/* 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) FAST_FUNC; \
+unsigned type xstrto##UT##_range(const char *str, int b, unsigned type l, unsigned type u) FAST_FUNC; \
+unsigned type xstrto##UT##_sfx(const char *str, int b, const struct suffix_mult *sfx) FAST_FUNC; \
+unsigned type xstrto##UT(const char *str, int b) FAST_FUNC; \
+unsigned type xato##UT##_range_sfx(const char *str, unsigned type l, unsigned type u, const struct suffix_mult *sfx) FAST_FUNC; \
+unsigned type xato##UT##_range(const char *str, unsigned type l, unsigned type u) FAST_FUNC; \
+unsigned type xato##UT##_sfx(const char *str, const struct suffix_mult *sfx) FAST_FUNC; \
+unsigned type xato##UT(const char *str) FAST_FUNC; \
+type xstrto##T##_range_sfx(const char *str, int b, type l, type u, const struct suffix_mult *sfx) FAST_FUNC; \
+type xstrto##T##_range(const char *str, int b, type l, type u) FAST_FUNC; \
+type xato##T##_range_sfx(const char *str, type l, type u, const struct suffix_mult *sfx) FAST_FUNC; \
+type xato##T##_range(const char *str, type l, type u) FAST_FUNC; \
+type xato##T##_sfx(const char *str, const struct suffix_mult *sfx) FAST_FUNC; \
+type xato##T(const char *str) FAST_FUNC; \
+
+/* Unsigned long long functions always exist */
+DECLARE_STR_CONV(long long, ll, ull)
+
+
+/* Provides 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) \
+\
+static ALWAYS_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); } \
+static ALWAYS_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); } \
+static ALWAYS_INLINE \
+unsigned narrow xstrto##UN##_sfx(const char *str, int b, const struct suffix_mult *sfx) \
+{ return xstrto##UW##_sfx(str, b, sfx); } \
+static ALWAYS_INLINE \
+unsigned narrow xstrto##UN(const char *str, int b) \
+{ return xstrto##UW(str, b); } \
+static ALWAYS_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); } \
+static ALWAYS_INLINE \
+unsigned narrow xato##UN##_range(const char *str, unsigned narrow l, unsigned narrow u) \
+{ return xato##UW##_range(str, l, u); } \
+static ALWAYS_INLINE \
+unsigned narrow xato##UN##_sfx(const char *str, const struct suffix_mult *sfx) \
+{ return xato##UW##_sfx(str, sfx); } \
+static ALWAYS_INLINE \
+unsigned narrow xato##UN(const char *str) \
+{ return xato##UW(str); } \
+static ALWAYS_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); } \
+static ALWAYS_INLINE \
+narrow xstrto##N##_range(const char *str, int b, narrow l, narrow u) \
+{ return xstrto##W##_range(str, b, l, u); } \
+static ALWAYS_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); } \
+static ALWAYS_INLINE \
+narrow xato##N##_range(const char *str, narrow l, narrow u) \
+{ return xato##W##_range(str, l, u); } \
+static ALWAYS_INLINE \
+narrow xato##N##_sfx(const char *str, const struct suffix_mult *sfx) \
+{ return xato##W##_sfx(str, sfx); } \
+static ALWAYS_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);
+static ALWAYS_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: bb_strto[u][l]l */
+
+/* On exit: errno = 0 only if there was non-empty, '\0' terminated value
+ * errno = EINVAL if value was not '\0' terminated, but otherwise 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 )
+ *    return value is all-ones in this case.
+ */
+
+unsigned long long bb_strtoull(const char *arg, char **endp, int base) FAST_FUNC;
+long long bb_strtoll(const char *arg, char **endp, int base) FAST_FUNC;
+
+#if ULONG_MAX == ULLONG_MAX
+static ALWAYS_INLINE
+unsigned long bb_strtoul(const char *arg, char **endp, int base)
+{ return bb_strtoull(arg, endp, base); }
+static ALWAYS_INLINE
+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) FAST_FUNC;
+long bb_strtol(const char *arg, char **endp, int base) FAST_FUNC;
+#endif
+
+#if UINT_MAX == ULLONG_MAX
+static ALWAYS_INLINE
+unsigned bb_strtou(const char *arg, char **endp, int base)
+{ return bb_strtoull(arg, endp, base); }
+static ALWAYS_INLINE
+int bb_strtoi(const char *arg, char **endp, int base)
+{ return bb_strtoll(arg, endp, base); }
+#elif UINT_MAX == ULONG_MAX
+static ALWAYS_INLINE
+unsigned bb_strtou(const char *arg, char **endp, int base)
+{ return bb_strtoul(arg, endp, base); }
+static ALWAYS_INLINE
+int bb_strtoi(const char *arg, char **endp, int base)
+{ return bb_strtol(arg, endp, base); }
+#else
+unsigned bb_strtou(const char *arg, char **endp, int base) FAST_FUNC;
+int bb_strtoi(const char *arg, char **endp, int base) FAST_FUNC;
+#endif
+
+int BUG_bb_strtou32_unimplemented(void);
+static ALWAYS_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) FAST_FUNC;
+
+POP_SAVED_FUNCTION_VISIBILITY
diff --git a/include/xregex.h b/include/xregex.h
new file mode 100644 (file)
index 0000000..61658ed
--- /dev/null
@@ -0,0 +1,23 @@
+/* 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_H
+#define BB_REGEX_H 1
+
+#include <regex.h>
+
+PUSH_AND_SET_FUNCTION_VISIBILITY_TO_HIDDEN
+
+char* regcomp_or_errmsg(regex_t *preg, const char *regex, int cflags) FAST_FUNC;
+void xregcomp(regex_t *preg, const char *regex, int cflags) FAST_FUNC;
+
+POP_SAVED_FUNCTION_VISIBILITY
+
+#endif
diff --git a/init/Config.in b/init/Config.in
new file mode 100644 (file)
index 0000000..1a1be4a
--- /dev/null
@@ -0,0 +1,103 @@
+#
+# 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 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_KILL_REMOVED
+       bool "Support killing processes that have been removed from inittab"
+       default y
+       depends on FEATURE_USE_INITTAB
+       help
+         When respawn entries are removed from inittab and a SIGHUP is
+         sent to init, this feature will kill the processes that have
+         been removed.
+
+config FEATURE_KILL_DELAY
+       int "How long to wait between TERM and KILL (0 - send TERM only)" if FEATURE_KILL_REMOVED
+       range 0 1024
+       default 0
+       depends on FEATURE_KILL_REMOVED
+       help
+         With nonzero setting, init sends TERM, forks, child waits N
+         seconds, sends KILL and exits. Setting it too high is unwise
+         (child will hang around for too long and could actually kill
+         the wrong process!)
+
+config FEATURE_INIT_SCTTY
+       bool "Run commands with leading dash with controlling tty"
+       default n
+       depends on INIT
+       help
+         If this option is enabled, init will try to give a controlling
+         tty to any command which has leading hyphen (often it's "-/bin/sh").
+         More precisely, init will do "ioctl(STDIN_FILENO, TIOCSCTTY, 0)".
+         If device attached to STDIN_FILENO can be a ctty but is not yet
+         a ctty for other session, it will become this process' ctty.
+         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.
+         NB: using cttyhack applet may work better.
+
+config FEATURE_INIT_SYSLOG
+       bool "Enable init to write to syslog"
+       default n
+       depends on INIT
+
+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 n
+       help
+         Stop all processes and either halt, reboot, or power off the system.
+
+config MESG
+       bool "mesg"
+       default n
+       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 (file)
index 0000000..c060f3a
--- /dev/null
@@ -0,0 +1,10 @@
+# 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_HALT)     += halt.o
+lib-$(CONFIG_INIT)     += init.o
+lib-$(CONFIG_MESG)     += mesg.o
diff --git a/init/halt.c b/init/halt.c
new file mode 100644 (file)
index 0000000..3a23eca
--- /dev/null
@@ -0,0 +1,104 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Poweroff reboot and halt, oh my.
+ *
+ * Copyright 2006 by Rob Landley <rob@landley.net>
+ *
+ * Licensed under GPL version 2, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.h"
+#include <sys/reboot.h>
+
+#if ENABLE_FEATURE_WTMP
+#include <sys/utsname.h>
+#include <utmp.h>
+
+static void write_wtmp(void)
+{
+       struct utmp utmp;
+       struct utsname uts;
+       if (access(bb_path_wtmp_file, R_OK|W_OK) == -1) {
+               close(creat(bb_path_wtmp_file, 0664));
+       }
+       memset(&utmp, 0, sizeof(utmp));
+       utmp.ut_tv.tv_sec = time(NULL);
+       safe_strncpy(utmp.ut_user, "shutdown", UT_NAMESIZE);
+       utmp.ut_type = RUN_LVL;
+       safe_strncpy(utmp.ut_id, "~~", sizeof(utmp.ut_id));
+       safe_strncpy(utmp.ut_line, "~~", UT_LINESIZE);
+       if (uname(&uts) == 0)
+               safe_strncpy(utmp.ut_host, uts.release, sizeof(utmp.ut_host));
+       updwtmp(bb_path_wtmp_file, &utmp);
+
+}
+#else
+#define write_wtmp() ((void)0)
+#endif
+
+#ifndef RB_HALT_SYSTEM
+#define RB_HALT_SYSTEM RB_HALT
+#endif
+
+#ifndef RB_POWER_OFF
+#define RB_POWER_OFF RB_POWERDOWN
+#endif
+
+int halt_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int halt_main(int argc UNUSED_PARAM, char **argv)
+{
+       static const int magic[] = {
+               RB_HALT_SYSTEM,
+               RB_POWER_OFF,
+               RB_AUTOBOOT
+       };
+       static const smallint signals[] = { SIGUSR1, SIGUSR2, SIGTERM };
+
+       int delay = 0;
+       int which, flags, rc;
+
+       /* Figure out which applet we're running */
+       for (which = 0; "hpr"[which] != applet_name[0]; which++)
+               continue;
+
+       /* Parse and handle arguments */
+       opt_complementary = "d+"; /* -d N */
+       /* We support -w even if !ENABLE_FEATURE_WTMP,
+        * in order to not break scripts.
+        * -i (shut down network interfaces) is ignored.
+        */
+       flags = getopt32(argv, "d:nfwi", &delay);
+
+       sleep(delay);
+
+       write_wtmp();
+
+       if (flags & 8) /* -w */
+               return EXIT_SUCCESS;
+
+       if (!(flags & 2)) /* no -n */
+               sync();
+
+       /* Perform action. */
+       rc = 1;
+       if (!(flags & 4)) { /* no -f */
+//TODO: I tend to think that signalling linuxrc is wrong
+// pity original author didn't comment on it...
+               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_perror_nomsg_and_die();
+       return rc;
+}
diff --git a/init/init.c b/init/init.c
new file mode 100644 (file)
index 0000000..ce26428
--- /dev/null
@@ -0,0 +1,1004 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Mini init implementation for busybox
+ *
+ * Copyright (C) 1995, 1996 by Bruce Perens <bruce@pixar.com>.
+ * Copyright (C) 1999-2004 by Erik Andersen <andersen@codepoet.org>
+ * 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 "libbb.h"
+#include <syslog.h>
+#include <paths.h>
+#include <sys/reboot.h>
+#include <sys/resource.h>
+#include <linux/vt.h>
+
+
+/* Was a CONFIG_xxx option. A lot of people were building
+ * not fully functional init by switching it on! */
+#define DEBUG_INIT 0
+
+#define COMMAND_SIZE      256
+#define CONSOLE_NAME_SIZE 32
+
+/* Default sysinit script. */
+#ifndef INIT_SCRIPT
+#define INIT_SCRIPT  "/etc/init.d/rcS"
+#endif
+
+/* Each type of actions can appear many times. They will be
+ * handled in order. RESTART is an exception, only 1st is used.
+ */
+/* Start these actions first and wait for completion */
+#define SYSINIT     0x01
+/* Start these after SYSINIT and wait for completion */
+#define WAIT        0x02
+/* Start these after WAIT and *dont* wait for completion */
+#define ONCE        0x04
+/*
+ * NB: while SYSINIT/WAIT/ONCE are being processed,
+ * SIGHUP ("reread /etc/inittab") will be ignored.
+ * Rationale: it would be ambiguous whether SYSINIT/WAIT/ONCE
+ * need to be rerun or not.
+ */
+/* Start these after ONCE are started, restart on exit */
+#define RESPAWN     0x08
+/* Like RESPAWN, but wait for <Enter> to be pressed on tty */
+#define ASKFIRST    0x10
+/*
+ * Start these on SIGINT, and wait for completion.
+ * Then go back to respawning RESPAWN and ASKFIRST actions.
+ * NB: kernel sends SIGINT to us if Ctrl-Alt-Del was pressed.
+ */
+#define CTRLALTDEL  0x20
+/*
+ * Start these before killing all processes in preparation for
+ * running RESTART actions or doing low-level halt/reboot/poweroff
+ * (initiated by SIGUSR1/SIGTERM/SIGUSR2).
+ * Wait for completion before proceeding.
+ */
+#define SHUTDOWN    0x40
+/*
+ * exec() on SIGQUIT. SHUTDOWN actions are started and waited for,
+ * then all processes are killed, then init exec's 1st RESTART action,
+ * replacing itself by it. If no RESTART action specified,
+ * SIGQUIT has no effect.
+ */
+#define RESTART     0x80
+
+
+/* A linked list of init_actions, to be read from inittab */
+struct init_action {
+       struct init_action *next;
+       pid_t pid;
+       uint8_t action_type;
+       char terminal[CONSOLE_NAME_SIZE];
+       char command[COMMAND_SIZE];
+};
+
+static struct init_action *init_action_list = NULL;
+
+static const char *log_console = VC_5;
+
+enum {
+       L_LOG = 0x1,
+       L_CONSOLE = 0x2,
+       MAYBE_CONSOLE = L_CONSOLE * !ENABLE_FEATURE_EXTRA_QUIET,
+#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
+};
+
+/* Print a message to the specified device.
+ * "where" may be bitwise-or'd from L_LOG | L_CONSOLE
+ * NB: careful, we can be called after vfork!
+ */
+#define dbg_message(...) do { if (DEBUG_INIT) message(__VA_ARGS__); } while (0)
+static void message(int where, const char *fmt, ...)
+       __attribute__ ((format(printf, 2, 3)));
+static void message(int where, const char *fmt, ...)
+{
+       va_list arguments;
+       unsigned l;
+       char msg[128];
+
+       msg[0] = '\r';
+       va_start(arguments, fmt);
+       l = 1 + vsnprintf(msg + 1, sizeof(msg) - 2, fmt, arguments);
+       if (l > sizeof(msg) - 1)
+               l = sizeof(msg) - 1;
+       va_end(arguments);
+
+#if ENABLE_FEATURE_INIT_SYSLOG
+       msg[l] = '\0';
+       if (where & L_LOG) {
+               /* Log the message to syslogd */
+               openlog("init", 0, LOG_DAEMON);
+               /* don't print "\r" */
+               syslog(LOG_INFO, "%s", msg + 1);
+               closelog();
+       }
+       msg[l++] = '\n';
+       msg[l] = '\0';
+#else
+       {
+               static int log_fd = -1;
+
+               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_console) {
+                               log_fd = STDERR_FILENO;
+                       } else {
+                               log_fd = device_open(log_console, O_WRONLY | O_NONBLOCK | O_NOCTTY);
+                               if (log_fd < 0) {
+                                       bb_error_msg("can't log to %s", log_console);
+                                       where = L_CONSOLE;
+                               } else {
+                                       close_on_exec_on(log_fd);
+                               }
+                       }
+               }
+               if (where & L_LOG) {
+                       full_write(log_fd, msg, l);
+                       if (log_fd == STDERR_FILENO)
+                               return; /* don't print dup messages */
+               }
+       }
+#endif
+
+       if (where & L_CONSOLE) {
+               /* Send console messages to console so people will see them. */
+               full_write(STDERR_FILENO, msg, l);
+       }
+}
+
+static void console_init(void)
+{
+       int vtno;
+       char *s;
+
+       s = getenv("CONSOLE");
+       if (!s)
+               s = getenv("console");
+       if (s) {
+               int fd = open(s, O_RDWR | O_NONBLOCK | O_NOCTTY);
+               if (fd >= 0) {
+                       dup2(fd, STDIN_FILENO);
+                       dup2(fd, STDOUT_FILENO);
+                       xmove_fd(fd, STDERR_FILENO);
+               }
+               dbg_message(L_LOG, "console='%s'", s);
+       } else {
+               /* Make sure fd 0,1,2 are not closed
+                * (so that they won't be used by future opens) */
+               bb_sanitize_stdio();
+// Users report problems
+//             /* Make sure init can't be blocked by writing to stderr */
+//             fcntl(STDERR_FILENO, F_SETFL, fcntl(STDERR_FILENO, F_GETFL) | O_NONBLOCK);
+       }
+
+       s = getenv("TERM");
+       if (ioctl(STDIN_FILENO, VT_OPENQRY, &vtno) != 0) {
+               /* Not a linux terminal, probably serial console.
+                * Force the TERM setting to vt102
+                * if TERM is set to linux (the default) */
+               if (!s || strcmp(s, "linux") == 0)
+                       putenv((char*)"TERM=vt102");
+               if (!ENABLE_FEATURE_INIT_SYSLOG)
+                       log_console = NULL;
+       } else if (!s)
+               putenv((char*)"TERM=linux");
+}
+
+/* Set terminal settings to reasonable defaults.
+ * NB: careful, we can be called after vfork! */
+static void set_sane_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 discipline 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_TCSANOW(&tty);
+}
+
+/* Open the new terminal device.
+ * NB: careful, we can be called after vfork! */
+static int open_stdio_to_tty(const char* tty_name)
+{
+       /* empty tty_name means "use init's tty", else... */
+       if (tty_name[0]) {
+               int fd;
+
+               close(STDIN_FILENO);
+               /* fd can be only < 0 or 0: */
+               fd = device_open(tty_name, O_RDWR);
+               if (fd) {
+                       message(L_LOG | L_CONSOLE, "can't open %s: %s",
+                               tty_name, strerror(errno));
+                       return 0; /* failure */
+               }
+               dup2(STDIN_FILENO, STDOUT_FILENO);
+               dup2(STDIN_FILENO, STDERR_FILENO);
+       }
+       set_sane_term();
+       return 1; /* success */
+}
+
+/* Wrapper around exec:
+ * Takes string (max COMMAND_SIZE chars).
+ * If chars like '>' detected, execs '[-]/bin/sh -c "exec ......."'.
+ * Otherwise splits words on whitespace, deals with leading dash,
+ * and uses plain exec().
+ * NB: careful, we can be called after vfork!
+ */
+static void init_exec(const char *command)
+{
+       char *cmd[COMMAND_SIZE / 2];
+       char buf[COMMAND_SIZE + 6];  /* COMMAND_SIZE+strlen("exec ")+1 */
+       int dash = (command[0] == '-' /* maybe? && command[1] == '/' */);
+
+       /* See if any special /bin/sh requiring characters are present */
+       if (strpbrk(command, "~`!$^&*()=|\\{}[];\"'<>?") != NULL) {
+               strcpy(buf, "exec ");
+               strcpy(buf + 5, command + dash); /* excluding "-" */
+               /* NB: LIBBB_DEFAULT_LOGIN_SHELL define has leading dash */
+               cmd[0] = (char*)(LIBBB_DEFAULT_LOGIN_SHELL + !dash);
+               cmd[1] = (char*)"-c";
+               cmd[2] = buf;
+               cmd[3] = NULL;
+       } else {
+               /* Convert command (char*) into cmd (char**, one word per string) */
+               char *word, *next;
+               int i = 0;
+               next = strcpy(buf, command); /* including "-" */
+               while ((word = strsep(&next, " \t")) != NULL) {
+                       if (*word != '\0') { /* not two spaces/tabs together? */
+                               cmd[i] = word;
+                               i++;
+                       }
+               }
+               cmd[i] = NULL;
+       }
+       /* If we saw leading "-", it is interactive shell.
+        * Try harder to give it a controlling tty.
+        * And skip "-" in actual exec call. */
+       if (dash) {
+               /* _Attempt_ to make stdin a controlling tty. */
+               if (ENABLE_FEATURE_INIT_SCTTY)
+                       ioctl(STDIN_FILENO, TIOCSCTTY, 0 /*only try, don't steal*/);
+       }
+       BB_EXECVP(cmd[0] + dash, cmd);
+       message(L_LOG | L_CONSOLE, "cannot run '%s': %s", cmd[0], strerror(errno));
+       /* returns if execvp fails */
+}
+
+/* Used only by run_actions */
+static pid_t run(const struct init_action *a)
+{
+       pid_t pid;
+
+       /* Careful: don't be affected by a signal in vforked child */
+       sigprocmask_allsigs(SIG_BLOCK);
+       if (BB_MMU && (a->action_type & ASKFIRST))
+               pid = fork();
+       else
+               pid = vfork();
+       if (pid < 0)
+               message(L_LOG | L_CONSOLE, "can't fork");
+       if (pid) {
+               sigprocmask_allsigs(SIG_UNBLOCK);
+               return pid; /* Parent or error */
+       }
+
+       /* Child */
+
+       /* Reset signal handlers that were set by the parent process */
+       bb_signals(0
+               + (1 << SIGUSR1)
+               + (1 << SIGUSR2)
+               + (1 << SIGTERM)
+               + (1 << SIGQUIT)
+               + (1 << SIGINT)
+               + (1 << SIGHUP)
+               + (1 << SIGTSTP)
+               , SIG_DFL);
+       sigprocmask_allsigs(SIG_UNBLOCK);
+
+       /* Create a new session and make ourself the process group leader */
+       setsid();
+
+       /* Open the new terminal device */
+       if (!open_stdio_to_tty(a->terminal))
+               _exit(EXIT_FAILURE);
+
+       /* NB: on NOMMU we can't wait for input in child, so
+        * "askfirst" will work the same as "respawn". */
+       if (BB_MMU && (a->action_type & ASKFIRST)) {
+               static const char press_enter[] ALIGN1 =
+#ifdef CUSTOMIZED_BANNER
+#include CUSTOMIZED_BANNER
+#endif
+                       "\nPlease press Enter to activate this console. ";
+               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.
+                */
+               dbg_message(L_LOG, "waiting for enter to start '%s'"
+                                       "(pid %d, tty '%s')\n",
+                               a->command, getpid(), a->terminal);
+               full_write(STDOUT_FILENO, press_enter, sizeof(press_enter) - 1);
+               while (safe_read(STDIN_FILENO, &c, 1) == 1 && c != '\n')
+                       continue;
+       }
+
+       /*
+        * When a file named /.init_enable_core 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....
+        */
+       if (ENABLE_FEATURE_INIT_COREDUMPS) {
+               if (access("/.init_enable_core", F_OK) == 0) {
+                       struct rlimit limit;
+                       limit.rlim_cur = RLIM_INFINITY;
+                       limit.rlim_max = RLIM_INFINITY;
+                       setrlimit(RLIMIT_CORE, &limit);
+               }
+       }
+
+       /* Log the process name and args */
+       message(L_LOG, "starting pid %d, tty '%s': '%s'",
+                         getpid(), a->terminal, a->command);
+
+       /* Now run it.  The new program will take over this PID,
+        * so nothing further in init.c should be run. */
+       init_exec(a->command);
+       /* We're still here?  Some error happened. */
+       _exit(-1);
+}
+
+static struct init_action *mark_terminated(pid_t pid)
+{
+       struct init_action *a;
+
+       if (pid > 0) {
+               for (a = init_action_list; a; a = a->next) {
+                       if (a->pid == pid) {
+                               a->pid = 0;
+                               return a;
+                       }
+               }
+       }
+       return NULL;
+}
+
+static void waitfor(pid_t pid)
+{
+       /* waitfor(run(x)): protect against failed fork inside run() */
+       if (pid <= 0)
+               return;
+
+       /* Wait for any child (prevent zombies from exiting orphaned processes)
+        * but exit the loop only when specified one has exited. */
+       while (1) {
+               pid_t wpid = wait(NULL);
+               mark_terminated(wpid);
+               /* Unsafe. SIGTSTP handler might have wait'ed it already */
+               /*if (wpid == pid) break;*/
+               /* More reliable: */
+               if (kill(pid, 0))
+                       break;
+       }
+}
+
+/* Run all commands of a particular type */
+static void run_actions(int action_type)
+{
+       struct init_action *a;
+
+       for (a = init_action_list; a; a = a->next) {
+               if (!(a->action_type & action_type))
+                       continue;
+
+               if (a->action_type & (SYSINIT | WAIT | ONCE | CTRLALTDEL | SHUTDOWN)) {
+                       pid_t pid = run(a);
+                       if (a->action_type & (SYSINIT | WAIT | CTRLALTDEL | SHUTDOWN))
+                               waitfor(pid);
+               }
+               if (a->action_type & (RESPAWN | ASKFIRST)) {
+                       /* Only run stuff with pid == 0. If pid != 0,
+                        * it is already running
+                        */
+                       if (a->pid == 0)
+                               a->pid = run(a);
+               }
+       }
+}
+
+static void new_init_action(uint8_t action_type, const char *command, const char *cons)
+{
+       struct init_action *a, **nextp;
+
+       /* Scenario:
+        * old inittab:
+        * ::shutdown:umount -a -r
+        * ::shutdown:swapoff -a
+        * new inittab:
+        * ::shutdown:swapoff -a
+        * ::shutdown:umount -a -r
+        * On reload, we must ensure entries end up in correct order.
+        * To achieve that, if we find a matching entry, we move it
+        * to the end.
+        */
+       nextp = &init_action_list;
+       while ((a = *nextp) != NULL) {
+               /* Don't enter action if it's already in the list,
+                * This prevents losing running RESPAWNs.
+                */
+               if ((strcmp(a->command, command) == 0)
+                && (strcmp(a->terminal, cons) == 0)
+               ) {
+                       /* Remove from list */
+                       *nextp = a->next;
+                       /* Find the end of the list */
+                       while (*nextp != NULL)
+                               nextp = &(*nextp)->next;
+                       a->next = NULL;
+                       break;
+               }
+               nextp = &a->next;
+       }
+
+       if (!a)
+               a = xzalloc(sizeof(*a));
+       /* Append to the end of the list */
+       *nextp = a;
+       a->action_type = action_type;
+       safe_strncpy(a->command, command, sizeof(a->command));
+       safe_strncpy(a->terminal, cons, sizeof(a->terminal));
+       dbg_message(L_LOG | L_CONSOLE, "command='%s' action=%d tty='%s'\n",
+               a->command, a->action_type, a->terminal);
+}
+
+/* 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)
+{
+#if ENABLE_FEATURE_USE_INITTAB
+       char *token[4];
+       parser_t *parser = config_open2("/etc/inittab", fopen_for_read);
+
+       if (parser == NULL)
+#endif
+       {
+               /* No inittab file - set up some default behavior */
+               /* Reboot on Ctrl-Alt-Del */
+               new_init_action(CTRLALTDEL, "reboot", "");
+               /* Umount all filesystems on halt/reboot */
+               new_init_action(SHUTDOWN, "umount -a -r", "");
+               /* Swapoff on halt/reboot */
+               if (ENABLE_SWAPONOFF)
+                       new_init_action(SHUTDOWN, "swapoff -a", "");
+               /* Prepare to restart init when a QUIT is received */
+               new_init_action(RESTART, "init", "");
+               /* Askfirst shell on tty1-4 */
+               new_init_action(ASKFIRST, bb_default_login_shell, "");
+//TODO: VC_1 instead of ""? "" is console -> ctty problems -> angry users
+               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;
+       }
+
+#if ENABLE_FEATURE_USE_INITTAB
+       /* optional_tty:ignored_runlevel:action:command
+        * Delims are not to be collapsed and need exactly 4 tokens
+        */
+       while (config_read(parser, token, 4, 0, "#:",
+                               PARSE_NORMAL & ~(PARSE_TRIM | PARSE_COLLAPSE))) {
+               /* order must correspond to SYSINIT..RESTART constants */
+               static const char actions[] ALIGN1 =
+                       "sysinit\0""wait\0""once\0""respawn\0""askfirst\0"
+                       "ctrlaltdel\0""shutdown\0""restart\0";
+               int action;
+               char *tty = token[0];
+
+               if (!token[3]) /* less than 4 tokens */
+                       goto bad_entry;
+               action = index_in_strings(actions, token[2]);
+               if (action < 0 || !token[3][0]) /* token[3]: command */
+                       goto bad_entry;
+               /* turn .*TTY -> /dev/TTY */
+               if (tty[0]) {
+                       if (strncmp(tty, "/dev/", 5) == 0)
+                               tty += 5;
+                       tty = concat_path_file("/dev/", tty);
+               }
+               new_init_action(1 << action, token[3], tty);
+               if (tty[0])
+                       free(tty);
+               continue;
+ bad_entry:
+               message(L_LOG | L_CONSOLE, "Bad inittab entry at line %d",
+                               parser->lineno);
+       }
+       config_close(parser);
+#endif
+}
+
+static void pause_and_low_level_reboot(unsigned magic) NORETURN;
+static void pause_and_low_level_reboot(unsigned magic)
+{
+       pid_t pid;
+
+       /* Allow time for last message to reach serial console, etc */
+       sleep(1);
+
+       /* We have to fork here, since the kernel calls do_exit(EXIT_SUCCESS)
+        * in linux/kernel/sys.c, which can cause the machine to panic when
+        * the init process exits... */
+       pid = vfork();
+       if (pid == 0) { /* child */
+               reboot(magic);
+               _exit(EXIT_SUCCESS);
+       }
+       while (1)
+               sleep(1);
+}
+
+static void run_shutdown_and_kill_processes(void)
+{
+       /* 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);
+
+       message(L_CONSOLE | L_LOG, "The system is going down NOW!");
+
+       /* Send signals to every process _except_ pid 1 */
+       kill(-1, SIGTERM);
+       message(L_CONSOLE | L_LOG, "Sent SIG%s to all processes", "TERM");
+       sync();
+       sleep(1);
+
+       kill(-1, SIGKILL);
+       message(L_CONSOLE, "Sent SIG%s to all processes", "KILL");
+       sync();
+       /*sleep(1); - callers take care about making a pause */
+}
+
+/* Signal handling by init:
+ *
+ * For process with PID==1, on entry kernel sets all signals to SIG_DFL
+ * and unmasks all signals. However, for process with PID==1,
+ * default action (SIG_DFL) on any signal is to ignore it,
+ * even for special signals SIGKILL and SIGCONT.
+ * Also, any signal can be caught or blocked.
+ * (but SIGSTOP is still handled specially, at least in 2.6.20)
+ *
+ * We install two kinds of handlers, "immediate" and "delayed".
+ *
+ * Immediate handlers execute at any time, even while, say, sysinit
+ * is running.
+ *
+ * Delayed handlers just set a flag variable. The variable is checked
+ * in the main loop and acted upon.
+ *
+ * halt/poweroff/reboot and restart have immediate handlers.
+ * They only traverse linked list of struct action's, never modify it,
+ * this should be safe to do even in signal handler. Also they
+ * never return.
+ *
+ * SIGSTOP and SIGTSTP have immediate handlers. They just wait
+ * for SIGCONT to happen.
+ *
+ * SIGHUP has a delayed handler, because modifying linked list
+ * of struct action's from a signal handler while it is manipulated
+ * by the program may be disastrous.
+ *
+ * Ctrl-Alt-Del has a delayed handler. Not a must, but allowing
+ * it to happen even somewhere inside "sysinit" would be a bit awkward.
+ *
+ * There is a tiny probability that SIGHUP and Ctrl-Alt-Del will collide
+ * and only one will be remembered and acted upon.
+ */
+
+static void halt_reboot_pwoff(int sig) NORETURN;
+static void halt_reboot_pwoff(int sig)
+{
+       const char *m;
+       unsigned rb;
+
+       run_shutdown_and_kill_processes();
+
+       m = "halt";
+       rb = RB_HALT_SYSTEM;
+       if (sig == SIGTERM) {
+               m = "reboot";
+               rb = RB_AUTOBOOT;
+       } else if (sig == SIGUSR2) {
+               m = "poweroff";
+               rb = RB_POWER_OFF;
+       }
+       message(L_CONSOLE, "Requesting system %s", m);
+       pause_and_low_level_reboot(rb);
+       /* not reached */
+}
+
+/* The SIGSTOP/SIGTSTP handler
+ * NB: inside it, all signals except SIGCONT are masked
+ * via appropriate setup in sigaction().
+ */
+static void stop_handler(int sig UNUSED_PARAM)
+{
+       smallint saved_bb_got_signal;
+       int saved_errno;
+
+       saved_bb_got_signal = bb_got_signal;
+       saved_errno = errno;
+       signal(SIGCONT, record_signo);
+
+       while (1) {
+               pid_t wpid;
+
+               if (bb_got_signal == SIGCONT)
+                       break;
+               /* NB: this can accidentally wait() for a process
+                * which we waitfor() elsewhere! waitfor() must have
+                * code which is resilient against this.
+                */
+               wpid = wait_any_nohang(NULL);
+               mark_terminated(wpid);
+               sleep(1);
+       }
+
+       signal(SIGCONT, SIG_DFL);
+       errno = saved_errno;
+       bb_got_signal = saved_bb_got_signal;
+}
+
+/* Handler for QUIT - exec "restart" action,
+ * else (no such action defined) do nothing */
+static void restart_handler(int sig UNUSED_PARAM)
+{
+       struct init_action *a;
+
+       for (a = init_action_list; a; a = a->next) {
+               if (!(a->action_type & RESTART))
+                       continue;
+
+               /* Starting from here, we won't return.
+                * Thus don't need to worry about preserving errno
+                * and such.
+                */
+               run_shutdown_and_kill_processes();
+
+               /* Allow Ctrl-Alt-Del to reboot the system.
+                * This is how kernel sets it up for init, we follow suit.
+                */
+               reboot(RB_ENABLE_CAD); /* misnomer */
+
+               if (open_stdio_to_tty(a->terminal)) {
+                       dbg_message(L_CONSOLE, "Trying to re-exec %s", a->command);
+                       /* Theoretically should be safe.
+                        * But in practice, kernel bugs may leave
+                        * unkillable processes, and wait() may block forever.
+                        * Oh well. Hoping "new" init won't be too surprised
+                        * by having children it didn't create.
+                        */
+                       //while (wait(NULL) > 0)
+                       //      continue;
+                       init_exec(a->command);
+               }
+               /* Open or exec failed */
+               pause_and_low_level_reboot(RB_HALT_SYSTEM);
+               /* not reached */
+       }
+}
+
+#if ENABLE_FEATURE_USE_INITTAB
+static void reload_inittab(void)
+{
+       struct init_action *a, **nextp;
+
+       message(L_LOG, "reloading /etc/inittab");
+
+       /* Disable old entries */
+       for (a = init_action_list; a; a = a->next)
+               a->action_type = ONCE;
+
+       /* Append new entries, or modify existing entries
+        * (set a->action_type) if cmd and device name
+        * match new ones. End result: only entries with
+        * a->action_type == ONCE are stale.
+        */
+       parse_inittab();
+
+#if ENABLE_FEATURE_KILL_REMOVED
+       /* Kill stale entries */
+       /* Be nice and send SIGTERM first */
+       for (a = init_action_list; a; a = a->next)
+               if (a->action_type == ONCE && a->pid != 0)
+                       kill(a->pid, SIGTERM);
+       if (CONFIG_FEATURE_KILL_DELAY) {
+               /* NB: parent will wait in NOMMU case */
+               if ((BB_MMU ? fork() : vfork()) == 0) { /* child */
+                       sleep(CONFIG_FEATURE_KILL_DELAY);
+                       for (a = init_action_list; a; a = a->next)
+                               if (a->action_type == ONCE && a->pid != 0)
+                                       kill(a->pid, SIGKILL);
+                       _exit(EXIT_SUCCESS);
+               }
+       }
+#endif
+
+       /* Remove stale (ONCE) and not useful (SYSINIT,WAIT) entries */
+       nextp = &init_action_list;
+       while ((a = *nextp) != NULL) {
+               if (a->action_type & (ONCE | SYSINIT | WAIT)) {
+                       *nextp = a->next;
+                       free(a);
+               } else {
+                       nextp = &a->next;
+               }
+       }
+
+       /* Not needed: */
+       /* run_actions(RESPAWN | ASKFIRST); */
+       /* - we return to main loop, which does this automagically */
+}
+#endif
+
+static int check_delayed_sigs(void)
+{
+       int sigs_seen = 0;
+
+       while (1) {
+               smallint sig = bb_got_signal;
+
+               if (!sig)
+                       return sigs_seen;
+               bb_got_signal = 0;
+               sigs_seen = 1;
+#if ENABLE_FEATURE_USE_INITTAB
+               if (sig == SIGHUP)
+                       reload_inittab();
+#endif
+               if (sig == SIGINT)
+                       run_actions(CTRLALTDEL);
+       }
+}
+
+int init_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int init_main(int argc UNUSED_PARAM, char **argv)
+{
+       die_sleep = 30 * 24*60*60; /* if xmalloc would ever die... */
+
+       if (argv[1] && !strcmp(argv[1], "-q")) {
+               return kill(1, SIGHUP);
+       }
+
+       if (!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();
+               }
+               /* Turn off rebooting via CTL-ALT-DEL - we get a
+                * SIGINT on CAD so we can shut things down gracefully... */
+               reboot(RB_DISABLE_CAD); /* misnomer */
+       }
+
+       /* Figure out where the default console should be */
+       console_init();
+       set_sane_term();
+       xchdir("/");
+       setsid();
+
+       /* Make sure environs is set to something sane */
+       putenv((char *) "HOME=/");
+       putenv((char *) bb_PATH_root_path);
+       putenv((char *) "SHELL=/bin/sh");
+       putenv((char *) "USER=root"); /* needed? why? */
+
+       if (argv[1])
+               xsetenv("RUNLEVEL", argv[1]);
+
+       /* Hello world */
+       message(MAYBE_CONSOLE | L_LOG, "init started: %s", bb_banner);
+
+       /* Make sure there is enough memory to do something useful. */
+       if (ENABLE_SWAPONOFF) {
+               struct sysinfo info;
+
+               if (sysinfo(&info) == 0
+                && (info.mem_unit ? : 1) * (long long)info.totalram < 1024*1024
+               ) {
+                       message(L_CONSOLE, "Low memory, forcing swapon");
+                       /* swapon -a requires /proc typically */
+                       new_init_action(SYSINIT, "mount -t proc proc /proc", "");
+                       /* Try to turn on swap */
+                       new_init_action(SYSINIT, "swapon -a", "");
+                       run_actions(SYSINIT);   /* wait and removing */
+               }
+       }
+
+       /* Check if we are supposed to be in single user mode */
+       if (argv[1]
+        && (!strcmp(argv[1], "single") || !strcmp(argv[1], "-s") || LONE_CHAR(argv[1], '1'))
+       ) {
+               /* ??? shouldn't we set RUNLEVEL="b" here? */
+               /* 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., INIT_SCRIPT and a pair
+                * of "askfirst" shells */
+               parse_inittab();
+       }
+
+#if ENABLE_SELINUX
+       if (getenv("SELINUX_INIT") == NULL) {
+               int enforce = 0;
+
+               putenv((char*)"SELINUX_INIT=YES");
+               if (selinux_init_load_policy(&enforce) == 0) {
+                       BB_EXECVP(argv[0], argv);
+               } else if (enforce > 0) {
+                       /* SELinux in enforcing mode but load_policy failed */
+                       message(L_CONSOLE, "cannot load SELinux Policy. "
+                               "Machine is in enforcing mode. Halting now.");
+                       exit(EXIT_FAILURE);
+               }
+       }
+#endif
+
+       /* Make the command line just say "init"  - thats all, nothing else */
+       strncpy(argv[0], "init", strlen(argv[0]));
+       /* Wipe argv[1]-argv[N] so they don't clutter the ps listing */
+       while (*++argv)
+               memset(*argv, 0, strlen(*argv));
+
+       /* Set up signal handlers */
+       if (!DEBUG_INIT) {
+               struct sigaction sa;
+
+               bb_signals(0
+                       + (1 << SIGUSR1) /* halt */
+                       + (1 << SIGTERM) /* reboot */
+                       + (1 << SIGUSR2) /* poweroff */
+                       , halt_reboot_pwoff);
+               signal(SIGQUIT, restart_handler); /* re-exec another init */
+
+               /* Stop handler must allow only SIGCONT inside itself */
+               memset(&sa, 0, sizeof(sa));
+               sigfillset(&sa.sa_mask);
+               sigdelset(&sa.sa_mask, SIGCONT);
+               sa.sa_handler = stop_handler;
+               /* NB: sa_flags doesn't have SA_RESTART.
+                * It must be able to interrupt wait().
+                */
+               sigaction_set(SIGTSTP, &sa); /* pause */
+               /* Does not work as intended, at least in 2.6.20.
+                * SIGSTOP is simply ignored by init:
+                */
+               sigaction_set(SIGSTOP, &sa); /* pause */
+
+               /* SIGINT (Ctrl-Alt-Del) must interrupt wait(),
+                * setting handler without SA_RESTART flag.
+                */
+               bb_signals_recursive_norestart((1 << SIGINT), record_signo);
+       }
+
+       /* Now run everything that needs to be run */
+       /* First run the sysinit command */
+       run_actions(SYSINIT);
+       check_delayed_sigs();
+       /* Next run anything that wants to block */
+       run_actions(WAIT);
+       check_delayed_sigs();
+       /* Next run anything to be run only once */
+       run_actions(ONCE);
+
+       /* Set up "reread /etc/inittab" handler.
+        * Handler is set up without SA_RESTART, it will interrupt syscalls.
+        */
+       if (!DEBUG_INIT && ENABLE_FEATURE_USE_INITTAB)
+               bb_signals_recursive_norestart((1 << SIGHUP), record_signo);
+
+       /* Now run the looping stuff for the rest of forever.
+        * NB: if delayed signal happened, avoid blocking in wait().
+        */
+       while (1) {
+               int maybe_WNOHANG;
+
+               maybe_WNOHANG = check_delayed_sigs();
+
+               /* (Re)run the respawn/askfirst stuff */
+               run_actions(RESPAWN | ASKFIRST);
+               maybe_WNOHANG |= check_delayed_sigs();
+
+               /* Don't consume all CPU time - sleep a bit */
+               sleep(1);
+               maybe_WNOHANG |= check_delayed_sigs();
+
+               /* Wait for any child process(es) to exit.
+                * NB: "delayed" signals will also interrupt this wait(),
+                * bb_signals_recursive_norestart() set them up for that.
+                * This guarantees we won't be stuck here
+                * till next orphan dies.
+                */
+               if (maybe_WNOHANG)
+                       maybe_WNOHANG = WNOHANG;
+               while (1) {
+                       pid_t wpid;
+                       struct init_action *a;
+
+                       wpid = waitpid(-1, NULL, maybe_WNOHANG);
+                       if (wpid <= 0)
+                               break;
+
+                       a = mark_terminated(wpid);
+                       if (a) {
+                               message(L_LOG, "process '%s' (pid %d) exited. "
+                                               "Scheduling for restart.",
+                                               a->command, wpid);
+                       }
+                       /* See if anyone else is waiting to be reaped */
+                       maybe_WNOHANG = WNOHANG;
+               }
+       } /* while (1) */
+}
diff --git a/init/mesg.c b/init/mesg.c
new file mode 100644 (file)
index 0000000..ca230f3
--- /dev/null
@@ -0,0 +1,46 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * mesg implementation for busybox
+ *
+ * Copyright (c) 2002 Manuel Novoa III  <mjn3@codepoet.org>
+ *
+ * Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
+ */
+
+#include "libbb.h"
+
+#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) MAIN_EXTERNALLY_VISIBLE;
+int mesg_main(int argc, char **argv)
+{
+       struct stat sb;
+       const char *tty;
+       char c = 0;
+
+       if (--argc == 0
+        || (argc == 1 && ((c = **++argv) == 'y' || c == 'n'))
+       ) {
+               tty = xmalloc_ttyname(STDERR_FILENO);
+               if (tty == NULL) {
+                       tty = "ttyname";
+               } else if (stat(tty, &sb) == 0) {
+                       mode_t m;
+                       if (argc == 0) {
+                               puts((sb.st_mode & (S_IWGRP|S_IWOTH)) ? "is y" : "is n");
+                               return EXIT_SUCCESS;
+                       }
+                       m = (c == 'y') ? sb.st_mode | S_IWGRP_OR_S_IWOTH
+                                      : sb.st_mode & ~(S_IWGRP|S_IWOTH);
+                       if (chmod(tty, m) == 0) {
+                               return EXIT_SUCCESS;
+                       }
+               }
+               bb_simple_perror_msg_and_die(tty);
+       }
+       bb_show_usage();
+}
diff --git a/libbb/Config.in b/libbb/Config.in
new file mode 100644 (file)
index 0000000..f5b804f
--- /dev/null
@@ -0,0 +1,154 @@
+#
+# 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
+
+config FEATURE_FAST_TOP
+       bool "Faster /proc scanning code (+100 bytes)"
+       default n
+       help
+         This option makes top (and ps) ~20% faster (or 20% less CPU hungry),
+         but code size is slightly bigger.
+
+config FEATURE_ETC_NETWORKS
+       bool "Support for /etc/networks"
+       default n
+       help
+         Enable support for network names in /etc/networks. This is
+         a rarely used feature which allows you to use names
+         instead of IP/mask pairs in route command.
+
+config FEATURE_EDITING
+       bool "Command line editing"
+       default n
+       help
+         Enable line editing (mainly for shell command line).
+
+config FEATURE_EDITING_MAX_LEN
+       int "Maximum length of input"
+       range 128 8192
+       default 1024
+       depends on FEATURE_EDITING
+       help
+         Line editing code uses on-stack buffers for storage.
+         You may want to decrease this parameter if your target machine
+         benefits from smaller stack usage.
+
+config FEATURE_EDITING_VI
+       bool "vi-style line editing commands"
+       default n
+       depends on FEATURE_EDITING
+       help
+         Enable vi-style line editing. In shells, this mode can be
+         turned on and off with "set -o vi" and "set +o vi".
+
+config FEATURE_EDITING_HISTORY
+       int "History size"
+       range 0 99999
+       default 15
+       depends on FEATURE_EDITING
+       help
+         Specify command history size.
+
+config FEATURE_EDITING_SAVEHISTORY
+       bool "History saving"
+       default n
+       depends on ASH && FEATURE_EDITING
+       help
+         Enable history saving in ash shell.
+
+config FEATURE_TAB_COMPLETION
+       bool "Tab completion"
+       default n
+       depends on FEATURE_EDITING
+       help
+         Enable tab completion.
+
+config FEATURE_USERNAME_COMPLETION
+       bool "Username completion"
+       default n
+       depends on FEATURE_TAB_COMPLETION
+       help
+         Enable username completion.
+
+config FEATURE_EDITING_FANCY_PROMPT
+       bool "Fancy shell prompts"
+       default n
+       depends on FEATURE_EDITING
+       help
+         Setting this option allows for prompts to use things like \w and
+         \$ and escape codes.
+
+config FEATURE_VERBOSE_CP_MESSAGE
+       bool "Give more precise messages when copy fails (cp, mv etc)"
+       default n
+       help
+         Error messages with this feature enabled:
+           $ cp file /does_not_exist/file
+           cp: cannot create '/does_not_exist/file': Path does not exist
+           $ cp file /vmlinuz/file
+           cp: cannot stat '/vmlinuz/file': Path has non-directory component
+         If this feature is not enabled, they will be, respectively:
+           cp: cannot remove '/does_not_exist/file': No such file or directory
+           cp: cannot stat '/vmlinuz/file': Not a directory
+         respectively.
+         This will cost you ~60 bytes.
+
+config FEATURE_COPYBUF_KB
+       int "Copy buffer size, in kilobytes"
+       range 1 1024
+       default 4
+       help
+         Size of buffer used by cp, mv, install etc.
+         Buffers which are 4 kb or less will be allocated on stack.
+         Bigger buffers will be allocated with mmap, with fallback to 4 kb
+         stack buffer if mmap fails.
+
+config MONOTONIC_SYSCALL
+       bool "Use clock_gettime(CLOCK_MONOTONIC) syscall"
+       default y
+       help
+         Use clock_gettime(CLOCK_MONOTONIC) syscall for measuring
+         time intervals (time, ping, traceroute etc need this).
+         Probably requires Linux 2.6+. If not selected, gettimeofday
+         will be used instead (which gives wrong results if date/time
+         is reset).
+
+config IOCTL_HEX2STR_ERROR
+       bool "Use ioctl names rather than hex values in error messages"
+       default y
+       help
+         Use ioctl names rather than hex values in error messages
+         (e.g. VT_DISALLOCATE rather than 0x5608). If disabled this
+         saves about 1400 bytes.
+
+config FEATURE_HWIB
+       bool "Support infiniband HW"
+       default y
+       help
+         Support for printing infiniband addresses in
+         network applets.
+endmenu
diff --git a/libbb/Kbuild b/libbb/Kbuild
new file mode 100644 (file)
index 0000000..8fddabd
--- /dev/null
@@ -0,0 +1,155 @@
+# 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-y += appletlib.o
+lib-y += ask_confirmation.o
+lib-y += bb_askpass.o
+lib-y += bb_basename.o
+lib-y += bb_do_delay.o
+lib-y += bb_pwd.o
+lib-y += bb_qsort.o
+lib-y += bb_strtod.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 += getpty.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 += lineedit.o lineedit_ptr_hack.o
+lib-y += llist.o
+lib-y += login.o
+lib-y += make_directory.o
+lib-y += makedev.o
+lib-y += match_fstype.o
+lib-y += md5.o
+# Alternative (disabled) implementation
+#lib-y += md5prime.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 += parse_config.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 += pidfile.o
+lib-y += printable.o
+lib-y += print_flags.o
+lib-y += process_escape_sequence.o
+lib-y += procps.o
+lib-y += ptr_to_globals.o
+lib-y += read.o
+lib-y += read_key.o
+lib-y += recursive_action.o
+lib-y += remove_file.o
+lib-y += restricted_shell.o
+lib-y += run_shell.o
+lib-y += safe_gethostname.o
+lib-y += safe_poll.o
+lib-y += safe_strncpy.o
+lib-y += safe_write.o
+lib-y += setup_environment.o
+lib-y += sha1.o
+lib-y += signals.o
+lib-y += simplify_path.o
+lib-y += skip_whitespace.o
+lib-y += speed_table.o
+lib-y += str_tolower.o
+lib-y += strrstr.o
+lib-y += time.o
+lib-y += trim.o
+lib-y += u_signal_names.o
+lib-y += udp_io.o
+lib-y += uuencode.o
+lib-y += vdprintf.o
+lib-y += verror_msg.o
+lib-y += vfork_daemon_rexec.o
+lib-y += warn_ignoring_args.o
+lib-y += wfopen.o
+lib-y += wfopen_input.o
+lib-y += write.o
+lib-y += xatonum.o
+lib-y += xconnect.o
+lib-y += xfuncs.o
+lib-y += xfuncs_printf.o
+lib-y += xfunc_die.o
+lib-y += xgetcwd.o
+lib-y += xgethostbyname.o
+lib-y += xreadlink.o
+lib-y += xrealloc_vector.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_ADDGROUP) += update_passwd.o
+lib-$(CONFIG_ADDUSER) += update_passwd.o
+lib-$(CONFIG_DELGROUP) += update_passwd.o
+lib-$(CONFIG_DELUSER) += update_passwd.o
+lib-$(CONFIG_PASSWD) += pw_encrypt.o update_passwd.o
+lib-$(CONFIG_CHPASSWD) += pw_encrypt.o update_passwd.o
+lib-$(CONFIG_CRYPTPW) += pw_encrypt.o
+lib-$(CONFIG_SULOGIN) += pw_encrypt.o
+lib-$(CONFIG_FEATURE_HTTPD_AUTH_MD5) += pw_encrypt.o
+lib-$(CONFIG_VLOCK) += pw_encrypt.o correct_password.o
+lib-$(CONFIG_SU) += pw_encrypt.o correct_password.o
+lib-$(CONFIG_LOGIN) += pw_encrypt.o correct_password.o
+lib-$(CONFIG_DF) += find_mount_point.o
+lib-$(CONFIG_MKFS_MINIX) += find_mount_point.o
+lib-$(CONFIG_SELINUX) += selinux_common.o
+lib-$(CONFIG_HWCLOCK) += rtc.o
+lib-$(CONFIG_RTCWAKE) += rtc.o
+lib-$(CONFIG_FEATURE_CHECK_NAMES) += die_if_bad_username.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_GREP) += xregcomp.o
+lib-$(CONFIG_EXPR) += xregcomp.o
+lib-$(CONFIG_MDEV) += xregcomp.o
+lib-$(CONFIG_LESS) += xregcomp.o
+lib-$(CONFIG_PGREP) += xregcomp.o
+lib-$(CONFIG_PKILL) += xregcomp.o
+lib-$(CONFIG_DEVFSD) += xregcomp.o
+lib-$(CONFIG_FEATURE_FIND_REGEX) += xregcomp.o
diff --git a/libbb/README b/libbb/README
new file mode 100644 (file)
index 0000000..4f28f7e
--- /dev/null
@@ -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
+       <andersen@codepoet.org>
+
diff --git a/libbb/appletlib.c b/libbb/appletlib.c
new file mode 100644 (file)
index 0000000..80380ae
--- /dev/null
@@ -0,0 +1,783 @@
+/* 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.
+ */
+
+/* We are trying to not use printf, this benefits the case when selected
+ * applets are really simple. Example:
+ *
+ * $ ./busybox
+ * ...
+ * Currently defined functions:
+ *         basename, false, true
+ *
+ * $ size busybox
+ *    text    data     bss     dec     hex filename
+ *    4473      52      72    4597    11f5 busybox
+ *
+ * FEATURE_INSTALLER or FEATURE_SUID will still link printf routines in. :(
+ */
+
+#include <assert.h>
+#include "busybox.h"
+
+
+/* Declare <applet>_main() */
+#define PROTOTYPES
+#include "applets.h"
+#undef PROTOTYPES
+
+#if ENABLE_SHOW_USAGE && !ENABLE_FEATURE_COMPRESS_USAGE
+/* Define usage_messages[] */
+static const char usage_messages[] ALIGN1 = ""
+#define MAKE_USAGE
+#include "usage.h"
+#include "applets.h"
+;
+#undef MAKE_USAGE
+#else
+#define usage_messages 0
+#endif /* SHOW_USAGE */
+
+
+/* Include generated applet names, pointers to <applet>_main, etc */
+#include "applet_tables.h"
+/* ...and if applet_tables generator says we have only one applet... */
+#ifdef SINGLE_APPLET_MAIN
+#undef ENABLE_FEATURE_INDIVIDUAL
+#define ENABLE_FEATURE_INDIVIDUAL 1
+#undef USE_FEATURE_INDIVIDUAL
+#define USE_FEATURE_INDIVIDUAL(...) __VA_ARGS__
+#endif
+
+
+#if ENABLE_FEATURE_COMPRESS_USAGE
+
+#include "usage_compressed.h"
+#include "unarchive.h"
+
+static const char *unpack_usage_messages(void)
+{
+       char *outbuf = NULL;
+       bunzip_data *bd;
+       int i;
+
+       i = start_bunzip(&bd,
+                       /* src_fd: */ -1,
+//FIXME: can avoid storing these 2 bytes!
+                       /* inbuf:  */ (void *)packed_usage + 2,
+                       /* len:    */ sizeof(packed_usage));
+       /* read_bunzip can longjmp to start_bunzip, and ultimately
+        * end up here with i != 0 on read data errors! Not trivial */
+       if (!i) {
+               /* Cannot use xmalloc: will leak bd in NOFORK case! */
+               outbuf = malloc_or_warn(SIZEOF_usage_messages);
+               if (outbuf)
+                       read_bunzip(bd, outbuf, SIZEOF_usage_messages);
+       }
+       dealloc_bunzip(bd);
+       return outbuf;
+}
+#define dealloc_usage_messages(s) free(s)
+
+#else
+
+#define unpack_usage_messages() usage_messages
+#define dealloc_usage_messages(s) ((void)(s))
+
+#endif /* FEATURE_COMPRESS_USAGE */
+
+
+static void full_write2_str(const char *str)
+{
+       xwrite_str(STDERR_FILENO, str);
+}
+
+void FAST_FUNC bb_show_usage(void)
+{
+       if (ENABLE_SHOW_USAGE) {
+#ifdef SINGLE_APPLET_STR
+               /* Imagine that this applet is "true". Dont suck in printf! */
+               const char *p;
+               const char *usage_string = p = unpack_usage_messages();
+
+               if (*p == '\b') {
+                       full_write2_str("No help available.\n\n");
+               } else {
+                       full_write2_str("Usage: "SINGLE_APPLET_STR" ");
+                       full_write2_str(p);
+                       full_write2_str("\n\n");
+               }
+               dealloc_usage_messages((char*)usage_string);
+#else
+               const char *p;
+               const char *usage_string = p = unpack_usage_messages();
+               int ap = find_applet_by_name(applet_name);
+
+               if (ap < 0) /* never happens, paranoia */
+                       xfunc_die();
+               while (ap) {
+                       while (*p++) continue;
+                       ap--;
+               }
+               full_write2_str(bb_banner);
+               full_write2_str(" multi-call binary\n");
+               if (*p == '\b')
+                       full_write2_str("\nNo help available.\n\n");
+               else {
+                       full_write2_str("\nUsage: ");
+                       full_write2_str(applet_name);
+                       full_write2_str(" ");
+                       full_write2_str(p);
+                       full_write2_str("\n\n");
+               }
+               dealloc_usage_messages((char*)usage_string);
+#endif
+       }
+       xfunc_die();
+}
+
+#if NUM_APPLETS > 8
+/* NB: any char pointer will work as well, not necessarily applet_names */
+static int applet_name_compare(const void *name, const void *v)
+{
+       int i = (const char *)v - applet_names;
+       return strcmp(name, APPLET_NAME(i));
+}
+#endif
+int FAST_FUNC find_applet_by_name(const char *name)
+{
+#if NUM_APPLETS > 8
+       /* Do a binary search to find the applet entry given the name. */
+       const char *p;
+       p = bsearch(name, applet_names, ARRAY_SIZE(applet_main), 1, applet_name_compare);
+       if (!p)
+               return -1;
+       return p - applet_names;
+#else
+       /* A version which does not pull in bsearch */
+       int i = 0;
+       const char *p = applet_names;
+       while (i < NUM_APPLETS) {
+               if (strcmp(name, p) == 0)
+                       return i;
+               p += strlen(p) + 1;
+               i++;
+       }
+       return -1;
+#endif
+}
+
+
+void lbb_prepare(const char *applet
+               USE_FEATURE_INDIVIDUAL(, char **argv))
+                               MAIN_EXTERNALLY_VISIBLE;
+void lbb_prepare(const char *applet
+               USE_FEATURE_INDIVIDUAL(, char **argv))
+{
+#ifdef __GLIBC__
+       (*(int **)&bb_errno) = __errno_location();
+       barrier();
+#endif
+       applet_name = applet;
+
+       /* Set locale for everybody except 'init' */
+       if (ENABLE_LOCALE_SUPPORT && getpid() != 1)
+               setlocale(LC_ALL, "");
+
+#if ENABLE_FEATURE_INDIVIDUAL
+       /* Redundant for busybox (run_applet_and_exit covers that case)
+        * but needed for "individual applet" mode */
+       if (argv[1] && !argv[2] && strcmp(argv[1], "--help") == 0) {
+               /* Special case. POSIX says "test --help"
+                * should be no different from e.g. "test --foo".  */
+               if (!ENABLE_TEST || strcmp(applet_name, "test") != 0)
+                       bb_show_usage();
+       }
+#endif
+}
+
+/* The code below can well be in applets/applets.c, as it is used only
+ * for busybox binary, not "individual" binaries.
+ * However, keeping it here and linking it into libbusybox.so
+ * (together with remaining tiny applets/applets.o)
+ * makes it possible to avoid --whole-archive at link time.
+ * This makes (shared busybox) + libbusybox smaller.
+ * (--gc-sections would be even better....)
+ */
+
+const char *applet_name;
+#if !BB_MMU
+bool re_execed;
+#endif
+
+
+/* If not built as a single-applet executable... */
+#if !defined(SINGLE_APPLET_MAIN)
+
+USE_FEATURE_SUID(static uid_t ruid;)  /* real uid */
+
+#if ENABLE_FEATURE_SUID_CONFIG
+
+/* applets[] is const, so we have to define this "override" structure */
+static struct BB_suid_config {
+       int m_applet;
+       uid_t m_uid;
+       gid_t m_gid;
+       mode_t m_mode;
+       struct BB_suid_config *m_next;
+} *suid_config;
+
+static bool 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);
+}
+
+/* Don't depend on the tools to combine strings. */
+static const char config_file[] ALIGN1 = "/etc/busybox.conf";
+
+/* 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[] ALIGN2 = {
+       /*  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 */
+};
+
+#define parse_error(x)  do { errmsg = x; goto pe_label; } while (0)
+
+static void parse_config_file(void)
+{
+       struct BB_suid_config *sct_head;
+       struct BB_suid_config *sct;
+       int applet_no;
+       FILE *f;
+       const char *errmsg;
+       char *s;
+       char *e;
+       int i;
+       unsigned lc;
+       smallint section;
+       char buffer[256];
+       struct stat st;
+
+       assert(!suid_config); /* Should be set to NULL by bss init. */
+
+       ruid = getuid();
+       if (ruid == 0) /* run by root - don't need to even read config file */
+               return;
+
+       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_for_read(config_file))      /* Cannot open? */
+       ) {
+               return;
+       }
+
+       suid_cfg_readable = 1;
+       sct_head = NULL;
+       section = lc = 0;
+
+       while (1) {
+               s = buffer;
+
+               if (!fgets(s, sizeof(buffer), f)) { /* Are we done? */
+// why?
+                       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. */
+               s = get_trimmed_slice(s, strchrnul(s, '#'));
+               if (!*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. */
+                       e = strchr(s, ']');
+                       if (!e   /* 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
+                        *    <key>[::space::]*=[::space::]*<value>
+                        * where both key and value could contain inner whitespace. */
+
+                       /* First get the key (an applet name in our case). */
+                       e = strchr(s, '=');
+                       if (e) {
+                               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. */
+                       applet_no = find_applet_by_name(s);
+                       if (applet_no >= 0) {
+                               /* 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_no;
+                               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++) {
+                                       /* There are 4 chars + 1 nul for each of user/group/other. */
+                                       static const char mode_chars[] ALIGN1 = "Ssx-\0" "Ssx-\0" "Ttx-";
+
+                                       const char *q;
+                                       q = strchrnul(mode_chars + 5*i, *e++);
+                                       if (!*q) {
+                                               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("<uid>.<gid>");
+                               }
+                               *e++ = '\0';
+
+                               /* We can't use get_ug_id here since it would exit()
+                                * if a uid or gid was not found.  Oh well... */
+                               sct->m_uid = bb_strtoul(s, NULL, 10);
+                               if (errno) {
+                                       struct passwd *pwd = getpwnam(s);
+                                       if (!pwd) {
+                                               parse_error("user");
+                                       }
+                                       sct->m_uid = pwd->pw_uid;
+                               }
+
+                               sct->m_gid = bb_strtoul(e, NULL, 10);
+                               if (errno) {
+                                       struct group *grp;
+                                       grp = getgrnam(e);
+                                       if (!grp) {
+                                               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, errmsg);
+
+       fclose(f);
+       /* Release any allocated memory before returning. */
+       while (sct_head) {
+               sct = sct_head->m_next;
+               free(sct_head);
+               sct_head = sct;
+       }
+}
+#else
+static inline void parse_config_file(void)
+{
+       USE_FEATURE_SUID(ruid = getuid();)
+}
+#endif /* FEATURE_SUID_CONFIG */
+
+
+#if ENABLE_FEATURE_SUID
+static void check_suid(int applet_no)
+{
+       gid_t rgid;  /* real gid */
+
+       if (ruid == 0) /* set by parse_config_file() */
+               return; /* run by root - no need to check more */
+       rgid = getgid();
+
+#if ENABLE_FEATURE_SUID_CONFIG
+       if (suid_cfg_readable) {
+               uid_t uid;
+               struct BB_suid_config *sct;
+               mode_t m;
+
+               for (sct = suid_config; sct; sct = sct->m_next) {
+                       if (sct->m_applet == applet_no)
+                               goto found;
+               }
+               goto check_need_suid;
+ found:
+               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!");
+
+               /* _both_ sgid and group_exec have to be set for setegid */
+               if ((sct->m_mode & (S_ISGID | S_IXGRP)) == (S_ISGID | S_IXGRP))
+                       rgid = sct->m_gid;
+               /* else (no setegid) we will set egid = rgid */
+
+               /* We set effective AND saved ids. If saved-id is not set
+                * like we do below, seteiud(0) can still later succeed! */
+               if (setresgid(-1, rgid, rgid))
+                       bb_perror_msg_and_die("setresgid");
+
+               /* do we have to set effective uid? */
+               uid = ruid;
+               if (sct->m_mode & S_ISUID)
+                       uid = sct->m_uid;
+               /* else (no seteuid) we will set euid = ruid */
+
+               if (setresuid(-1, uid, uid))
+                       bb_perror_msg_and_die("setresuid");
+               return;
+       }
+#if !ENABLE_FEATURE_SUID_CONFIG_QUIET
+       {
+               static bool onetime = 0;
+
+               if (!onetime) {
+                       onetime = 1;
+                       fprintf(stderr, "Using fallback suid method\n");
+               }
+       }
+#endif
+ check_need_suid:
+#endif
+       if (APPLET_SUID(applet_no) == _BB_SUID_ALWAYS) {
+               /* Real uid is not 0. If euid isn't 0 too, suid bit
+                * is most probably not set on our executable */
+               if (geteuid())
+                       bb_error_msg_and_die("must be suid to work properly");
+       } else if (APPLET_SUID(applet_no) == _BB_SUID_NEVER) {
+               xsetgid(rgid);  /* drop all privileges */
+               xsetuid(ruid);
+       }
+}
+#else
+#define check_suid(x) ((void)0)
+#endif /* FEATURE_SUID */
+
+
+#if ENABLE_FEATURE_INSTALLER
+/* create (sym)links for each applet */
+static void install_links(const char *busybox, int use_symbolic_links)
+{
+       /* directory table
+        * this should be consistent w/ the enum,
+        * busybox.h::bb_install_loc_t, or else... */
+       static const char usr_bin [] ALIGN1 = "/usr/bin";
+       static const char usr_sbin[] ALIGN1 = "/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
+       };
+
+       int (*lf)(const char *, const char *);
+       char *fpc;
+       unsigned i;
+       int rc;
+
+       lf = link;
+       if (use_symbolic_links)
+               lf = symlink;
+
+       for (i = 0; i < ARRAY_SIZE(applet_main); i++) {
+               fpc = concat_path_file(
+                               install_dir[APPLET_INSTALL_LOC(i)],
+                               APPLET_NAME(i));
+               // debug: bb_error_msg("%slinking %s to busybox",
+               //              use_symbolic_links ? "sym" : "", fpc);
+               rc = lf(busybox, fpc);
+               if (rc != 0 && errno != EEXIST) {
+                       bb_simple_perror_msg(fpc);
+               }
+               free(fpc);
+       }
+}
+#else
+#define install_links(x,y) ((void)0)
+#endif /* FEATURE_INSTALLER */
+
+/* If we were called as "busybox..." */
+static int busybox_main(char **argv)
+{
+       if (!argv[1]) {
+               /* Called without arguments */
+               const char *a;
+               unsigned col, output_width;
+ help:
+               output_width = 80;
+               if (ENABLE_FEATURE_AUTOWIDTH) {
+                       /* Obtain the terminal width */
+                       get_terminal_width_height(0, &output_width, NULL);
+               }
+               /* leading tab and room to wrap */
+               output_width -= MAX_APPLET_NAME_LEN + 8;
+
+               dup2(1, 2);
+               full_write2_str(bb_banner); /* reuse const string... */
+               full_write2_str(" multi-call binary\n"
+                      "Copyright (C) 1998-2008 Erik Andersen, Rob Landley, Denys Vlasenko\n"
+                      "and others. Licensed under GPLv2.\n"
+                      "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"
+                      "\n"
+                      "Currently defined functions:\n");
+               col = 0;
+               a = applet_names;
+               while (*a) {
+                       int len;
+                       if (col > output_width) {
+                               full_write2_str(",\n");
+                               col = 0;
+                       }
+                       full_write2_str(col ? ", " : "\t");
+                       full_write2_str(a);
+                       len = strlen(a);
+                       col += len + 2;
+                       a += len + 1;
+               }
+               full_write2_str("\n\n");
+               return 0;
+       }
+
+       if (ENABLE_FEATURE_INSTALLER && strcmp(argv[1], "--install") == 0) {
+               const char *busybox;
+               busybox = xmalloc_readlink(bb_busybox_exec_path);
+               if (!busybox)
+                       busybox = bb_busybox_exec_path;
+               /* -s makes symlinks */
+               install_links(busybox, argv[2] && strcmp(argv[2], "-s") == 0);
+               return 0;
+       }
+
+       if (strcmp(argv[1], "--help") == 0) {
+               /* "busybox --help [<applet>]" */
+               if (!argv[2])
+                       goto help;
+               /* convert to "<applet> --help" */
+               argv[0] = argv[2];
+               argv[2] = NULL;
+       } else {
+               /* "busybox <applet> arg1 arg2 ..." */
+               argv++;
+       }
+       /* We support "busybox /a/path/to/applet args..." too. Allows for
+        * "#!/bin/busybox"-style wrappers */
+       applet_name = bb_get_last_path_component_nostrip(argv[0]);
+       run_applet_and_exit(applet_name, argv);
+
+       /*bb_error_msg_and_die("applet not found"); - sucks in printf */
+       full_write2_str(applet_name);
+       full_write2_str(": applet not found\n");
+       xfunc_die();
+}
+
+void FAST_FUNC run_applet_no_and_exit(int applet_no, char **argv)
+{
+       int argc = 1;
+
+       while (argv[argc])
+               argc++;
+
+       /* Reinit some shared global data */
+       xfunc_error_retval = EXIT_FAILURE;
+
+       applet_name = APPLET_NAME(applet_no);
+       if (argc == 2 && strcmp(argv[1], "--help") == 0) {
+               /* Special case. POSIX says "test --help"
+                * should be no different from e.g. "test --foo".  */
+//TODO: just compare applet_no with APPLET_NO_test
+               if (!ENABLE_TEST || strcmp(applet_name, "test") != 0)
+                       bb_show_usage();
+       }
+       if (ENABLE_FEATURE_SUID)
+               check_suid(applet_no);
+       exit(applet_main[applet_no](argc, argv));
+}
+
+void FAST_FUNC run_applet_and_exit(const char *name, char **argv)
+{
+       int applet = find_applet_by_name(name);
+       if (applet >= 0)
+               run_applet_no_and_exit(applet, argv);
+       if (!strncmp(name, "busybox", 7))
+               exit(busybox_main(argv));
+}
+
+#endif /* !defined(SINGLE_APPLET_MAIN) */
+
+
+
+#if ENABLE_BUILD_LIBBUSYBOX
+int lbb_main(char **argv)
+#else
+int main(int argc UNUSED_PARAM, char **argv)
+#endif
+{
+#if defined(SINGLE_APPLET_MAIN)
+       /* Only one applet is selected by the user! */
+       /* applet_names in this case is just "applet\0\0" */
+       lbb_prepare(applet_names USE_FEATURE_INDIVIDUAL(, argv));
+       return SINGLE_APPLET_MAIN(argc, argv);
+#else
+       lbb_prepare("busybox" USE_FEATURE_INDIVIDUAL(, argv));
+
+#if !BB_MMU
+       /* NOMMU re-exec trick sets high-order bit in first byte of name */
+       if (argv[0][0] & 0x80) {
+               re_execed = 1;
+               argv[0][0] &= 0x7f;
+       }
+#endif
+       applet_name = argv[0];
+       if (applet_name[0] == '-')
+               applet_name++;
+       applet_name = bb_basename(applet_name);
+
+       parse_config_file(); /* ...maybe, if FEATURE_SUID_CONFIG */
+
+       run_applet_and_exit(applet_name, argv);
+
+       /*bb_error_msg_and_die("applet not found"); - sucks in printf */
+       full_write2_str(applet_name);
+       full_write2_str(": applet not found\n");
+       xfunc_die();
+#endif
+}
diff --git a/libbb/ask_confirmation.c b/libbb/ask_confirmation.c
new file mode 100644 (file)
index 0000000..d08bc51
--- /dev/null
@@ -0,0 +1,34 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * bb_ask_confirmation implementation for busybox
+ *
+ * Copyright (C) 2003  Manuel Novoa III  <mjn3@codepoet.org>
+ *
+ * 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 "libbb.h"
+
+int FAST_FUNC 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 (file)
index 0000000..c0dcf0c
--- /dev/null
@@ -0,0 +1,79 @@
+/* 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 <andersen@codepoet.org>
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.h"
+
+/* do nothing signal handler */
+static void askpass_timeout(int UNUSED_PARAM ignore)
+{
+}
+
+char* FAST_FUNC bb_ask_stdin(const char *prompt)
+{
+       return bb_ask(STDIN_FILENO, 0, prompt);
+}
+char* FAST_FUNC bb_ask(const int fd, int timeout, const char *prompt)
+{
+       /* Was static char[BIGNUM] */
+       enum { sizeof_passwd = 128 };
+       static char *passwd;
+
+       char *ret;
+       int i;
+       struct sigaction sa, oldsa;
+       struct termios tio, oldtio;
+
+       if (!passwd)
+               passwd = xmalloc(sizeof_passwd);
+       memset(passwd, 0, sizeof_passwd);
+
+       tcgetattr(fd, &oldtio);
+       tcflush(fd, TCIFLUSH);
+       tio = oldtio;
+       tio.c_iflag &= ~(IUCLC|IXON|IXOFF|IXANY);
+       tio.c_lflag &= ~(ECHO|ECHOE|ECHOK|ECHONL|TOSTOP);
+       tcsetattr_stdin_TCSANOW(&tio);
+
+       memset(&sa, 0, sizeof(sa));
+       /* sa.sa_flags = 0; - no SA_RESTART! */
+       /* SIGINT and SIGALRM will interrupt read below */
+       sa.sa_handler = askpass_timeout;
+       sigaction(SIGINT, &sa, &oldsa);
+       if (timeout) {
+               sigaction_set(SIGALRM, &sa);
+               alarm(timeout);
+       }
+
+       fputs(prompt, stdout);
+       fflush(stdout);
+       ret = NULL;
+       /* On timeout or Ctrl-C, read will hopefully be interrupted,
+        * and we return NULL */
+       if (read(fd, 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);
+       }
+       sigaction_set(SIGINT, &oldsa);
+
+       tcsetattr_stdin_TCSANOW(&oldtio);
+       bb_putchar('\n');
+       fflush(stdout);
+       return ret;
+}
diff --git a/libbb/bb_basename.c b/libbb/bb_basename.c
new file mode 100644 (file)
index 0000000..bab4166
--- /dev/null
@@ -0,0 +1,18 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Utility routines.
+ *
+ * Copyright (C) 2007 Denys Vlasenko
+ *
+ * Licensed under GPL version 2, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.h"
+
+const char* FAST_FUNC bb_basename(const char *name)
+{
+       const char *cp = strrchr(name, '/');
+       if (cp)
+               return cp + 1;
+       return name;
+}
diff --git a/libbb/bb_do_delay.c b/libbb/bb_do_delay.c
new file mode 100644 (file)
index 0000000..3d52cc5
--- /dev/null
@@ -0,0 +1,22 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Busybox utility routines.
+ *
+ * Copyright (C) 2005 by Tito Ragusa <tito-wolit@tiscali.it>
+ *
+ * Licensed under the GPL v2, see the file LICENSE in this tarball.
+ */
+
+#include "libbb.h"
+
+void FAST_FUNC 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 (file)
index 0000000..d728577
--- /dev/null
@@ -0,0 +1,112 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * password utility routines.
+ *
+ * Copyright (C) 1999-2004 by Erik Andersen <andersen@codepoet.org>
+ * Copyright (C) 2008 by Tito Ragusa <farmatito@tiscali.it>
+ *
+ * Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
+ */
+
+#include "libbb.h"
+
+/* TODO: maybe change API to return malloced data?
+ * This will allow to stop using libc functions returning
+ * pointers to static data (getpwuid)
+ */
+
+struct passwd* FAST_FUNC xgetpwnam(const char *name)
+{
+       struct passwd *pw = getpwnam(name);
+       if (!pw)
+               bb_error_msg_and_die("unknown user %s", name);
+       return pw;
+}
+
+struct group* FAST_FUNC xgetgrnam(const char *name)
+{
+       struct group *gr = getgrnam(name);
+       if (!gr)
+               bb_error_msg_and_die("unknown group %s", name);
+       return gr;
+}
+
+
+struct passwd* FAST_FUNC xgetpwuid(uid_t uid)
+{
+       struct passwd *pw = getpwuid(uid);
+       if (!pw)
+               bb_error_msg_and_die("unknown uid %u", (unsigned)uid);
+       return pw;
+}
+
+struct group* FAST_FUNC xgetgrgid(gid_t gid)
+{
+       struct group *gr = getgrgid(gid);
+       if (!gr)
+               bb_error_msg_and_die("unknown gid %u", (unsigned)gid);
+       return gr;
+}
+
+char* FAST_FUNC xuid2uname(uid_t uid)
+{
+       struct passwd *pw = xgetpwuid(uid);
+       return pw->pw_name;
+}
+
+char* FAST_FUNC xgid2group(gid_t gid)
+{
+       struct group *gr = xgetgrgid(gid);
+       return gr->gr_name;
+}
+
+char* FAST_FUNC uid2uname(uid_t uid)
+{
+       struct passwd *pw = getpwuid(uid);
+       return (pw) ? pw->pw_name : NULL;
+}
+
+char* FAST_FUNC gid2group(gid_t gid)
+{
+       struct group *gr = getgrgid(gid);
+       return (gr) ? gr->gr_name : NULL;
+}
+
+char* FAST_FUNC uid2uname_utoa(long uid)
+{
+       char *name = uid2uname(uid);
+       return (name) ? name : utoa(uid);
+}
+
+char* FAST_FUNC gid2group_utoa(long gid)
+{
+       char *name = gid2group(gid);
+       return (name) ? name : utoa(gid);
+}
+
+long FAST_FUNC xuname2uid(const char *name)
+{
+       struct passwd *myuser;
+
+       myuser = xgetpwnam(name);
+       return myuser->pw_uid;
+}
+
+long FAST_FUNC xgroup2gid(const char *name)
+{
+       struct group *mygroup;
+
+       mygroup = xgetgrnam(name);
+       return mygroup->gr_gid;
+}
+
+unsigned long FAST_FUNC get_ug_id(const char *s,
+               long FAST_FUNC (*xname2id)(const char *))
+{
+       unsigned long r;
+
+       r = bb_strtoul(s, NULL, 10);
+       if (errno)
+               return xname2id(s);
+       return r;
+}
diff --git a/libbb/bb_qsort.c b/libbb/bb_qsort.c
new file mode 100644 (file)
index 0000000..9773afa
--- /dev/null
@@ -0,0 +1,20 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Wrapper for common string vector sorting operation
+ *
+ * Copyright (c) 2008 Denys Vlasenko
+ *
+ * Licensed under GPLv2, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.h"
+
+int /* not FAST_FUNC! */ bb_pstrcmp(const void *a, const void *b)
+{
+       return strcmp(*(char**)a, *(char**)b);
+}
+
+void FAST_FUNC qsort_string_vector(char **sv, unsigned count)
+{
+       qsort(sv, count, sizeof(char*), bb_pstrcmp);
+}
diff --git a/libbb/bb_strtod.c b/libbb/bb_strtod.c
new file mode 100644 (file)
index 0000000..39bdeb5
--- /dev/null
@@ -0,0 +1,86 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Utility routines.
+ *
+ * Copyright (C) 1999-2004 by Erik Andersen <andersen@codepoet.org>
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.h"
+#include <math.h>  /* just for HUGE_VAL */
+
+#define NOT_DIGIT(a) (((unsigned char)(a-'0')) > 9)
+
+double FAST_FUNC bb_strtod(const char *arg, char **endp)
+{
+       double v;
+       char *endptr;
+
+       /* Allow .NN form. People want to use "sleep .15" etc */
+       if (arg[0] != '-' && 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;
+}
+
+#if 0
+/* String to timespec: "NNNN[.NNNNN]" -> struct timespec.
+ * Can be used for other fixed-point needs.
+ * Returns pointer past last converted char,
+ * and returns errno similar to bb_strtoXX functions.
+ */
+char* FAST_FUNC bb_str_to_ts(struct timespec *ts, const char *arg)
+{
+       if (sizeof(ts->tv_sec) <= sizeof(int))
+               ts->tv_sec = bb_strtou(arg, &arg, 10);
+       else if (sizeof(ts->tv_sec) <= sizeof(long))
+               ts->tv_sec = bb_strtoul(arg, &arg, 10);
+       else
+               ts->tv_sec = bb_strtoull(arg, &arg, 10);
+       ts->tv_nsec = 0;
+
+       if (*arg != '.')
+               return arg;
+
+       /* !EINVAL: number is not ok (alphanumeric ending, overflow etc) */
+       if (errno != EINVAL)
+               return arg;
+
+       if (!*++arg) /* "NNN." */
+               return arg;
+
+       { /* "NNN.xxx" - parse xxx */
+               int ndigits;
+               char *p;
+               char buf[10]; /* we never use more than 9 digits */
+
+               /* Need to make a copy to avoid false overflow */
+               safe_strncpy(buf, arg, 10);
+               ts->tv_nsec = bb_strtou(buf, &p, 10);
+               ndigits = p - buf;
+               arg += ndigits;
+               /* normalize to nsec */
+               while (ndigits < 9) {
+                       ndigits++;
+                       ts->tv_nsec *= 10;
+               }
+               while (isdigit(*arg)) /* skip possible 10th plus digits */
+                       arg++;
+       }
+       return arg;
+}
+#endif
diff --git a/libbb/bb_strtonum.c b/libbb/bb_strtonum.c
new file mode 100644 (file)
index 0000000..87cd744
--- /dev/null
@@ -0,0 +1,139 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Utility routines.
+ *
+ * Copyright (C) 1999-2004 by Erik Andersen <andersen@codepoet.org>
+ *
+ * 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 otherwise 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 )
+ *    return value is all-ones in this case.
+ *
+ * Test code:
+ * char *endptr;
+ * const char *minus = "-";
+ * errno = 0;
+ * bb_strtoi(minus, &endptr, 0); // must set ERANGE
+ * printf("minus:%p endptr:%p errno:%d EINVAL:%d\n", minus, endptr, errno, EINVAL);
+ * errno = 0;
+ * bb_strtoi("-0-", &endptr, 0); // must set EINVAL and point to second '-'
+ * printf("endptr[0]:%c errno:%d EINVAL:%d\n", endptr[0], errno, EINVAL);
+ */
+
+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;
+
+       /* 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 FAST_FUNC 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 FAST_FUNC bb_strtoll(const char *arg, char **endp, int base)
+{
+       unsigned long long v;
+       char *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! :( */
+       char first = (arg[0] != '-' ? arg[0] : arg[1]);
+       if (!isalnum(first)) return ret_ERANGE();
+
+       errno = 0;
+       v = strtoll(arg, &endptr, base);
+       return handle_errors(v, endp, endptr);
+}
+
+#if ULONG_MAX != ULLONG_MAX
+unsigned long FAST_FUNC 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 FAST_FUNC bb_strtol(const char *arg, char **endp, int base)
+{
+       long v;
+       char *endptr;
+
+       char first = (arg[0] != '-' ? arg[0] : arg[1]);
+       if (!isalnum(first)) return ret_ERANGE();
+
+       errno = 0;
+       v = strtol(arg, &endptr, base);
+       return handle_errors(v, endp, endptr);
+}
+#endif
+
+#if UINT_MAX != ULONG_MAX
+unsigned FAST_FUNC 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 FAST_FUNC bb_strtoi(const char *arg, char **endp, int base)
+{
+       long v;
+       char *endptr;
+
+       char first = (arg[0] != '-' ? arg[0] : arg[1]);
+       if (!isalnum(first)) 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
diff --git a/libbb/change_identity.c b/libbb/change_identity.c
new file mode 100644 (file)
index 0000000..619db09
--- /dev/null
@@ -0,0 +1,41 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Copyright 1989 - 1991, Julianne Frances Haugh <jockgrrl@austin.rr.com>
+ * 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.  */
+void FAST_FUNC change_identity(const struct passwd *pw)
+{
+       if (initgroups(pw->pw_name, pw->pw_gid) == -1)
+               bb_perror_msg_and_die("can't set groups");
+       endgrent(); /* helps to close a fd used internally by libc */
+       xsetgid(pw->pw_gid);
+       xsetuid(pw->pw_uid);
+}
diff --git a/libbb/chomp.c b/libbb/chomp.c
new file mode 100644 (file)
index 0000000..ed4bf6b
--- /dev/null
@@ -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 FAST_FUNC 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 (file)
index 0000000..43c59e8
--- /dev/null
@@ -0,0 +1,78 @@
+/* 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 FAST_FUNC 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;
+}
+
+int FAST_FUNC index_in_strings(const char *strings, const char *key)
+{
+       int idx = 0;
+
+       while (*strings) {
+               if (strcmp(strings, key) == 0) {
+                       return idx;
+               }
+               strings += strlen(strings) + 1; /* skip NUL */
+               idx++;
+       }
+       return -1;
+}
+
+/* returns the array index of the string, even if it matches only a beginning */
+/* (index of first match is returned, or -1) */
+#ifdef UNUSED
+int FAST_FUNC index_in_substr_array(const char *const string_array[], const char *key)
+{
+       int i;
+       int len = strlen(key);
+       if (len) {
+               for (i = 0; string_array[i] != 0; i++) {
+                       if (strncmp(string_array[i], key, len) == 0) {
+                               return i;
+                       }
+               }
+       }
+       return -1;
+}
+#endif
+
+int FAST_FUNC index_in_substrings(const char *strings, const char *key)
+{
+       int len = strlen(key);
+
+       if (len) {
+               int idx = 0;
+               while (*strings) {
+                       if (strncmp(strings, key, len) == 0) {
+                               return idx;
+                       }
+                       strings += strlen(strings) + 1; /* skip NUL */
+                       idx++;
+               }
+       }
+       return -1;
+}
+
+const char* FAST_FUNC nth_string(const char *strings, int n)
+{
+       while (n) {
+               n--;
+               strings += strlen(strings) + 1;
+       }
+       return strings;
+}
diff --git a/libbb/concat_path_file.c b/libbb/concat_path_file.c
new file mode 100644 (file)
index 0000000..fb53354
--- /dev/null
@@ -0,0 +1,29 @@
+/* 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 filename to new allocated buffer.
+ * Add '/' only as needed (no duplicate // are produced).
+ * If path is NULL, it is assumed to be "/".
+ * filename should not be NULL.
+ */
+
+#include "libbb.h"
+
+char* FAST_FUNC 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 (file)
index 0000000..313fa63
--- /dev/null
@@ -0,0 +1,23 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Utility routines.
+ *
+ * Copyright (C) (C) 2003  Vladimir Oleynik  <dzo@simtreas.ru>
+ *
+ * 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 skipping "." and ".." directory entries
+*/
+
+#include "libbb.h"
+
+char* FAST_FUNC concat_subpath_file(const char *path, const char *f)
+{
+       if (f && DOT_OR_DOTDOT(f))
+               return NULL;
+       return concat_path_file(path, f);
+}
diff --git a/libbb/copy_file.c b/libbb/copy_file.c
new file mode 100644 (file)
index 0000000..d804ecc
--- /dev/null
@@ -0,0 +1,399 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Mini copy_file implementation for busybox
+ *
+ * Copyright (C) 2001 by Matt Kraai <kraai@alumni.carnegiemellon.edu>
+ * SELinux support by Yuichi Nakamura <ynakam@hitachisoft.jp>
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ *
+ */
+
+#include "libbb.h"
+
+// POSIX: if exists and -i, ask (w/o -i assume yes).
+// Then open w/o EXCL (yes, not unlink!).
+// 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...
+//
+// NB: we have special code which still allows for "cp file /dev/node"
+// to work POSIX-ly (the only realistic case where it makes sense)
+
+#define DO_POSIX_CP 0  /* 1 - POSIX behavior, 0 - safe behavior */
+
+// errno must be set to relevant value ("why we cannot create dest?")
+// for POSIX mode to give reasonable error message
+static int ask_and_unlink(const char *dest, int flags)
+{
+       int e = errno;
+#if DO_POSIX_CP
+       if (!(flags & (FILEUTILS_FORCE|FILEUTILS_INTERACTIVE))) {
+               // Either it exists, or the *path* doesnt exist
+               bb_perror_msg("cannot create '%s'", dest);
+               return -1;
+       }
+#endif
+       // If !DO_POSIX_CP, act as if -f is always in effect - we don't want
+       // "cannot create" msg, we want unlink to be done (silently unless -i).
+
+       // TODO: maybe we should do it only if ctty is present?
+       if (flags & FILEUTILS_INTERACTIVE) {
+               // We would not do POSIX insanity. -i asks,
+               // then _unlinks_ the offender. Presto.
+               // (No "opening without O_EXCL", no "unlink only if -f")
+               // Or else we will end up having 3 open()s!
+               fprintf(stderr, "%s: overwrite '%s'? ", applet_name, dest);
+               if (!bb_ask_confirmation())
+                       return 0; // not allowed to overwrite
+       }
+       if (unlink(dest) < 0) {
+#if ENABLE_FEATURE_VERBOSE_CP_MESSAGE
+               if (e == errno && e == ENOENT) {
+                       /* e == ENOTDIR is similar: path has non-dir component,
+                        * but in this case we don't even reach copy_file() */
+                       bb_error_msg("cannot create '%s': Path does not exist", dest);
+                       return -1; // error
+               }
+#endif
+               errno = e;
+               bb_perror_msg("cannot create '%s'", dest);
+               return -1; // error
+       }
+       return 1; // ok (to try again)
+}
+
+/* Return:
+ * -1 error, copy not made
+ *  0 copy is made or user answered "no" in interactive mode
+ *    (failures to preserve mode/owner/times are not reported in exit code)
+ */
+int FAST_FUNC copy_file(const char *source, const char *dest, int flags)
+{
+       /* This is a recursive function, try to minimize stack usage */
+       /* NB: each struct stat is ~100 bytes */
+       struct stat source_stat;
+       struct stat dest_stat;
+       signed char retval = 0;
+       signed char dest_exists = 0;
+       signed char ovr;
+
+/* Inverse of cp -d ("cp without -d") */
+#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 ENABLE_SELINUX
+       if ((flags & FILEUTILS_PRESERVE_SECURITY_CONTEXT) && is_selinux_enabled() > 0) {
+               security_context_t con;
+               if (lgetfilecon(source, &con) >= 0) {
+                       if (setfscreatecon(con) < 0) {
+                               bb_perror_msg("cannot set setfscreatecon %s", con);
+                               freecon(con);
+                               return -1;
+                       }
+               } else if (errno == ENOTSUP || errno == ENODATA) {
+                       setfscreatecon_or_die(NULL);
+               } else {
+                       bb_perror_msg("cannot lgetfilecon %s", source);
+                       return -1;
+               }
+       }
+#endif
+
+       if (S_ISDIR(source_stat.st_mode)) {
+               DIR *dp;
+               const char *tp;
+               struct dirent *d;
+               mode_t saved_umask = 0;
+
+               if (!(flags & FILEUTILS_RECUR)) {
+                       bb_error_msg("omitting directory '%s'", source);
+                       return -1;
+               }
+
+               /* Did we ever create source ourself before? */
+               tp = is_in_ino_dev_hashtable(&source_stat);
+               if (tp) {
+                       /* We did! it's a recursion! man the lifeboats... */
+                       bb_error_msg("recursion detected, 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;
+                       }
+                       /* race here: user can substitute a symlink between
+                        * this check and actual creation of files inside dest */
+               } 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;
+                       /* Allow owner to access new dir (at least for now) */
+                       mode |= S_IRWXU;
+                       if (mkdir(dest, mode) < 0) {
+                               umask(saved_umask);
+                               bb_perror_msg("cannot create directory '%s'", dest);
+                               return -1;
+                       }
+                       umask(saved_umask);
+                       /* need stat info for add_to_ino_dev_hashtable */
+                       if (lstat(dest, &dest_stat) < 0) {
+                               bb_perror_msg("cannot stat '%s'", dest);
+                               return -1;
+                       }
+               }
+               /* remember (dev,inode) of each created dir.
+                * NULL: name is not remembered */
+               add_to_ino_dev_hashtable(&dest_stat, NULL);
+
+               /* Recursively copy files in SOURCE */
+               dp = opendir(source);
+               if (dp == NULL) {
+                       retval = -1;
+                       goto preserve_mode_ugid_time;
+               }
+
+               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)
+                               retval = -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 preserve %s of '%s'", "permissions", dest);
+                       /* retval = -1; - WRONG! copy *WAS* made */
+               }
+               goto preserve_mode_ugid_time;
+       }
+
+       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 = ask_and_unlink(dest, flags);
+                       if (ovr <= 0)
+                               return ovr;
+                       if (lf(source, dest) < 0) {
+                               bb_perror_msg("cannot create link '%s'", dest);
+                               return -1;
+                       }
+               }
+               /* _Not_ jumping to preserve_mode_ugid_time:
+                * hard/softlinks don't have those */
+               return 0;
+       }
+
+       if (/* "cp thing1 thing2" without -R: just open and read() from thing1 */
+           !(flags & FILEUTILS_RECUR)
+           /* "cp [-opts] regular_file thing2" */
+        || S_ISREG(source_stat.st_mode)
+        /* DEREF uses stat, which never returns S_ISLNK() == true.
+         * So the below is never true: */
+        /* || (FLAGS_DEREF && S_ISLNK(source_stat.st_mode)) */
+       ) {
+               int src_fd;
+               int dst_fd;
+               mode_t new_mode;
+
+               if (!FLAGS_DEREF && S_ISLNK(source_stat.st_mode)) {
+                       /* "cp -d symlink dst": create a link */
+                       goto dont_cat;
+               }
+
+               if (ENABLE_FEATURE_PRESERVE_HARDLINKS && !FLAGS_DEREF) {
+                       const char *link_target;
+                       link_target = is_in_ino_dev_hashtable(&source_stat);
+                       if (link_target) {
+                               if (link(link_target, dest) < 0) {
+                                       ovr = ask_and_unlink(dest, flags);
+                                       if (ovr <= 0)
+                                               return ovr;
+                                       if (link(link_target, dest) < 0) {
+                                               bb_perror_msg("cannot create link '%s'", dest);
+                                               return -1;
+                                       }
+                               }
+                               return 0;
+                       }
+                       add_to_ino_dev_hashtable(&source_stat, dest);
+               }
+
+               src_fd = open_or_warn(source, O_RDONLY);
+               if (src_fd < 0)
+                       return -1;
+
+               /* Do not try to open with weird mode fields */
+               new_mode = source_stat.st_mode;
+               if (!S_ISREG(source_stat.st_mode))
+                       new_mode = 0666;
+
+               /* POSIX way is a security problem versus symlink attacks,
+                * we do it only for non-symlinks, and only for non-recursive,
+                * non-interactive cp. NB: it is still racy
+                * for "cp file /home/bad_user/file" case
+                * (user can rm file and create a link to /etc/passwd) */
+               if (DO_POSIX_CP
+                || (dest_exists
+                    && !(flags & (FILEUTILS_RECUR|FILEUTILS_INTERACTIVE))
+                    && !S_ISLNK(dest_stat.st_mode))
+               ) {
+                       dst_fd = open(dest, O_WRONLY|O_CREAT|O_TRUNC, new_mode);
+               } else  /* safe way: */
+                       dst_fd = open(dest, O_WRONLY|O_CREAT|O_EXCL, new_mode);
+               if (dst_fd == -1) {
+                       ovr = ask_and_unlink(dest, flags);
+                       if (ovr <= 0) {
+                               close(src_fd);
+                               return ovr;
+                       }
+                       /* It shouldn't exist. If it exists, do not open (symlink attack?) */
+                       dst_fd = open3_or_warn(dest, O_WRONLY|O_CREAT|O_EXCL, new_mode);
+                       if (dst_fd < 0) {
+                               close(src_fd);
+                               return -1;
+                       }
+               }
+
+#if ENABLE_SELINUX
+               if ((flags & (FILEUTILS_PRESERVE_SECURITY_CONTEXT|FILEUTILS_SET_SECURITY_CONTEXT))
+                && is_selinux_enabled() > 0
+               ) {
+                       security_context_t con;
+                       if (getfscreatecon(&con) == -1) {
+                               bb_perror_msg("getfscreatecon");
+                               return -1;
+                       }
+                       if (con) {
+                               if (setfilecon(dest, con) == -1) {
+                                       bb_perror_msg("setfilecon:%s,%s", dest, con);
+                                       freecon(con);
+                                       return -1;
+                               }
+                               freecon(con);
+                       }
+               }
+#endif
+               if (bb_copyfd_eof(src_fd, dst_fd) == -1)
+                       retval = -1;
+               /* Ok, writing side I can understand... */
+               if (close(dst_fd) < 0) {
+                       bb_perror_msg("cannot close '%s'", dest);
+                       retval = -1;
+               }
+               /* ...but read size is already checked by bb_copyfd_eof */
+               close(src_fd);
+               /* "cp /dev/something new_file" should not
+                * copy mode of /dev/something */
+               if (!S_ISREG(source_stat.st_mode))
+                       return retval;
+               goto preserve_mode_ugid_time;
+       }
+ dont_cat:
+
+       /* Source is a symlink or a special file */
+       /* We are lazy here, a bit lax with races... */
+       if (dest_exists) {
+               errno = EEXIST;
+               ovr = ask_and_unlink(dest, flags);
+               if (ovr <= 0)
+                       return ovr;
+       }
+       if (S_ISLNK(source_stat.st_mode)) {
+               char *lpath = xmalloc_readlink_or_warn(source);
+               if (lpath) {
+                       int r = symlink(lpath, dest);
+                       free(lpath);
+                       if (r < 0) {
+                               bb_perror_msg("cannot create symlink '%s'", dest);
+                               return -1;
+                       }
+                       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);
+               }
+               /* _Not_ jumping to preserve_mode_ugid_time:
+                * symlinks don't have those */
+               return 0;
+       }
+       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)
+       ) {
+               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("unrecognized file '%s' with mode %x", source, source_stat.st_mode);
+               return -1;
+       }
+
+ preserve_mode_ugid_time:
+
+       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;
+               /* BTW, utimes sets usec-precision time - just FYI */
+               if (utime(dest, &times) < 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 retval;
+}
diff --git a/libbb/copyfd.c b/libbb/copyfd.c
new file mode 100644 (file)
index 0000000..c5f8b5b
--- /dev/null
@@ -0,0 +1,119 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Utility routines.
+ *
+ * Copyright (C) 1999-2005 by Erik Andersen <andersen@codepoet.org>
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.h"
+
+/* Used by NOFORK applets (e.g. cat) - must not use xmalloc */
+
+static off_t bb_full_fd_action(int src_fd, int dst_fd, off_t size)
+{
+       int status = -1;
+       off_t total = 0;
+#if CONFIG_FEATURE_COPYBUF_KB <= 4
+       char buffer[CONFIG_FEATURE_COPYBUF_KB * 1024];
+       enum { buffer_size = sizeof(buffer) };
+#else
+       char *buffer;
+       int buffer_size;
+
+       /* We want page-aligned buffer, just in case kernel is clever
+        * and can do page-aligned io more efficiently */
+       buffer = mmap(NULL, CONFIG_FEATURE_COPYBUF_KB * 1024,
+                       PROT_READ | PROT_WRITE,
+                       MAP_PRIVATE | MAP_ANON,
+                       /* ignored: */ -1, 0);
+       buffer_size = CONFIG_FEATURE_COPYBUF_KB * 1024;
+       if (buffer == MAP_FAILED) {
+               buffer = alloca(4 * 1024);
+               buffer_size = 4 * 1024;
+       }
+#endif
+
+       if (src_fd < 0)
+               goto out;
+
+       if (!size) {
+               size = buffer_size;
+               status = 1; /* copy until eof */
+       }
+
+       while (1) {
+               ssize_t rd;
+
+               rd = safe_read(src_fd, buffer, size > buffer_size ? buffer_size : 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) { /* if we aren't copying till EOF... */
+                       size -= rd;
+                       if (!size) {
+                               /* 'size' bytes copied - all done */
+                               status = 0;
+                               break;
+                       }
+               }
+       }
+ out:
+
+#if CONFIG_FEATURE_COPYBUF_KB > 4
+       if (buffer_size != 4 * 1024)
+               munmap(buffer, buffer_size);
+#endif
+       return status ? -1 : total;
+}
+
+
+#if 0
+void FAST_FUNC complain_copyfd_and_die(off_t sz)
+{
+       if (sz != -1)
+               bb_error_msg_and_die("short read");
+       /* if sz == -1, bb_copyfd_XX already complained */
+       xfunc_die();
+}
+#endif
+
+off_t FAST_FUNC bb_copyfd_size(int fd1, int fd2, off_t size)
+{
+       if (size) {
+               return bb_full_fd_action(fd1, fd2, size);
+       }
+       return 0;
+}
+
+void FAST_FUNC bb_copyfd_exact_size(int fd1, int fd2, off_t size)
+{
+       off_t sz = bb_copyfd_size(fd1, fd2, size);
+       if (sz == size)
+               return;
+       if (sz != -1)
+               bb_error_msg_and_die("short read");
+       /* if sz == -1, bb_copyfd_XX already complained */
+       xfunc_die();
+}
+
+off_t FAST_FUNC 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 (file)
index 0000000..6301589
--- /dev/null
@@ -0,0 +1,80 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Copyright 1989 - 1991, Julianne Frances Haugh <jockgrrl@austin.rr.com>
+ * 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 if PW has an empty password.
+ *
+ * NULL pw means "just fake it for login with bad username" */
+
+int FAST_FUNC correct_password(const struct passwd *pw)
+{
+       char *unencrypted, *encrypted;
+       const char *correct;
+       int r;
+#if ENABLE_FEATURE_SHADOWPASSWDS
+       /* Using _r function to avoid pulling in static buffers */
+       struct spwd spw;
+       char buffer[256];
+#endif
+
+       /* fake salt. crypt() can choke otherwise. */
+       correct = "aa";
+       if (!pw) {
+               /* "aa" will never match */
+               goto fake_it;
+       }
+       correct = pw->pw_passwd;
+#if ENABLE_FEATURE_SHADOWPASSWDS
+       if ((correct[0] == 'x' || correct[0] == '*') && !correct[1]) {
+               /* getspnam_r may return 0 yet set result to NULL.
+                * At least glibc 2.4 does this. Be extra paranoid here. */
+               struct spwd *result = NULL;
+               r = getspnam_r(pw->pw_name, &spw, buffer, sizeof(buffer), &result);
+               correct = (r || !result) ? "aa" : result->sp_pwdp;
+       }
+#endif
+
+       if (!correct[0]) /* empty password field? */
+               return 1;
+
+ fake_it:
+       unencrypted = bb_ask_stdin("Password: ");
+       if (!unencrypted) {
+               return 0;
+       }
+       encrypted = pw_encrypt(unencrypted, correct, 1);
+       r = (strcmp(encrypted, correct) == 0);
+       free(encrypted);
+       memset(unencrypted, 0, strlen(unencrypted));
+       return r;
+}
diff --git a/libbb/crc32.c b/libbb/crc32.c
new file mode 100644 (file)
index 0000000..36ac860
--- /dev/null
@@ -0,0 +1,42 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * CRC32 table fill function
+ * Copyright (C) 2006 by Rob Sullivan <cogito.ergo.cogito@gmail.com>
+ * (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
+ *
+ * Licensed under GPLv2, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.h"
+
+uint32_t* FAST_FUNC crc32_filltable(uint32_t *crc_table, int endian)
+{
+       uint32_t polynomial = endian ? 0x04c11db7 : 0xedb88320;
+       uint32_t c;
+       int i, j;
+
+       if (!crc_table)
+               crc_table = xmalloc(256 * sizeof(uint32_t));
+
+       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 (file)
index 0000000..91e478e
--- /dev/null
@@ -0,0 +1,38 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Utility routines.
+ *
+ * create raw socket for icmp (IPv6 version) protocol
+ * and drop root privileges if running setuid
+ *
+ * Licensed under GPLv2, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.h"
+
+#if ENABLE_FEATURE_IPV6
+int FAST_FUNC create_icmp6_socket(void)
+{
+       int sock;
+#if 0
+       struct protoent *proto;
+       proto = getprotobyname("ipv6-icmp");
+       /* if getprotobyname failed, just silently force
+        * proto->p_proto to have the correct value for "ipv6-icmp" */
+       sock = socket(AF_INET6, SOCK_RAW,
+                       (proto ? proto->p_proto : IPPROTO_ICMPV6));
+#else
+       sock = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6);
+#endif
+       if (sock < 0) {
+               if (errno == EPERM)
+                       bb_error_msg_and_die(bb_msg_perm_denied_are_you_root);
+               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 (file)
index 0000000..d75f845
--- /dev/null
@@ -0,0 +1,36 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Utility routines.
+ *
+ * create raw socket for icmp protocol
+ * and drop root privileges if running setuid
+ *
+ * Licensed under GPLv2, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.h"
+
+int FAST_FUNC create_icmp_socket(void)
+{
+       int sock;
+#if 0
+       struct protoent *proto;
+       proto = getprotobyname("icmp");
+       /* if getprotobyname failed, just silently force
+        * proto->p_proto to have the correct value for "icmp" */
+       sock = socket(AF_INET, SOCK_RAW,
+                       (proto ? proto->p_proto : 1)); /* 1 == ICMP */
+#else
+       sock = socket(AF_INET, SOCK_RAW, 1); /* 1 == ICMP */
+#endif
+       if (sock < 0) {
+               if (errno == EPERM)
+                       bb_error_msg_and_die(bb_msg_perm_denied_are_you_root);
+               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 (file)
index 0000000..0b19f21
--- /dev/null
@@ -0,0 +1,18 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Copyright (C) 2003  Manuel Novoa III  <mjn3@codepoet.org>
+ *
+ * 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 "libbb.h"
+
+int xfunc_error_retval = EXIT_FAILURE;
diff --git a/libbb/device_open.c b/libbb/device_open.c
new file mode 100644 (file)
index 0000000..cf8bcf6
--- /dev/null
@@ -0,0 +1,32 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Utility routines.
+ *
+ * Copyright (C) 1999-2004 by Erik Andersen <andersen@codepoet.org>
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.h"
+
+/* try to open up the specified device */
+int FAST_FUNC device_open(const char *device, int mode)
+{
+       int m, f, fd;
+
+       m = mode | O_NONBLOCK;
+
+       /* Retry up to 5 times */
+       /* TODO: explain why it can't be considered insane */
+       for (f = 0; f < 5; f++) {
+               fd = open(device, m, 0600);
+               if (fd >= 0)
+                       break;
+       }
+       if (fd < 0)
+               return fd;
+       /* Reset original flags. */
+       if (m != mode)
+               fcntl(fd, F_SETFL, mode);
+       return fd;
+}
diff --git a/libbb/die_if_bad_username.c b/libbb/die_if_bad_username.c
new file mode 100644 (file)
index 0000000..c1641d3
--- /dev/null
@@ -0,0 +1,36 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Check user and group names for illegal characters
+ *
+ * Copyright (C) 2008 Tito Ragusa <farmatito@tiscali.it>
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.h"
+
+/* To avoid problems, the username should consist only of
+ * letters, digits, underscores, periods, at signs and dashes,
+ * and not start with a dash (as defined by IEEE Std 1003.1-2001).
+ * For compatibility with Samba machine accounts $ is also supported
+ * at the end of the username.
+ */
+
+void FAST_FUNC die_if_bad_username(const char *name)
+{
+       goto skip; /* 1st char being dash isn't valid */
+       do {
+               if (*name == '-')
+                       continue;
+ skip:
+               if (isalnum(*name)
+                || *name == '_'
+                || *name == '.'
+                || *name == '@'
+                || (*name == '$' && !*(name + 1))
+               ) {
+                       continue;
+               }
+               bb_error_msg_and_die("illegal character '%c'", *name);
+       } while (*++name);
+}
diff --git a/libbb/dump.c b/libbb/dump.c
new file mode 100644 (file)
index 0000000..2e777c3
--- /dev/null
@@ -0,0 +1,839 @@
+/* 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"
+
+static const char index_str[] ALIGN1 = ".#-+ 0123456789";
+
+static const char size_conv_str[] ALIGN1 =
+"\x1\x4\x4\x4\x4\x4\x4\x8\x8\x8\x8\010cdiouxXeEfgG";
+
+static const char lcc[] ALIGN1 = "diouxX";
+
+
+typedef struct priv_dumper_t {
+       dumper_t pub;
+
+       char **argv;
+       FU *endfu;
+       off_t savaddress;        /* saved address/offset in stream */
+       off_t eaddress;          /* end address */
+       off_t address;           /* address/offset in stream */
+       int blocksize;
+       smallint exitval;        /* final exit value */
+
+       /* former statics */
+       smallint next__done;
+       smallint get__ateof; // = 1;
+       unsigned char *get__curp;
+       unsigned char *get__savp;
+} priv_dumper_t;
+
+dumper_t* FAST_FUNC alloc_dumper(void)
+{
+       priv_dumper_t *dumper = xzalloc(sizeof(*dumper));
+       dumper->pub.dump_length = -1;
+       dumper->pub.dump_vflag = FIRST;
+       dumper->get__ateof = 1;
+       return &dumper->pub;
+}
+
+
+static NOINLINE 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;
+                       /*
+                        * 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))
+                                       continue;
+                       }
+                       p = strchr(size_conv_str + 12, *fmt);
+                       if (!p) {
+                               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(priv_dumper_t *dumper, 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] zalloc 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 through.
+                        */
+
+                       /* skip preceding text and up to the next % sign */
+                       for (p1 = fmtp; *p1 && *p1 != '%'; ++p1)
+                               continue;
+
+                       /* 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;
+                               /* skip to conversion character */
+                               for (++p1; strchr(index_str, *p1); ++p1)
+                                       continue;
+                       } else {
+                               /* skip any special chars, field width */
+                               while (strchr(index_str + 1, *++p1))
+                                       continue;
+                               if (*p1 == '.' && isdigit(*++p1)) {
+                                       sokay = USEPREC;
+                                       prec = atoi(p1);
+                                       while (isdigit(*++p1))
+                                               continue;
+                               } 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;
+                                       e = strchr(lcc, *p1);
+                                       if (!e) {
+                                               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':
+                                       dumper->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;
+                       //Too early! xrealloc can move pr->fmt!
+                       //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++)
+                               continue;
+                       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;
+                       }
+
+                       pr->cchar = pr->fmt + (p1 - fmtp);
+                       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 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 = fu->nextfu) {
+               if (!fu->nextfu && fs->bcnt < dumper->blocksize
+                && !(fu->flags & F_SETREP) && fu->bcnt
+               ) {
+                       fu->reps += (dumper->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(priv_dumper_t *dumper, const char *fname, int statok)
+{
+       struct stat sbuf;
+
+       if (statok) {
+               if (fstat(STDIN_FILENO, &sbuf)) {
+                       bb_simple_perror_msg_and_die(fname);
+               }
+               if (!(S_ISCHR(sbuf.st_mode) || S_ISBLK(sbuf.st_mode) || S_ISFIFO(sbuf.st_mode))
+                && dumper->pub.dump_skip >= sbuf.st_size
+               ) {
+                       /* If bb_dump_size valid and pub.dump_skip >= size */
+                       dumper->pub.dump_skip -= sbuf.st_size;
+                       dumper->address += sbuf.st_size;
+                       return;
+               }
+       }
+       if (fseek(stdin, dumper->pub.dump_skip, SEEK_SET)) {
+               bb_simple_perror_msg_and_die(fname);
+       }
+       dumper->address += dumper->pub.dump_skip;
+       dumper->savaddress = dumper->address;
+       dumper->pub.dump_skip = 0;
+}
+
+static NOINLINE int next(priv_dumper_t *dumper)
+{
+       int statok;
+
+       for (;;) {
+               if (*dumper->argv) {
+                       if (!(freopen(*dumper->argv, "r", stdin))) {
+                               bb_simple_perror_msg(*dumper->argv);
+                               dumper->exitval = 1;
+                               ++dumper->argv;
+                               continue;
+                       }
+                       dumper->next__done = statok = 1;
+               } else {
+                       if (dumper->next__done)
+                               return 0;
+                       dumper->next__done = 1;
+                       statok = 0;
+               }
+               if (dumper->pub.dump_skip)
+                       do_skip(dumper, statok ? *dumper->argv : "stdin", statok);
+               if (*dumper->argv)
+                       ++dumper->argv;
+               if (!dumper->pub.dump_skip)
+                       return 1;
+       }
+       /* NOTREACHED */
+}
+
+static unsigned char *get(priv_dumper_t *dumper)
+{
+       int n;
+       int need, nread;
+       int blocksize = dumper->blocksize;
+
+       if (!dumper->get__curp) {
+               dumper->address = (off_t)0; /*DBU:[dave@cray.com] initialize,initialize..*/
+               dumper->get__curp = xmalloc(blocksize);
+               dumper->get__savp = xzalloc(blocksize); /* need to be initialized */
+       } else {
+               unsigned char *tmp = dumper->get__curp;
+               dumper->get__curp = dumper->get__savp;
+               dumper->get__savp = tmp;
+               dumper->savaddress += blocksize;
+               dumper->address = dumper->savaddress;
+       }
+       need = blocksize;
+       nread = 0;
+       while (1) {
+               /*
+                * 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 (!dumper->pub.dump_length || (dumper->get__ateof && !next(dumper))) {
+                       if (need == blocksize) {
+                               return NULL;
+                       }
+                       if (dumper->pub.dump_vflag != ALL && !memcmp(dumper->get__curp, dumper->get__savp, nread)) {
+                               if (dumper->pub.dump_vflag != DUP) {
+                                       puts("*");
+                               }
+                               return NULL;
+                       }
+                       memset(dumper->get__curp + nread, 0, need);
+                       dumper->eaddress = dumper->address + nread;
+                       return dumper->get__curp;
+               }
+               n = fread(dumper->get__curp + nread, sizeof(unsigned char),
+                               dumper->pub.dump_length == -1 ? need : MIN(dumper->pub.dump_length, need), stdin);
+               if (!n) {
+                       if (ferror(stdin)) {
+                               bb_simple_perror_msg(dumper->argv[-1]);
+                       }
+                       dumper->get__ateof = 1;
+                       continue;
+               }
+               dumper->get__ateof = 0;
+               if (dumper->pub.dump_length != -1) {
+                       dumper->pub.dump_length -= n;
+               }
+               need -= n;
+               if (!need) {
+                       if (dumper->pub.dump_vflag == ALL || dumper->pub.dump_vflag == FIRST
+                        || memcmp(dumper->get__curp, dumper->get__savp, blocksize)
+                       ) {
+                               if (dumper->pub.dump_vflag == DUP || dumper->pub.dump_vflag == FIRST) {
+                                       dumper->pub.dump_vflag = WAIT;
+                               }
+                               return dumper->get__curp;
+                       }
+                       if (dumper->pub.dump_vflag == WAIT) {
+                               puts("*");
+                       }
+                       dumper->pub.dump_vflag = DUP;
+                       dumper->savaddress += blocksize;
+                       dumper->address = dumper->savaddress;
+                       need = 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)
+               continue;
+       for (p2 = ++p1; *p1 && strchr(" -0+#", *p1); ++p1)
+               if (pr->nospace)
+                       pr->nospace--;
+       while ((*p2++ = *p1++) != 0)
+               continue;
+}
+
+static const char conv_str[] ALIGN1 =
+       "\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"
+       ;
+
+
+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';
+               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[] ALIGN1 =
+               "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(priv_dumper_t* dumper)
+{
+       FS *fs;
+       FU *fu;
+       PR *pr;
+       int cnt;
+       unsigned char *bp, *savebp;
+       off_t saveaddress;
+       unsigned char savech = '\0';
+
+       while ((bp = get(dumper)) != NULL) {
+               fs = dumper->pub.fshead;
+               savebp = bp;
+               saveaddress = dumper->address;
+               for (; fs; fs = fs->nextfs, bp = savebp, dumper->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; dumper->address += pr->bcnt,
+                                                               bp += pr->bcnt, pr = pr->nextpr) {
+                                               if (dumper->eaddress && dumper->address >= dumper->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) dumper->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:
+                                                               memcpy(&fval, bp, sizeof(fval));
+                                                               printf(pr->fmt, fval);
+                                                               break;
+                                                       case 8:
+                                                               memcpy(&dval, 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:
+                                                               memcpy(&sval, bp, sizeof(sval));
+                                                               printf(pr->fmt, (int) sval);
+                                                               break;
+                                                       case 4:
+                                                               memcpy(&ival, 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 ival;
+                                                       unsigned short sval;
+
+                                                       switch (pr->bcnt) {
+                                                       case 1:
+                                                               printf(pr->fmt, (unsigned) *bp);
+                                                               break;
+                                                       case 2:
+                                                               memcpy(&sval, bp, sizeof(sval));
+                                                               printf(pr->fmt, (unsigned) sval);
+                                                               break;
+                                                       case 4:
+                                                               memcpy(&ival, bp, sizeof(ival));
+                                                               printf(pr->fmt, ival);
+                                                               break;
+                                                       }
+                                                       break;
+                                               }
+                                               }
+                                               if (cnt == 1 && pr->nospace) {
+                                                       *pr->nospace = savech;
+                                               }
+                                       }
+                               }
+                       }
+               }
+       }
+       if (dumper->endfu) {
+               /*
+                * if eaddress not set, error or file size was multiple
+                * of blocksize, and no partial block ever found.
+                */
+               if (!dumper->eaddress) {
+                       if (!dumper->address) {
+                               return;
+                       }
+                       dumper->eaddress = dumper->address;
+               }
+               for (pr = dumper->endfu->nextpr; pr; pr = pr->nextpr) {
+                       switch (pr->flags) {
+                       case F_ADDRESS:
+                               printf(pr->fmt, (unsigned) dumper->eaddress);
+                               break;
+                       case F_TEXT:
+                               printf(pr->fmt);
+                               break;
+                       }
+               }
+       }
+}
+
+#define dumper ((priv_dumper_t*)pub_dumper)
+int FAST_FUNC bb_dump_dump(dumper_t *pub_dumper, char **argv)
+{
+       FS *tfs;
+       int blocksize;
+
+       /* figure out the data block bb_dump_size */
+       blocksize = 0;
+       tfs = dumper->pub.fshead;
+       while (tfs) {
+               tfs->bcnt = bb_dump_size(tfs);
+               if (blocksize < tfs->bcnt) {
+                       blocksize = tfs->bcnt;
+               }
+               tfs = tfs->nextfs;
+       }
+       dumper->blocksize = blocksize;
+
+       /* rewrite the rules, do syntax checking */
+       for (tfs = dumper->pub.fshead; tfs; tfs = tfs->nextfs) {
+               rewrite(dumper, tfs);
+       }
+
+       dumper->argv = argv;
+       display(dumper);
+
+       return dumper->exitval;
+}
+
+void FAST_FUNC bb_dump_add(dumper_t* pub_dumper, const char *fmt)
+{
+       const char *p;
+       char *p1;
+       char *p2;
+       FS *tfs;
+       FU *tfu, **nextfupp;
+       const char *savep;
+
+       /* start new linked list of format units */
+       tfs = xzalloc(sizeof(FS)); /*DBU:[dave@cray.com] start out NULL */
+       if (!dumper->pub.fshead) {
+               dumper->pub.fshead = tfs;
+       } else {
+               FS *fslast = dumper->pub.fshead;
+               while (fslast->nextfs)
+                       fslast = fslast->nextfs;
+               fslast->nextfs = tfs;
+       }
+       nextfupp = &tfs->nextfu;
+
+       /* take the format string and break it up into format units */
+       p = fmt;
+       for (;;) {
+               p = skip_whitespace(p);
+               if (!*p) {
+                       break;
+               }
+
+               /* allocate a new format unit and link it in */
+               /* NOSTRICT */
+               /* DBU:[dave@cray.com] zalloc so that forward pointers start out NULL */
+               tfu = xzalloc(sizeof(FU));
+               *nextfupp = tfu;
+               nextfupp = &tfu->nextfu;
+               tfu->reps = 1;
+
+               /* if leading digit, repetition count */
+               if (isdigit(*p)) {
+                       for (savep = p; isdigit(*p); ++p)
+                               continue;
+                       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;
+                       /* skip trailing white space */
+                       p = skip_whitespace(++p);
+               }
+
+               /* skip slash and trailing white space */
+               if (*p == '/') {
+                       p = skip_whitespace(++p);
+               }
+
+               /* byte count */
+               if (isdigit(*p)) {
+// TODO: use bb_strtou
+                       savep = p;
+                       while (isdigit(*++p))
+                               continue;
+                       if (!isspace(*p)) {
+                               bb_error_msg_and_die("bad format {%s}", fmt);
+                       }
+                       tfu->bcnt = atoi(savep);
+                       /* 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 = xstrndup(savep, p - savep);
+/*      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 (file)
index 0000000..802fd57
--- /dev/null
@@ -0,0 +1,19 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Utility routines.
+ *
+ * Copyright (C) 1999-2004 by Erik Andersen <andersen@codepoet.org>
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.h"
+
+void FAST_FUNC 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 (file)
index 0000000..243433b
--- /dev/null
@@ -0,0 +1,20 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Utility routines.
+ *
+ * Copyright (C) 1999-2004 by Erik Andersen <andersen@codepoet.org>
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.h"
+
+void FAST_FUNC bb_error_msg_and_die(const char *s, ...)
+{
+       va_list p;
+
+       va_start(p, s);
+       bb_verror_msg(s, p, NULL);
+       va_end(p);
+       xfunc_die();
+}
diff --git a/libbb/execable.c b/libbb/execable.c
new file mode 100644 (file)
index 0000000..5c7ac16
--- /dev/null
@@ -0,0 +1,78 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Utility routines.
+ *
+ * Copyright (C) 2006 Gabriel Somlo <somlo at cmu.edu>
+ *
+ * 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 FAST_FUNC execable_file(const char *name)
+{
+       struct stat s;
+       return (!access(name, X_OK) && !stat(name, &s) && S_ISREG(s.st_mode));
+}
+
+/* search (*PATHp) for an executable file;
+ * return allocated string containing full path if found;
+ *  PATHp points to the component after the one where it was found
+ *  (or NULL),
+ *  you may call find_execable again with this PATHp to continue
+ *  (if it's not NULL).
+ * return NULL otherwise; (PATHp is undefined)
+ * in all cases (*PATHp) contents will be trashed (s/:/NUL/).
+ */
+char* FAST_FUNC find_execable(const char *filename, char **PATHp)
+{
+       char *p, *n;
+
+       p = *PATHp;
+       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)) {
+                               *PATHp = n;
+                               return p;
+                       }
+                       free(p);
+               }
+               p = n;
+       } /* on loop exit p == NULL */
+       return p;
+}
+
+/* search $PATH for an executable file;
+ * return 1 if found;
+ * return 0 otherwise;
+ */
+int FAST_FUNC exists_execable(const char *filename)
+{
+       char *path = xstrdup(getenv("PATH"));
+       char *tmp = path;
+       char *ret = find_execable(filename, &tmp);
+       free(path);
+       if (ret) {
+               free(ret);
+               return 1;
+       }
+       return 0;
+}
+
+#if ENABLE_FEATURE_PREFER_APPLETS
+/* just like the real execvp, but try to launch an applet named 'file' first
+ */
+int FAST_FUNC bb_execvp(const char *file, char *const argv[])
+{
+       return execvp(find_applet_by_name(file) >= 0 ? bb_busybox_exec_path : file,
+                                       argv);
+}
+#endif
diff --git a/libbb/fclose_nonstdin.c b/libbb/fclose_nonstdin.c
new file mode 100644 (file)
index 0000000..6f3f373
--- /dev/null
@@ -0,0 +1,25 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * fclose_nonstdin implementation for busybox
+ *
+ * Copyright (C) 2003  Manuel Novoa III  <mjn3@codepoet.org>
+ *
+ * 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 FAST_FUNC fclose_if_not_stdin(FILE *f)
+{
+       /* Some more paranoid applets want ferror() check too */
+       int r = ferror(f); /* NB: does NOT set errno! */
+       if (r) errno = EIO; /* so we'll help it */
+       if (f != stdin)
+               return (r | fclose(f)); /* fclose does set errno on error */
+       return r;
+}
diff --git a/libbb/fflush_stdout_and_exit.c b/libbb/fflush_stdout_and_exit.c
new file mode 100644 (file)
index 0000000..742fb9f
--- /dev/null
@@ -0,0 +1,29 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * fflush_stdout_and_exit implementation for busybox
+ *
+ * Copyright (C) 2003  Manuel Novoa III  <mjn3@codepoet.org>
+ *
+ * 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 FAST_FUNC fflush_stdout_and_exit(int retval)
+{
+       if (fflush(stdout))
+               bb_perror_msg_and_die(bb_msg_standard_output);
+
+       if (ENABLE_FEATURE_PREFER_APPLETS && die_sleep < 0) {
+               /* We are in NOFORK applet. Do not exit() directly,
+                * but use xfunc_die() */
+               xfunc_error_retval = retval;
+               xfunc_die();
+       }
+
+       exit(retval);
+}
diff --git a/libbb/fgets_str.c b/libbb/fgets_str.c
new file mode 100644 (file)
index 0000000..3fe61cd
--- /dev/null
@@ -0,0 +1,86 @@
+/* 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"
+
+static char *xmalloc_fgets_internal(FILE *file, const char *terminating_string, int chop_off, size_t *maxsz_p)
+{
+       char *linebuf = NULL;
+       const int term_length = strlen(terminating_string);
+       int end_string_offset;
+       int linebufsz = 0;
+       int idx = 0;
+       int ch;
+       size_t maxsz = *maxsz_p;
+
+       while (1) {
+               ch = fgetc(file);
+               if (ch == EOF) {
+                       if (idx == 0)
+                               return linebuf; /* NULL */
+                       break;
+               }
+
+               if (idx >= linebufsz) {
+                       linebufsz += 200;
+                       linebuf = xrealloc(linebuf, linebufsz);
+                       if (idx >= maxsz) {
+                               linebuf[idx] = ch;
+                               idx++;
+                               break;
+                       }
+               }
+
+               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
+               ) {
+                       if (chop_off)
+                               idx -= term_length;
+                       break;
+               }
+       }
+       /* Grow/shrink *first*, then store NUL */
+       linebuf = xrealloc(linebuf, idx + 1);
+       linebuf[idx] = '\0';
+       *maxsz_p = idx;
+       return linebuf;
+}
+
+/* Read up to TERMINATING_STRING from FILE and return it,
+ * including terminating string.
+ * Non-terminated string can be returned if EOF is reached.
+ * Return NULL if EOF is reached immediately.  */
+char* FAST_FUNC xmalloc_fgets_str(FILE *file, const char *terminating_string)
+{
+       size_t maxsz = INT_MAX - 4095;
+       return xmalloc_fgets_internal(file, terminating_string, 0, &maxsz);
+}
+
+char* FAST_FUNC xmalloc_fgets_str_len(FILE *file, const char *terminating_string, size_t *maxsz_p)
+{
+       size_t maxsz;
+
+       if (!maxsz_p) {
+               maxsz = INT_MAX - 4095;
+               maxsz_p = &maxsz;
+       }
+       return xmalloc_fgets_internal(file, terminating_string, 0, maxsz_p);
+}
+
+char* FAST_FUNC xmalloc_fgetline_str(FILE *file, const char *terminating_string)
+{
+       size_t maxsz = INT_MAX - 4095;
+       return xmalloc_fgets_internal(file, terminating_string, 1, &maxsz);
+}
diff --git a/libbb/find_mount_point.c b/libbb/find_mount_point.c
new file mode 100644 (file)
index 0000000..12b2cfc
--- /dev/null
@@ -0,0 +1,61 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Utility routines.
+ *
+ * Copyright (C) 1999-2004 by Erik Andersen <andersen@codepoet.org>
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.h"
+#include <mntent.h>
+
+/*
+ * 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* FAST_FUNC find_mount_point(const char *name)
+{
+       struct stat s;
+       dev_t mountDevice;
+       FILE *mountTable;
+       struct mntent *mountEntry;
+
+       if (stat(name, &s) != 0)
+               return NULL;
+
+       if (S_ISBLK(s.st_mode))
+               mountDevice = s.st_rdev;
+       else
+               mountDevice = s.st_dev;
+
+
+       mountTable = setmntent(bb_path_mtab_file, "r");
+       if (!mountTable)
+               return 0;
+
+       while ((mountEntry = getmntent(mountTable)) != NULL) {
+               /* rootfs mount in Linux 2.6 exists always,
+                * and it makes sense to always ignore it.
+                * Otherwise people can't reference their "real" root! */
+               if (strcmp(mountEntry->mnt_fsname, "rootfs") == 0)
+                       continue;
+
+               if (strcmp(name, mountEntry->mnt_dir) == 0
+                || strcmp(name, mountEntry->mnt_fsname) == 0
+               ) { /* String match. */
+                       break;
+               }
+               /* Match the device. */
+               if (stat(mountEntry->mnt_fsname, &s) == 0 && s.st_rdev == mountDevice)
+                       break;
+               /* Match the directory's mount point. */
+               if (stat(mountEntry->mnt_dir, &s) == 0 && s.st_dev == mountDevice)
+                       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 (file)
index 0000000..600d4e1
--- /dev/null
@@ -0,0 +1,116 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Utility routines.
+ *
+ * Copyright (C) 1999-2004 by Erik Andersen <andersen@codepoet.org>
+ *
+ * Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
+ */
+
+#include "libbb.h"
+
+/*
+In Linux we have three ways to determine "process name":
+1. /proc/PID/stat has "...(name)...", among other things. It's so-called "comm" field.
+2. /proc/PID/cmdline's first NUL-terminated string. It's argv[0] from exec syscall.
+3. /proc/PID/exe symlink. Points to the running executable file.
+
+kernel threads:
+ comm: thread name
+ cmdline: empty
+ exe: <readlink fails>
+
+executable
+ comm: first 15 chars of base name
+ (if executable is a symlink, then first 15 chars of symlink name are used)
+ cmdline: argv[0] from exec syscall
+ exe: points to executable (resolves symlink, unlike comm)
+
+script (an executable with #!/path/to/interpreter):
+ comm: first 15 chars of script's base name (symlinks are not resolved)
+ cmdline: /path/to/interpreter (symlinks are not resolved)
+ (script name is in argv[1], args are pushed into argv[2] etc)
+ exe: points to interpreter's executable (symlinks are resolved)
+
+If FEATURE_PREFER_APPLETS=y (and more so if FEATURE_SH_STANDALONE=y),
+some commands started from busybox shell, xargs or find are started by
+execXXX("/proc/self/exe", applet_name, params....)
+and therefore comm field contains "exe".
+*/
+
+static int comm_match(procps_status_t *p, const char *procName)
+{
+       int argv1idx;
+
+       /* comm does not match */
+       if (strncmp(p->comm, procName, 15) != 0)
+               return 0;
+
+       /* in Linux, if comm is 15 chars, it may be a truncated */
+       if (p->comm[14] == '\0') /* comm is not truncated - match */
+               return 1;
+
+       /* comm is truncated, but first 15 chars match.
+        * This can be crazily_long_script_name.sh!
+        * The telltale sign is basename(argv[1]) == procName. */
+
+       if (!p->argv0)
+               return 0;
+
+       argv1idx = strlen(p->argv0) + 1;
+       if (argv1idx >= p->argv_len)
+               return 0;
+
+       if (strcmp(bb_basename(p->argv0 + argv1idx), procName) != 0)
+               return 0;
+
+       return 1;
+}
+
+/* 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.
+ *
+ * Modified by Vladimir Oleynik for use with libbb/procps.c
+ */
+pid_t* FAST_FUNC find_pid_by_name(const char *procName)
+{
+       pid_t* pidList;
+       int i = 0;
+       procps_status_t* p = NULL;
+
+       pidList = xzalloc(sizeof(*pidList));
+       while ((p = procps_scan(p, PSSCAN_PID|PSSCAN_COMM|PSSCAN_ARGVN))) {
+               if (comm_match(p, procName)
+               /* or we require argv0 to match (essential for matching reexeced /proc/self/exe)*/
+                || (p->argv0 && strcmp(bb_basename(p->argv0), procName) == 0)
+               /* TODO: we can also try /proc/NUM/exe link, do we want that? */
+               ) {
+                       pidList = xrealloc_vector(pidList, 2, i);
+                       pidList[i++] = p->pid;
+               }
+       }
+
+       pidList[i] = 0;
+       return pidList;
+}
+
+pid_t* FAST_FUNC 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 (file)
index 0000000..ca46bf5
--- /dev/null
@@ -0,0 +1,74 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Utility routines.
+ *
+ * Copyright (C) 1999-2004 by Erik Andersen <andersen@codepoet.org>
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.h"
+
+/* Find block device /dev/XXX which contains specified file
+ * We handle /dev/dir/dir/dir too, at a cost of ~80 more bytes code */
+
+/* Do not reallocate all this stuff on each recursion */
+enum { DEVNAME_MAX = 256 };
+struct arena {
+       struct stat st;
+       dev_t dev;
+       /* Was PATH_MAX, but we recurse _/dev_. We can assume
+        * people are not crazy enough to have mega-deep tree there */
+       char devpath[DEVNAME_MAX];
+};
+
+static char *find_block_device_in_dir(struct arena *ap)
+{
+       DIR *dir;
+       struct dirent *entry;
+       char *retpath = NULL;
+       int len, rem;
+
+       dir = opendir(ap->devpath);
+       if (!dir)
+               return NULL;
+
+       len = strlen(ap->devpath);
+       rem = DEVNAME_MAX-2 - len;
+       if (rem <= 0)
+               return NULL;
+       ap->devpath[len++] = '/';
+
+       while ((entry = readdir(dir)) != NULL) {
+               safe_strncpy(ap->devpath + len, entry->d_name, rem);
+               /* lstat: do not follow links */
+               if (lstat(ap->devpath, &ap->st) != 0)
+                       continue;
+               if (S_ISBLK(ap->st.st_mode) && ap->st.st_rdev == ap->dev) {
+                       retpath = xstrdup(ap->devpath);
+                       break;
+               }
+               if (S_ISDIR(ap->st.st_mode)) {
+                       /* Do not recurse for '.' and '..' */
+                       if (DOT_OR_DOTDOT(entry->d_name))
+                               continue;
+                       retpath = find_block_device_in_dir(ap);
+                       if (retpath)
+                               break;
+               }
+       }
+       closedir(dir);
+
+       return retpath;
+}
+
+char* FAST_FUNC find_block_device(const char *path)
+{
+       struct arena a;
+
+       if (stat(path, &a.st) != 0)
+               return NULL;
+       a.dev = S_ISBLK(a.st.st_mode) ? a.st.st_rdev : a.st.st_dev;
+       strcpy(a.devpath, "/dev");
+       return find_block_device_in_dir(&a);
+}
diff --git a/libbb/full_write.c b/libbb/full_write.c
new file mode 100644 (file)
index 0000000..f353b7d
--- /dev/null
@@ -0,0 +1,42 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Utility routines.
+ *
+ * Copyright (C) 1999-2004 by Erik Andersen <andersen@codepoet.org>
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+#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 FAST_FUNC 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) {
+                       if (total) {
+                               /* we already wrote some! */
+                               /* user can do another write to know the error code */
+                               return total;
+                       }
+                       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 (file)
index 0000000..74022b5
--- /dev/null
@@ -0,0 +1,80 @@
+/* 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"
+
+/* From <linux/kd.h> */
+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 FAST_FUNC get_console_fd_or_die(void)
+{
+       static const char *const console_names[] = {
+               DEV_CONSOLE, CURRENT_VC, CURRENT_TTY
+       };
+
+       int fd;
+
+       for (fd = 2; fd >= 0; fd--) {
+               int fd4name;
+               int choice_fd;
+               char arg;
+
+               fd4name = open_a_console(console_names[fd]);
+ chk_std:
+               choice_fd = (fd4name >= 0 ? fd4name : fd);
+
+               arg = 0;
+               if (ioctl(choice_fd, KDGKBTYPE, &arg) == 0)
+                       return choice_fd;
+               if (fd4name >= 0) {
+                       close(fd4name);
+                       fd4name = -1;
+                       goto chk_std;
+               }
+       }
+
+       bb_error_msg_and_die("can't open console");
+       /*return fd; - total failure */
+}
+
+/* From <linux/vt.h> */
+enum {
+       VT_ACTIVATE = 0x5606,   /* make vt active */
+       VT_WAITACTIVE = 0x5607  /* wait for vt active */
+};
+
+void FAST_FUNC console_make_active(int fd, const int vt_num)
+{
+       xioctl(fd, VT_ACTIVATE, (void *)(ptrdiff_t)vt_num);
+       xioctl(fd, VT_WAITACTIVE, (void *)(ptrdiff_t)vt_num);
+}
diff --git a/libbb/get_last_path_component.c b/libbb/get_last_path_component.c
new file mode 100644 (file)
index 0000000..7c99116
--- /dev/null
@@ -0,0 +1,42 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * bb_get_last_path_component implementation for busybox
+ *
+ * Copyright (C) 2001  Manuel Novoa III  <mjn3@codepoet.org>
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.h"
+/*
+ * "/"        -> "/"
+ * "abc"      -> "abc"
+ * "abc/def"  -> "def"
+ * "abc/def/" -> ""
+ */
+char* FAST_FUNC bb_get_last_path_component_nostrip(const char *path)
+{
+       char *slash = strrchr(path, '/');
+
+       if (!slash || (slash == path && !slash[1]))
+               return (char*)path;
+
+       return slash + 1;
+}
+
+/*
+ * "/"        -> "/"
+ * "abc"      -> "abc"
+ * "abc/def"  -> "def"
+ * "abc/def/" -> "def" !!
+ */
+char* FAST_FUNC bb_get_last_path_component_strip(char *path)
+{
+       char *slash = last_char_is(path, '/');
+
+       if (slash)
+               while (*slash == '/' && slash != path)
+                       *slash-- = '\0';
+
+       return bb_get_last_path_component_nostrip(path);
+}
diff --git a/libbb/get_line_from_file.c b/libbb/get_line_from_file.c
new file mode 100644 (file)
index 0000000..3cb46d2
--- /dev/null
@@ -0,0 +1,207 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Utility routines.
+ *
+ * Copyright (C) 2005, 2006 Rob Landley <rob@landley.net>
+ * Copyright (C) 2004 Erik Andersen <andersen@codepoet.org>
+ * Copyright (C) 2001 Matt Krai
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+/* for getline() [GNUism]
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE 1
+#endif
+*/
+#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 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 is stored in it.
+ * If lineno is not NULL, *lineno is incremented for each line,
+ * and also trailing '\' is recognized as line continuation.
+ *
+ * Returns NULL if EOF/error. */
+char* FAST_FUNC bb_get_chunk_with_continuation(FILE *file, int *end, int *lineno)
+{
+       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) {
+                       linebufsz += 256;
+                       linebuf = xrealloc(linebuf, linebufsz);
+               }
+               linebuf[idx++] = (char) ch;
+               if (!ch)
+                       break;
+               if (end && ch == '\n') {
+                       if (lineno == NULL)
+                               break;
+                       (*lineno)++;
+                       if (idx < 2 || linebuf[idx-2] != '\\')
+                               break;
+                       idx -= 2;
+               }
+       }
+       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;
+}
+
+char* FAST_FUNC bb_get_chunk_from_file(FILE *file, int *end)
+{
+       return bb_get_chunk_with_continuation(file, end, NULL);
+}
+
+/* Get line, including trailing \n if any */
+char* FAST_FUNC xmalloc_fgets(FILE *file)
+{
+       int i;
+
+       return bb_get_chunk_from_file(file, &i);
+}
+/* Get line.  Remove trailing \n */
+char* FAST_FUNC xmalloc_fgetline(FILE *file)
+{
+       int i;
+       char *c = bb_get_chunk_from_file(file, &i);
+
+       if (i && c[--i] == '\n')
+               c[i] = '\0';
+
+       return c;
+}
+
+#if 0
+/* GNUism getline() should be faster (not tested) than a loop with fgetc */
+
+/* Get line, including trailing \n if any */
+char* FAST_FUNC xmalloc_fgets(FILE *file)
+{
+       char *res_buf = NULL;
+       size_t res_sz;
+
+       if (getline(&res_buf, &res_sz, file) == -1) {
+               free(res_buf); /* uclibc allocates a buffer even on EOF. WTF? */
+               res_buf = NULL;
+       }
+//TODO: trimming to res_sz?
+       return res_buf;
+}
+/* Get line.  Remove trailing \n */
+char* FAST_FUNC xmalloc_fgetline(FILE *file)
+{
+       char *res_buf = NULL;
+       size_t res_sz;
+
+       res_sz = getline(&res_buf, &res_sz, file);
+
+       if ((ssize_t)res_sz != -1) {
+               if (res_buf[res_sz - 1] == '\n')
+                       res_buf[--res_sz] = '\0';
+//TODO: trimming to res_sz?
+       } else {
+               free(res_buf); /* uclibc allocates a buffer even on EOF. WTF? */
+               res_buf = NULL;
+       }
+       return res_buf;
+}
+
+#endif
+
+#if 0
+/* Faster routines (~twice as fast). +170 bytes. Unused as of 2008-07.
+ *
+ * NB: they stop at NUL byte too.
+ * Performance is important here. Think "grep 50gigabyte_file"...
+ * Ironically, grep can't use it because of NUL issue.
+ * We sorely need C lib to provide fgets which reports size!
+ *
+ * Update:
+ * Actually, uclibc and glibc have it. man getline. It's GNUism,
+ *   but very useful one (if it's as fast as this code).
+ * TODO:
+ * - currently, sed and sort use bb_get_chunk_from_file and heavily
+ *   depend on its "stop on \n or \0" behavior, and STILL they fail
+ *   to handle all cases with embedded NULs correctly. So:
+ * - audit sed and sort; convert them to getline FIRST.
+ * - THEN ditch bb_get_chunk_from_file, replace it with getline.
+ * - provide getline implementation for non-GNU systems.
+ */
+
+static char* xmalloc_fgets_internal(FILE *file, int *sizep)
+{
+       int len;
+       int idx = 0;
+       char *linebuf = NULL;
+
+       while (1) {
+               char *r;
+
+               linebuf = xrealloc(linebuf, idx + 0x100);
+               r = fgets(&linebuf[idx], 0x100, file);
+               if (!r) {
+                       /* need to terminate in case this is error
+                        * (EOF puts NUL itself) */
+                       linebuf[idx] = '\0';
+                       break;
+               }
+               /* stupid. fgets knows the len, it should report it somehow */
+               len = strlen(&linebuf[idx]);
+               idx += len;
+               if (len != 0xff || linebuf[idx - 1] == '\n')
+                       break;
+       }
+       *sizep = idx;
+       if (idx) {
+               /* xrealloc(linebuf, idx + 1) is up to caller */
+               return linebuf;
+       }
+       free(linebuf);
+       return NULL;
+}
+
+/* Get line, remove trailing \n */
+char* FAST_FUNC xmalloc_fgetline_fast(FILE *file)
+{
+       int sz;
+       char *r = xmalloc_fgets_internal(file, &sz);
+       if (r && r[sz - 1] == '\n')
+               r[--sz] = '\0';
+       return r; /* not xrealloc(r, sz + 1)! */
+}
+
+char* FAST_FUNC xmalloc_fgets(FILE *file)
+{
+       int sz;
+       return xmalloc_fgets_internal(file, &sz);
+}
+
+/* Get line, remove trailing \n */
+char* FAST_FUNC xmalloc_fgetline(FILE *file)
+{
+       int sz;
+       char *r = xmalloc_fgets_internal(file, &sz);
+       if (!r)
+               return r;
+       if (r[sz - 1] == '\n')
+               r[--sz] = '\0';
+       return xrealloc(r, sz + 1);
+}
+#endif
diff --git a/libbb/getopt32.c b/libbb/getopt32.c
new file mode 100644 (file)
index 0000000..5190fa6
--- /dev/null
@@ -0,0 +1,592 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * universal getopt32 implementation for busybox
+ *
+ * Copyright (C) 2003-2005  Vladimir Oleynik  <dzo@simtreas.ru>
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+#include <getopt.h>
+#include "libbb.h"
+
+/*      Documentation
+
+uint32_t
+getopt32(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(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(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(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 char *applet_long_options
+
+        This struct allows you to define long options:
+
+        static const char applet_longopts[] ALIGN1 =
+               //"name\0" has_arg val
+               "verbose\0" No_argument "v"
+               ;
+        applet_long_options = applet_longopts;
+
+        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_<applet>_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(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; // must be initialized!
+        opt_complementary = "ww";
+        getopt32(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(argv, "vb:c", &my_b, &verbose_level);
+        if (f & 2)       // -c after -b unsets -b flag
+                while (my_b) dosomething_with(llist_pop(&my_b));
+        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(argv, "wx");
+
+        Allows any arguments to be given without a dash (./program w x)
+        as well as with a dash (./program -x).
+
+        NB: getopt32() will leak a small amount of memory if you use
+        this option! Do not use it if there is a possibility of recursive
+        getopt32() calls.
+
+ "--"   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
+
+        NB: getopt32() will leak a small amount of memory if you use
+        this option! Do not use it if there is a possibility of recursive
+        getopt32() calls.
+
+ "-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-b"  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(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--b" 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 mutually 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 will call
+       bb_show_usage() and die.
+
+ "x--x" Variation of the above, it means that -x option should occur
+        at most once.
+
+ "a+"   A plus after a char in opt_complementary means that the parameter
+        for this option is a nonnegative integer. It will be processed
+        with xatoi_u() - allowed range is 0..INT_MAX.
+
+        int param;  // "unsigned param;" will also work
+        opt_complementary = "p+";
+        getopt32(argv, "p:", &param);
+
+ "a::"  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_end(llist_t **old_head, char *new_item).)
+
+        opt_complementary = "e::";
+
+        getopt32(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
+
+ "a?b"  A "?" 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(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(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.
+        But it's far too obfuscated. Use ':' to separate groups.
+*/
+
+/* Code here assumes that 'unsigned' is at least 32 bits wide */
+
+const char *const bb_argv_dash[] = { "-", NULL };
+
+const char *opt_complementary;
+
+enum {
+       PARAM_STRING,
+       PARAM_LIST,
+       PARAM_INT,
+};
+
+typedef struct {
+       unsigned char opt_char;
+       smallint param_type;
+       unsigned switch_on;
+       unsigned switch_off;
+       unsigned incongruously;
+       unsigned requires;
+       void **optarg;  /* char**, llist_t** or int *. */
+       int *counter;
+} t_complementary;
+
+/* You can set applet_long_options for parse called long options */
+#if ENABLE_GETOPT_LONG
+static const struct option bb_null_long_options[1] = {
+       { 0, 0, 0, 0 }
+};
+const char *applet_long_options;
+#endif
+
+uint32_t option_mask32;
+
+uint32_t FAST_FUNC
+getopt32(char **argv, const char *applet_opts, ...)
+{
+       int argc;
+       unsigned flags = 0;
+       unsigned requires = 0;
+       t_complementary complementary[33]; /* last stays zero-filled */
+       int c;
+       const unsigned char *s;
+       t_complementary *on_off;
+       va_list p;
+#if ENABLE_GETOPT_LONG
+       const struct option *l_o;
+       struct option *long_options = (struct option *) &bb_null_long_options;
+#endif
+       unsigned trigger;
+       char **pargv;
+       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
+
+       int spec_flgs = 0;
+
+       /* skip 0: some applets cheat: they do not actually HAVE argv[0] */
+       argc = 1;
+       while (argv[argc])
+               argc++;
+
+       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_char = *s;
+               on_off->switch_on = (1 << c);
+               if (*++s == ':') {
+                       on_off->optarg = va_arg(p, void **);
+                       while (*++s == ':')
+                               continue;
+               }
+               on_off++;
+               c++;
+       }
+
+#if ENABLE_GETOPT_LONG
+       if (applet_long_options) {
+               const char *optstr;
+               unsigned i, count;
+
+               count = 1;
+               optstr = applet_long_options;
+               while (optstr[0]) {
+                       optstr += strlen(optstr) + 3; /* skip NUL, has_arg, val */
+                       count++;
+               }
+               /* count == no. of longopts + 1 */
+               long_options = alloca(count * sizeof(*long_options));
+               memset(long_options, 0, count * sizeof(*long_options));
+               i = 0;
+               optstr = applet_long_options;
+               while (--count) {
+                       long_options[i].name = optstr;
+                       optstr += strlen(optstr) + 1;
+                       long_options[i].has_arg = (unsigned char)(*optstr++);
+                       /* long_options[i].flag = NULL; */
+                       long_options[i].val = (unsigned char)(*optstr++);
+                       i++;
+               }
+               for (l_o = long_options; l_o->name; l_o++) {
+                       if (l_o->flag)
+                               continue;
+                       for (on_off = complementary; on_off->opt_char; on_off++)
+                               if (on_off->opt_char == l_o->val)
+                                       goto next_long;
+                       if (c >= 32)
+                               break;
+                       on_off->opt_char = 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_char; on_off++)
+                       if (on_off->opt_char == *s)
+                               break;
+               if (c == ':' && s[2] == ':') {
+                       on_off->param_type = PARAM_LIST;
+                       continue;
+               }
+               if (c == '+' && (s[2] == ':' || s[2] == '\0')) {
+                       on_off->param_type = PARAM_INT;
+                       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_char; on_off++)
+                                       if (on_off->opt_char == *s) {
+                                               *pair_switch |= on_off->switch_on;
+                                               break;
+                                       }
+                       }
+               }
+               s--;
+       }
+       va_end(p);
+
+       if (spec_flgs & (FIRST_ARGV_IS_OPT | ALL_ARGV_IS_OPTS)) {
+               pargv = argv + 1;
+               while (*pargv) {
+                       if (pargv[0][0] != '-' && pargv[0][0] != '\0') {
+                               /* Can't use alloca: opts with params will
+                                * return pointers to stack!
+                                * NB: we leak these allocations... */
+                               char *pp = xmalloc(strlen(*pargv) + 2);
+                               *pp = '-';
+                               strcpy(pp + 1, *pargv);
+                               *pargv = pp;
+                       }
+                       if (!(spec_flgs & ALL_ARGV_IS_OPTS))
+                               break;
+                       pargv++;
+               }
+       }
+
+       /* In case getopt32 was already called:
+        * reset the libc getopt() function, which keeps internal state.
+        * run_nofork_applet_prime() does this, but we might end up here
+        * also via gunzip_main() -> gzip_main(). Play safe.
+        */
+#ifdef __GLIBC__
+       optind = 0;
+#else /* BSD style */
+       optind = 1;
+       /* optreset = 1; */
+#endif
+       /* optarg = NULL; opterr = 0; optopt = 0; - do we need this?? */
+
+       pargv = NULL;
+
+       /* Note: just "getopt() <= 0" will not work well 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,
+                       long_options, NULL)) != -1) {
+#else
+       while ((c = getopt(argc, argv, applet_opts)) != -1) {
+#endif
+               /* getopt prints "option requires an argument -- X"
+                * and returns '?' if an option has no arg, but one is reqd */
+               c &= 0xff; /* fight libc's sign extension */
+               for (on_off = complementary; on_off->opt_char != c; on_off++) {
+                       /* c can be NUL if long opt has non-NULL ->flag,
+                        * but we construct long opts so that flag
+                        * is always NULL (see above) */
+                       if (on_off->opt_char == '\0' /* && c != '\0' */) {
+                               /* c is probably '?' - "bad option" */
+                               bb_show_usage();
+                       }
+               }
+               if (flags & on_off->incongruously)
+                       bb_show_usage();
+               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->param_type == PARAM_LIST) {
+                       if (optarg)
+                               llist_add_to_end((llist_t **)(on_off->optarg), optarg);
+               } else if (on_off->param_type == PARAM_INT) {
+                       if (optarg)
+//TODO: xatoi_u indirectly pulls in printf machinery
+                               *(unsigned*)(on_off->optarg) = xatoi_u(optarg);
+               } else if (on_off->optarg) {
+                       if (optarg)
+                               *(char **)(on_off->optarg) = optarg;
+               }
+               if (pargv != NULL)
+                       break;
+       }
+
+       /* check depending requires for given options */
+       for (on_off = complementary; on_off->opt_char; 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/getpty.c b/libbb/getpty.c
new file mode 100644 (file)
index 0000000..4bffd9a
--- /dev/null
@@ -0,0 +1,64 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Mini getpty implementation for busybox
+ * Bjorn Wesen, Axis Communications AB (bjornw@axis.com)
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.h"
+
+#define DEBUG 0
+
+int FAST_FUNC xgetpty(char *line)
+{
+       int p;
+
+#if ENABLE_FEATURE_DEVPTS
+       p = open("/dev/ptmx", O_RDWR);
+       if (p > 0) {
+               grantpt(p); /* chmod+chown corresponding slave pty */
+               unlockpt(p); /* (what does this do?) */
+#if 0 /* if ptsname_r is not available... */
+               const char *name;
+               name = ptsname(p); /* find out the name of slave pty */
+               if (!name) {
+                       bb_perror_msg_and_die("ptsname error (is /dev/pts mounted?)");
+               }
+               safe_strncpy(line, name, GETPTY_BUFSIZE);
+#else
+               /* find out the name of slave pty */
+               if (ptsname_r(p, line, GETPTY_BUFSIZE-1) != 0) {
+                       bb_perror_msg_and_die("ptsname error (is /dev/pts mounted?)");
+               }
+               line[GETPTY_BUFSIZE-1] = '\0';
+#endif
+               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 */
+       bb_error_msg_and_die("can't find free pty");
+}
diff --git a/libbb/herror_msg.c b/libbb/herror_msg.c
new file mode 100644 (file)
index 0000000..7e4f640
--- /dev/null
@@ -0,0 +1,19 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Utility routines.
+ *
+ * Copyright (C) 1999-2004 by Erik Andersen <andersen@codepoet.org>
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.h"
+
+void FAST_FUNC bb_herror_msg(const char *s, ...)
+{
+       va_list p;
+
+       va_start(p, s);
+       bb_verror_msg(s, p, hstrerror(h_errno));
+       va_end(p);
+}
diff --git a/libbb/herror_msg_and_die.c b/libbb/herror_msg_and_die.c
new file mode 100644 (file)
index 0000000..230fe64
--- /dev/null
@@ -0,0 +1,20 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Utility routines.
+ *
+ * Copyright (C) 1999-2004 by Erik Andersen <andersen@codepoet.org>
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.h"
+
+void FAST_FUNC bb_herror_msg_and_die(const char *s, ...)
+{
+       va_list p;
+
+       va_start(p, s);
+       bb_verror_msg(s, p, hstrerror(h_errno));
+       va_end(p);
+       xfunc_die();
+}
diff --git a/libbb/human_readable.c b/libbb/human_readable.c
new file mode 100644 (file)
index 0000000..05e7d86
--- /dev/null
@@ -0,0 +1,99 @@
+/* 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.
+ *
+ * Licensed under GPLv2, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.h"
+
+const char* FAST_FUNC 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 unit_chars[] ALIGN1 = {
+               '\0', 'K', 'M', 'G', 'T', 'P', 'E'
+       };
+       static const char fmt[] ALIGN1 = "%llu";
+       static const char fmt_tenths[] ALIGN1 = "%llu.%d%c";
+
+       static char str[21] ALIGN1;  /* Sufficient for 64 bit unsigned integers */
+
+       unsigned long long val;
+       int frac;
+       const char *u;
+       const char *f;
+       smallint no_tenths;
+
+       if (size == 0)
+               return "0";
+
+       /* If block_size is 0 then do not print tenths */
+       no_tenths = 0;
+       if (block_size == 0) {
+               no_tenths = 1;
+               block_size = 1;
+       }
+
+       u = unit_chars;
+       val = size * block_size;
+       f = fmt;
+       frac = 0;
+
+       if (display_unit) {
+               val += display_unit/2;  /* Deal with rounding */
+               val /= display_unit;    /* Don't combine with the line above!!! */
+               /* will just print it as ulonglong (below) */
+       } else {
+               while ((val >= 1024)
+                && (u < unit_chars + sizeof(unit_chars) - 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 1
+               /* Sample code to omit decimal point and tenths digit. */
+               if (no_tenths) {
+                       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 (file)
index 0000000..fa4d867
--- /dev/null
@@ -0,0 +1,221 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * stolen from net-tools-1.59 and stripped down for busybox by
+ *                      Erik Andersen <andersen@codepoet.org>
+ *
+ * Heavily modified by Manuel Novoa III       Mar 12, 2001
+ *
+ * Licensed under GPLv2, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.h"
+#include "inet_common.h"
+
+int FAST_FUNC INET_resolve(const char *name, struct sockaddr_in *s_in, int hostfirst)
+{
+       struct hostent *hp;
+#if ENABLE_FEATURE_ETC_NETWORKS
+       struct netent *np;
+#endif
+
+       /* 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);
+               if (hp != NULL) {
+                       memcpy(&s_in->sin_addr, hp->h_addr_list[0],
+                               sizeof(struct in_addr));
+                       return 0;
+               }
+       }
+#if ENABLE_FEATURE_ETC_NETWORKS
+       /* Try the NETWORKS database to see if this is a known network. */
+#ifdef DEBUG
+       bb_error_msg("getnetbyname(%s)", name);
+#endif
+       np = getnetbyname(name);
+       if (np != NULL) {
+               s_in->sin_addr.s_addr = htonl(np->n_net);
+               return 1;
+       }
+#endif
+       if (hostfirst) {
+               /* Don't try again */
+               return -1;
+       }
+#ifdef DEBUG
+       res_init();
+       _res.options |= RES_DEBUG;
+       bb_error_msg("gethostbyname(%s)", name);
+#endif
+       hp = gethostbyname(name);
+       if (hp == NULL) {
+               return -1;
+       }
+       memcpy(&s_in->sin_addr, hp->h_addr_list[0], sizeof(struct in_addr));
+       return 0;
+}
+
+
+/* numeric: & 0x8000: default instead of *,
+ *          & 0x4000: host instead of net,
+ *          & 0x0fff: don't resolve
+ */
+char* FAST_FUNC INET_rresolve(struct sockaddr_in *s_in, int numeric, uint32_t netmask)
+{
+       /* addr-to-name cache */
+       struct addr {
+               struct addr *next;
+               struct sockaddr_in addr;
+               int host;
+               char name[1];
+       };
+       static struct addr *cache = NULL;
+
+       struct addr *pn;
+       char *name;
+       uint32_t ad, host_ad;
+       int host = 0;
+
+       if (s_in->sin_family != AF_INET) {
+#ifdef DEBUG
+               bb_error_msg("rresolve: unsupported address family %d!",
+                                 s_in->sin_family);
+#endif
+               errno = EAFNOSUPPORT;
+               return NULL;
+       }
+       ad = s_in->sin_addr.s_addr;
+#ifdef DEBUG
+       bb_error_msg("rresolve: %08x, mask %08x, num %08x", (unsigned)ad, netmask, numeric);
+#endif
+       if (ad == INADDR_ANY) {
+               if ((numeric & 0x0FFF) == 0) {
+                       if (numeric & 0x8000)
+                               return xstrdup(bb_str_default);
+                       return xstrdup("*");
+               }
+       }
+       if (numeric & 0x0FFF)
+               return xstrdup(inet_ntoa(s_in->sin_addr));
+
+       if ((ad & (~netmask)) != 0 || (numeric & 0x4000))
+               host = 1;
+       pn = cache;
+       while (pn) {
+               if (pn->addr.sin_addr.s_addr == ad && pn->host == host) {
+#ifdef DEBUG
+                       bb_error_msg("rresolve: found %s %08x in cache",
+                                         (host ? "host" : "net"), (unsigned)ad);
+#endif
+                       return xstrdup(pn->name);
+               }
+               pn = pn->next;
+       }
+
+       host_ad = ntohl(ad);
+       name = NULL;
+       if (host) {
+               struct hostent *ent;
+#ifdef DEBUG
+               bb_error_msg("gethostbyaddr (%08x)", (unsigned)ad);
+#endif
+               ent = gethostbyaddr((char *) &ad, 4, AF_INET);
+               if (ent)
+                       name = xstrdup(ent->h_name);
+       } else if (ENABLE_FEATURE_ETC_NETWORKS) {
+               struct netent *np;
+#ifdef DEBUG
+               bb_error_msg("getnetbyaddr (%08x)", (unsigned)host_ad);
+#endif
+               np = getnetbyaddr(host_ad, AF_INET);
+               if (np)
+                       name = xstrdup(np->n_name);
+       }
+       if (!name)
+               name = xstrdup(inet_ntoa(s_in->sin_addr));
+       pn = xmalloc(sizeof(*pn) + strlen(name)); /* no '+ 1', it's already accounted for */
+       pn->next = cache;
+       pn->addr = *s_in;
+       pn->host = host;
+       strcpy(pn->name, name);
+       cache = pn;
+       return name;
+}
+
+#if ENABLE_FEATURE_IPV6
+
+int FAST_FUNC 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
+
+
+char* FAST_FUNC INET6_rresolve(struct sockaddr_in6 *sin6, int numeric)
+{
+       char name[128];
+       int s;
+
+       if (sin6->sin6_family != AF_INET6) {
+#ifdef DEBUG
+               bb_error_msg("rresolve: unsupport address family %d!",
+                                 sin6->sin6_family);
+#endif
+               errno = EAFNOSUPPORT;
+               return NULL;
+       }
+       if (numeric & 0x7FFF) {
+               inet_ntop(AF_INET6, &sin6->sin6_addr, name, sizeof(name));
+               return xstrdup(name);
+       }
+       if (IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr)) {
+               if (numeric & 0x8000)
+                       return xstrdup(bb_str_default);
+               return xstrdup("*");
+       }
+
+       s = getnameinfo((struct sockaddr *) sin6, sizeof(struct sockaddr_in6),
+                               name, sizeof(name), NULL, 0, 0);
+       if (s) {
+               bb_error_msg("getnameinfo failed");
+               return NULL;
+       }
+       return xstrdup(name);
+}
+
+#endif         /* CONFIG_FEATURE_IPV6 */
diff --git a/libbb/info_msg.c b/libbb/info_msg.c
new file mode 100644 (file)
index 0000000..8b8a1fc
--- /dev/null
@@ -0,0 +1,56 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Utility routines.
+ *
+ * Copyright (C) 1999-2004 by Erik Andersen <andersen@codepoet.org>
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.h"
+#include <syslog.h>
+
+void FAST_FUNC bb_info_msg(const char *s, ...)
+{
+#ifdef THIS_ONE_DOESNT_DO_SINGLE_WRITE
+       va_list p;
+       /* va_copy is used because it is not portable
+        * to use va_list p twice */
+       va_list p2;
+
+       va_start(p, s);
+       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);
+       va_end(p);
+#else
+       int used;
+       char *msg;
+       va_list p;
+
+       if (logmode == 0)
+               return;
+
+       va_start(p, s);
+       used = vasprintf(&msg, s, p);
+       if (used < 0)
+               return;
+
+       if (ENABLE_FEATURE_SYSLOG && (logmode & LOGMODE_SYSLOG))
+               syslog(LOG_INFO, "%s", msg);
+       if (logmode & LOGMODE_STDIO) {
+               fflush(stdout);
+               /* used = strlen(msg); - must be true already */
+               msg[used++] = '\n';
+               full_write(STDOUT_FILENO, msg, used);
+       }
+
+       free(msg);
+       va_end(p);
+#endif
+}
diff --git a/libbb/inode_hash.c b/libbb/inode_hash.c
new file mode 100644 (file)
index 0000000..b32bd26
--- /dev/null
@@ -0,0 +1,87 @@
+/* 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"
+
+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;
+
+#define HASH_SIZE      311             /* Should be prime */
+#define hash_inode(i)  ((i) % HASH_SIZE)
+
+/* array of [HASH_SIZE] elements */
+static ino_dev_hashtable_bucket_t **ino_dev_hashtable;
+
+/*
+ * Return name if statbuf->st_ino && statbuf->st_dev are recorded in
+ * ino_dev_hashtable, else return NULL
+ */
+char* FAST_FUNC is_in_ino_dev_hashtable(const struct stat *statbuf)
+{
+       ino_dev_hashtable_bucket_t *bucket;
+
+       if (!ino_dev_hashtable)
+               return NULL;
+
+       bucket = ino_dev_hashtable[hash_inode(statbuf->st_ino)];
+       while (bucket != NULL) {
+               if ((bucket->ino == statbuf->st_ino)
+                && (bucket->dev == statbuf->st_dev)
+               ) {
+                       return bucket->name;
+               }
+               bucket = bucket->next;
+       }
+       return NULL;
+}
+
+/* Add statbuf to statbuf hash table */
+void FAST_FUNC add_to_ino_dev_hashtable(const struct stat *statbuf, const char *name)
+{
+       int i;
+       ino_dev_hashtable_bucket_t *bucket;
+
+       i = hash_inode(statbuf->st_ino);
+       if (!name)
+               name = "";
+       bucket = xmalloc(sizeof(ino_dev_hashtable_bucket_t) + strlen(name));
+       bucket->ino = statbuf->st_ino;
+       bucket->dev = statbuf->st_dev;
+       strcpy(bucket->name, name);
+
+       if (!ino_dev_hashtable)
+               ino_dev_hashtable = xzalloc(HASH_SIZE * sizeof(*ino_dev_hashtable));
+
+       bucket->next = ino_dev_hashtable[i];
+       ino_dev_hashtable[i] = bucket;
+}
+
+#if ENABLE_DU || ENABLE_FEATURE_CLEAN_UP
+/* Clear statbuf hash table */
+void FAST_FUNC reset_ino_dev_hashtable(void)
+{
+       int i;
+       ino_dev_hashtable_bucket_t *bucket;
+
+       for (i = 0; ino_dev_hashtable && 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;
+               }
+       }
+       free(ino_dev_hashtable);
+       ino_dev_hashtable = NULL;
+}
+#endif
diff --git a/libbb/isdirectory.c b/libbb/isdirectory.c
new file mode 100644 (file)
index 0000000..28ed3ec
--- /dev/null
@@ -0,0 +1,36 @@
+/* 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 <sys/stat.h>
+#include "libbb.h"
+
+/*
+ * Return TRUE if fileName is a directory.
+ * Nonexistent files return FALSE.
+ */
+int FAST_FUNC is_directory(const char *fileName, const int followLinks, struct stat *statBuf)
+{
+       int status;
+       struct stat astatBuf;
+
+       if (statBuf == NULL) {
+               /* use auto stack buffer */
+               statBuf = &astatBuf;
+       }
+
+       if (followLinks)
+               status = stat(fileName, statBuf);
+       else
+               status = lstat(fileName, statBuf);
+
+       status = (status == 0 && S_ISDIR(statBuf->st_mode));
+
+       return status;
+}
diff --git a/libbb/kernel_version.c b/libbb/kernel_version.c
new file mode 100644 (file)
index 0000000..8b9c4ec
--- /dev/null
@@ -0,0 +1,37 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Utility routines.
+ *
+ * Copyright (C) 1999-2004 by Erik Andersen <andersen@codepoet.org>
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+#include <sys/utsname.h>               /* 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)) { <stuff> }
+ */
+int FAST_FUNC 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 (file)
index 0000000..b059256
--- /dev/null
@@ -0,0 +1,24 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * busybox library eXtended function
+ *
+ * Copyright (C) 2001 Larry Doolittle, <ldoolitt@recycle.lbl.gov>
+ *
+ * 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.
+ */
+char* FAST_FUNC last_char_is(const char *s, int c)
+{
+       if (s && *s) {
+               size_t sz = strlen(s) - 1;
+               s += sz;
+               if ( (unsigned char)*s == c)
+                       return (char*)s;
+       }
+       return NULL;
+}
diff --git a/libbb/lineedit.c b/libbb/lineedit.c
new file mode 100644 (file)
index 0000000..7bcdb95
--- /dev/null
@@ -0,0 +1,2006 @@
+/* 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.
+ *
+ * lineedit does not know that the terminal escape sequences do not
+ * take up space on the screen. The redisplay code assumes, unless
+ * told otherwise, that each character in the prompt is a printable
+ * character that takes up one character position on the screen.
+ * You need to tell lineedit that some sequences of characters
+ * in the prompt take up no screen space. Compatibly with readline,
+ * use the \[ escape to begin a sequence of non-printing characters,
+ * and the \] escape to signal the end of such a sequence. Example:
+ *
+ * PS1='\[\033[01;32m\]\u@\h\[\033[01;34m\] \w \$\[\033[00m\] '
+ */
+
+#include "libbb.h"
+
+
+/* FIXME: obsolete CONFIG item? */
+#define ENABLE_FEATURE_NONPRINTABLE_INVERSE_PUT 0
+
+
+#ifdef TEST
+
+#define ENABLE_FEATURE_EDITING 0
+#define ENABLE_FEATURE_TAB_COMPLETION 0
+#define ENABLE_FEATURE_USERNAME_COMPLETION 0
+#define ENABLE_FEATURE_NONPRINTABLE_INVERSE_PUT 0
+
+#endif  /* TEST */
+
+
+/* Entire file (except TESTing part) sits inside this #if */
+#if ENABLE_FEATURE_EDITING
+
+#if ENABLE_LOCALE_SUPPORT
+#define Isprint(c) isprint(c)
+#else
+#define Isprint(c) ((c) >= ' ' && (c) != ((unsigned char)'\233'))
+#endif
+
+#define ENABLE_FEATURE_GETUSERNAME_AND_HOMEDIR \
+       (ENABLE_FEATURE_USERNAME_COMPLETION || ENABLE_FEATURE_EDITING_FANCY_PROMPT)
+#define USE_FEATURE_GETUSERNAME_AND_HOMEDIR(...)
+#if ENABLE_FEATURE_GETUSERNAME_AND_HOMEDIR
+#undef USE_FEATURE_GETUSERNAME_AND_HOMEDIR
+#define USE_FEATURE_GETUSERNAME_AND_HOMEDIR(...) __VA_ARGS__
+#endif
+
+enum {
+       /* We use int16_t for positions, need to limit line len */
+       MAX_LINELEN = CONFIG_FEATURE_EDITING_MAX_LEN < 0x7ff0
+                     ? CONFIG_FEATURE_EDITING_MAX_LEN
+                     : 0x7ff0
+};
+
+#if ENABLE_FEATURE_GETUSERNAME_AND_HOMEDIR
+static const char null_str[] ALIGN1 = "";
+#endif
+
+/* We try to minimize both static and stack usage. */
+struct lineedit_statics {
+       line_input_t *state;
+
+       volatile unsigned cmdedit_termw; /* = 80; */ /* actual terminal width */
+       sighandler_t previous_SIGWINCH_handler;
+
+       unsigned cmdedit_x;        /* real x terminal position */
+       unsigned cmdedit_y;        /* pseudoreal y terminal position */
+       unsigned cmdedit_prmt_len; /* length of prompt (without colors etc) */
+
+       unsigned cursor;
+       unsigned command_len;
+       char *command_ps;
+
+       const char *cmdedit_prompt;
+#if ENABLE_FEATURE_EDITING_FANCY_PROMPT
+       int num_ok_lines; /* = 1; */
+#endif
+
+#if ENABLE_FEATURE_GETUSERNAME_AND_HOMEDIR
+       char *user_buf;
+       char *home_pwd_buf; /* = (char*)null_str; */
+#endif
+
+#if ENABLE_FEATURE_TAB_COMPLETION
+       char **matches;
+       unsigned num_matches;
+#endif
+
+#if ENABLE_FEATURE_EDITING_VI
+#define DELBUFSIZ 128
+       char *delptr;
+       smallint newdelflag;     /* whether delbuf should be reused yet */
+       char delbuf[DELBUFSIZ];  /* a place to store deleted characters */
+#endif
+
+       /* Formerly these were big buffers on stack: */
+#if ENABLE_FEATURE_TAB_COMPLETION
+       char exe_n_cwd_tab_completion__dirbuf[MAX_LINELEN];
+       char input_tab__matchBuf[MAX_LINELEN];
+       int16_t find_match__int_buf[MAX_LINELEN + 1]; /* need to have 9 bits at least */
+       int16_t find_match__pos_buf[MAX_LINELEN + 1];
+#endif
+};
+
+/* See lineedit_ptr_hack.c */
+extern struct lineedit_statics *const lineedit_ptr_to_statics;
+
+#define S (*lineedit_ptr_to_statics)
+#define state            (S.state           )
+#define cmdedit_termw    (S.cmdedit_termw   )
+#define previous_SIGWINCH_handler (S.previous_SIGWINCH_handler)
+#define cmdedit_x        (S.cmdedit_x       )
+#define cmdedit_y        (S.cmdedit_y       )
+#define cmdedit_prmt_len (S.cmdedit_prmt_len)
+#define cursor           (S.cursor          )
+#define command_len      (S.command_len     )
+#define command_ps       (S.command_ps      )
+#define cmdedit_prompt   (S.cmdedit_prompt  )
+#define num_ok_lines     (S.num_ok_lines    )
+#define user_buf         (S.user_buf        )
+#define home_pwd_buf     (S.home_pwd_buf    )
+#define matches          (S.matches         )
+#define num_matches      (S.num_matches     )
+#define delptr           (S.delptr          )
+#define newdelflag       (S.newdelflag      )
+#define delbuf           (S.delbuf          )
+
+#define INIT_S() do { \
+       (*(struct lineedit_statics**)&lineedit_ptr_to_statics) = xzalloc(sizeof(S)); \
+       barrier(); \
+       cmdedit_termw = 80; \
+       USE_FEATURE_EDITING_FANCY_PROMPT(num_ok_lines = 1;) \
+       USE_FEATURE_GETUSERNAME_AND_HOMEDIR(home_pwd_buf = (char*)null_str;) \
+} while (0)
+static void deinit_S(void)
+{
+#if ENABLE_FEATURE_EDITING_FANCY_PROMPT
+       /* This one is allocated only if FANCY_PROMPT is on
+        * (otherwise it points to verbatim prompt (NOT malloced) */
+       free((char*)cmdedit_prompt);
+#endif
+#if ENABLE_FEATURE_GETUSERNAME_AND_HOMEDIR
+       free(user_buf);
+       if (home_pwd_buf != null_str)
+               free(home_pwd_buf);
+#endif
+       free(lineedit_ptr_to_statics);
+}
+#define DEINIT_S() deinit_S()
+
+
+/* Put 'command_ps[cursor]', cursor++.
+ * Advance cursor on screen. If we reached right margin, scroll text up
+ * and remove terminal margin effect by printing 'next_char' */
+#define HACK_FOR_WRONG_WIDTH 1
+#if HACK_FOR_WRONG_WIDTH
+static void cmdedit_set_out_char(void)
+#define cmdedit_set_out_char(next_char) cmdedit_set_out_char()
+#else
+static void cmdedit_set_out_char(int next_char)
+#endif
+{
+       int c = (unsigned char)command_ps[cursor];
+
+       if (c == '\0') {
+               /* erase character after end of input string */
+               c = ' ';
+       }
+#if ENABLE_FEATURE_NONPRINTABLE_INVERSE_PUT
+       /* Display non-printable characters in reverse */
+       if (!Isprint(c)) {
+               if (c >= 128)
+                       c -= 128;
+               if (c < ' ')
+                       c += '@';
+               if (c == 127)
+                       c = '?';
+               printf("\033[7m%c\033[0m", c);
+       } else
+#endif
+       {
+               bb_putchar(c);
+       }
+       if (++cmdedit_x >= cmdedit_termw) {
+               /* terminal is scrolled down */
+               cmdedit_y++;
+               cmdedit_x = 0;
+#if HACK_FOR_WRONG_WIDTH
+               /* This works better if our idea of term width is wrong
+                * and it is actually wider (often happens on serial lines).
+                * Printing CR,LF *forces* cursor to next line.
+                * OTOH if terminal width is correct AND terminal does NOT
+                * have automargin (IOW: it is moving cursor to next line
+                * by itself (which is wrong for VT-10x terminals)),
+                * this will break things: there will be one extra empty line */
+               puts("\r"); /* + implicit '\n' */
+#else
+               /* Works ok only if cmdedit_termw is correct */
+               /* destroy "(auto)margin" */
+               bb_putchar(next_char);
+               bb_putchar('\b');
+#endif
+       }
+// Huh? What if command_ps[cursor] == '\0' (we are at the end already?)
+       cursor++;
+}
+
+/* Move to end of line (by printing all chars till the end) */
+static void input_end(void)
+{
+       while (cursor < command_len)
+               cmdedit_set_out_char(' ');
+}
+
+/* Go to the next line */
+static void goto_new_line(void)
+{
+       input_end();
+       if (cmdedit_x)
+               bb_putchar('\n');
+}
+
+
+static void out1str(const char *s)
+{
+       if (s)
+               fputs(s, stdout);
+}
+
+static void beep(void)
+{
+       bb_putchar('\007');
+}
+
+/* Move back one character */
+/* (optimized for slow terminals) */
+static void input_backward(unsigned num)
+{
+       int count_y;
+
+       if (num > cursor)
+               num = cursor;
+       if (!num)
+               return;
+       cursor -= num;
+
+       if (cmdedit_x >= num) {
+               cmdedit_x -= num;
+               if (num <= 4) {
+                       /* This is longer by 5 bytes on x86.
+                        * Also gets miscompiled for ARM users
+                        * (busybox.net/bugs/view.php?id=2274).
+                        * printf(("\b\b\b\b" + 4) - num);
+                        * return;
+                        */
+                       do {
+                               bb_putchar('\b');
+                       } while (--num);
+                       return;
+               }
+               printf("\033[%uD", num);
+               return;
+       }
+
+       /* Need to go one or more lines up */
+       num -= cmdedit_x;
+       {
+               unsigned w = cmdedit_termw; /* volatile var */
+               count_y = 1 + (num / w);
+               cmdedit_y -= count_y;
+               cmdedit_x = w * count_y - num;
+       }
+       /* go to 1st column; go up; go to correct column */
+       printf("\r" "\033[%dA" "\033[%dC", count_y, cmdedit_x);
+}
+
+static void put_prompt(void)
+{
+       out1str(cmdedit_prompt);
+       cursor = 0;
+       {
+               unsigned w = cmdedit_termw; /* volatile var */
+               cmdedit_y = cmdedit_prmt_len / w; /* new quasireal y */
+               cmdedit_x = cmdedit_prmt_len % w;
+       }
+}
+
+/* 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);
+       bb_putchar('\r');
+       put_prompt();
+       input_end();                            /* rewrite */
+       printf("\033[J");                       /* erase after cursor */
+       input_backward(back_cursor);
+}
+
+/* Delete the char in front of the cursor, optionally saving it
+ * for later putback */
+#if !ENABLE_FEATURE_EDITING_VI
+static void input_delete(void)
+#define input_delete(save) input_delete()
+#else
+static void input_delete(int save)
+#endif
+{
+       int j = cursor;
+
+       if (j == (int)command_len)
+               return;
+
+#if ENABLE_FEATURE_EDITING_VI
+       if (save) {
+               if (newdelflag) {
+                       delptr = delbuf;
+                       newdelflag = 0;
+               }
+               if ((delptr - delbuf) < DELBUFSIZ)
+                       *delptr++ = command_ps[j];
+       }
+#endif
+
+       overlapping_strcpy(command_ps + j, command_ps + j + 1);
+       command_len--;
+       input_end();                    /* rewrite new line */
+       cmdedit_set_out_char(' ');      /* erase char */
+       input_backward(cursor - j);     /* back to old pos cursor */
+}
+
+#if ENABLE_FEATURE_EDITING_VI
+static void put(void)
+{
+       int ocursor;
+       int j = delptr - delbuf;
+
+       if (j == 0)
+               return;
+       ocursor = cursor;
+       /* open hole and then fill it */
+       memmove(command_ps + cursor + j, command_ps + cursor, command_len - cursor + 1);
+       strncpy(command_ps + cursor, delbuf, j);
+       command_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 < command_len)
+               cmdedit_set_out_char(command_ps[cursor + 1]);
+}
+
+#if ENABLE_FEATURE_TAB_COMPLETION
+
+static void free_tab_completion_data(void)
+{
+       if (matches) {
+               while (num_matches)
+                       free(matches[--num_matches]);
+               free(matches);
+               matches = NULL;
+       }
+}
+
+static void add_match(char *matched)
+{
+       matches = xrealloc_vector(matches, 4, num_matches);
+       matches[num_matches] = matched;
+       num_matches++;
+}
+
+#if ENABLE_FEATURE_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 = NULL;
+
+               if (*ud == '/') {       /* "~/..."     */
+                       home = home_pwd_buf;
+               } else {
+                       /* "~user/..." */
+                       char *temp;
+                       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) < MAX_LINELEN) {
+                               /* /home/user/... */
+                               sprintf(sav_ud, "%s%s", home, ud);
+                       }
+               }
+       } else {
+               /* "~[^/]*" */
+               /* Using _r function to avoid pulling in static buffers */
+               char line_buff[256];
+               struct passwd pwd;
+               struct passwd *result;
+
+               setpwent();
+               while (!getpwent_r(&pwd, line_buff, sizeof(line_buff), &result)) {
+                       /* Null usernames should result in all users as possible completions. */
+                       if (/*!userlen || */ strncmp(ud, pwd.pw_name, userlen) == 0) {
+                               add_match(xasprintf("~%s/", pwd.pw_name));
+                       }
+               }
+               endpwent();
+       }
+}
+#endif  /* FEATURE_COMMAND_USERNAME_COMPLETION */
+
+enum {
+       FIND_EXE_ONLY = 0,
+       FIND_DIR_ONLY = 1,
+       FIND_FILE_ONLY = 2,
+};
+
+static int path_parse(char ***p, int flags)
+{
+       int npth;
+       const char *pth;
+       char *tmp;
+       char **res;
+
+       /* if not setenv PATH variable, to search cur dir "." */
+       if (flags != FIND_EXE_ONLY)
+               return 1;
+
+       if (state->flags & WITH_PATH_LOOKUP)
+               pth = state->path_lookup;
+       else
+               pth = getenv("PATH");
+       /* PATH=<empty> or PATH=:<empty> */
+       if (!pth || !pth[0] || LONE_CHAR(pth, ':'))
+               return 1;
+
+       tmp = (char*)pth;
+       npth = 1; /* path component count */
+       while (1) {
+               tmp = strchr(tmp, ':');
+               if (!tmp)
+                       break;
+               if (*++tmp == '\0')
+                       break;  /* :<empty> */
+               npth++;
+       }
+
+       res = xmalloc(npth * sizeof(char*));
+       res[0] = tmp = xstrdup(pth);
+       npth = 1;
+       while (1) {
+               tmp = strchr(tmp, ':');
+               if (!tmp)
+                       break;
+               *tmp++ = '\0'; /* ':' -> '\0' */
+               if (*tmp == '\0')
+                       break; /* :<empty> */
+               res[npth++] = tmp;
+       }
+       *p = res;
+       return npth;
+}
+
+static void exe_n_cwd_tab_completion(char *command, int type)
+{
+       DIR *dir;
+       struct dirent *next;
+       struct stat st;
+       char *path1[1];
+       char **paths = path1;
+       int npaths;
+       int i;
+       char *found;
+       char *pfind = strrchr(command, '/');
+/*     char dirbuf[MAX_LINELEN]; */
+#define dirbuf (S.exe_n_cwd_tab_completion__dirbuf)
+
+       npaths = 1;
+       path1[0] = (char*)".";
+
+       if (pfind == NULL) {
+               /* no dir, if flags==EXE_ONLY - get paths, else "." */
+               npaths = path_parse(&paths, type);
+               pfind = command;
+       } else {
+               /* dirbuf = ".../.../.../" */
+               safe_strncpy(dirbuf, command, (pfind - command) + 2);
+#if ENABLE_FEATURE_USERNAME_COMPLETION
+               if (dirbuf[0] == '~')   /* ~/... or ~user/... */
+                       username_tab_completion(dirbuf, dirbuf);
+#endif
+               paths[0] = dirbuf;
+               /* point to 'l' in "..../last_component" */
+               pfind++;
+       }
+
+       for (i = 0; i < npaths; i++) {
+               dir = opendir(paths[i]);
+               if (!dir)
+                       continue; /* don't print an error */
+
+               while ((next = readdir(dir)) != NULL) {
+                       int len1;
+                       const char *str_found = next->d_name;
+
+                       /* matched? */
+                       if (strncmp(str_found, pfind, strlen(pfind)))
+                               continue;
+                       /* not see .name without .match */
+                       if (*str_found == '.' && *pfind == '\0') {
+                               if (NOT_LONE_CHAR(paths[i], '/') || str_found[1])
+                                       continue;
+                               str_found = ""; /* only "/" */
+                       }
+                       found = concat_path_file(paths[i], str_found);
+                       /* hmm, remove in progress? */
+                       /* NB: stat() first so that we see is it a directory;
+                        * but if that fails, use lstat() so that
+                        * we still match dangling links */
+                       if (stat(found, &st) && lstat(found, &st))
+                               goto cont;
+                       /* find with dirs? */
+                       if (paths[i] != dirbuf)
+                               strcpy(found, next->d_name); /* only name */
+
+                       len1 = strlen(found);
+                       found = xrealloc(found, len1 + 2);
+                       found[len1] = '\0';
+                       found[len1+1] = '\0';
+
+                       if (S_ISDIR(st.st_mode)) {
+                               /* name is a directory */
+                               if (found[len1-1] != '/') {
+                                       found[len1] = '/';
+                               }
+                       } else {
+                               /* not put found file if search only dirs for cd */
+                               if (type == FIND_DIR_ONLY)
+                                       goto cont;
+                       }
+                       /* Add it to the list */
+                       add_match(found);
+                       continue;
+ cont:
+                       free(found);
+               }
+               closedir(dir);
+       }
+       if (paths != path1) {
+               free(paths[0]); /* allocated memory is only in first member */
+               free(paths);
+       }
+#undef dirbuf
+}
+
+#define QUOT (UCHAR_MAX+1)
+
+#define collapse_pos(is, in) do { \
+       memmove(int_buf+(is), int_buf+(in), (MAX_LINELEN+1-(is)-(in)) * sizeof(pos_buf[0])); \
+       memmove(pos_buf+(is), pos_buf+(in), (MAX_LINELEN+1-(is)-(in)) * sizeof(pos_buf[0])); \
+} while (0)
+
+static int find_match(char *matchBuf, int *len_with_quotes)
+{
+       int i, j;
+       int command_mode;
+       int c, c2;
+/*     int16_t int_buf[MAX_LINELEN + 1]; */
+/*     int16_t pos_buf[MAX_LINELEN + 1]; */
+#define int_buf (S.find_match__int_buf)
+#define pos_buf (S.find_match__pos_buf)
+
+       /* set to integer dimension characters and own positions */
+       for (i = 0;; i++) {
+               int_buf[i] = (unsigned char)matchBuf[i];
+               if (int_buf[i] == 0) {
+                       pos_buf[i] = -1;        /* indicator end line */
+                       break;
+               }
+               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++;
+#if ENABLE_FEATURE_NONPRINTABLE_INVERSE_PUT
+                       if (matchBuf[i] == '\t')        /* algorithm equivalent */
+                               int_buf[j] = ' ' | QUOT;
+#endif
+               }
+#if ENABLE_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 has 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;
+                       }
+               }
+       for (i = 0; int_buf[i]; i++)
+               /* "strlen" */;
+       /* 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++)
+               /*skip*/;
+       /* 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 length matchBuf with quotes symbols */
+       *len_with_quotes = j ? j - pos_buf[0] : 0;
+
+       return command_mode;
+#undef int_buf
+#undef pos_buf
+}
+
+/*
+ * display by column (original idea from ls applet,
+ * very optimized by me :)
+ */
+static void showfiles(void)
+{
+       int ncols, row;
+       int column_width = 0;
+       int nfiles = num_matches;
+       int nrows = nfiles;
+       int l;
+
+       /* find the longest file name-  use that as the column width */
+       for (row = 0; row < nrows; row++) {
+               l = strlen(matches[row]);
+               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;
+       }
+       for (row = 0; row < nrows; row++) {
+               int n = row;
+               int nc;
+
+               for (nc = 1; nc < ncols && n+nrows < nfiles; n += nrows, nc++) {
+                       printf("%s%-*s", matches[n],
+                               (int)(column_width - strlen(matches[n])), "");
+               }
+               puts(matches[n]);
+       }
+}
+
+static char *add_quote_for_spec_chars(char *found)
+{
+       int l = 0;
+       char *s = xmalloc((strlen(found) + 1) * 2);
+
+       while (*found) {
+               if (strchr(" `\"#$%^&*()=+{}[]:;\'|\\<>", *found))
+                       s[l++] = '\\';
+               s[l++] = *found++;
+       }
+       s[l] = 0;
+       return s;
+}
+
+/* Do TAB completion */
+static void input_tab(smallint *lastWasTab)
+{
+       if (!(state->flags & TAB_COMPLETION))
+               return;
+
+       if (!*lastWasTab) {
+               char *tmp, *tmp1;
+               size_t len_found;
+/*             char matchBuf[MAX_LINELEN]; */
+#define matchBuf (S.input_tab__matchBuf)
+               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 */
+               free_tab_completion_data();
+
+#if ENABLE_FEATURE_USERNAME_COMPLETION
+               /* If the word starts with `~' and there is no slash in the word,
+                * then try completing this word as a username. */
+               if (state->flags & USERNAME_COMPLETION)
+                       if (matchBuf[0] == '~' && strchr(matchBuf, '/') == 0)
+                               username_tab_completion(matchBuf, NULL);
+#endif
+               /* Try to match any executable in our path and everything
+                * in the current working directory */
+               if (!matches)
+                       exe_n_cwd_tab_completion(matchBuf, find_type);
+               /* Sort, then remove any duplicates found */
+               if (matches) {
+                       unsigned i;
+                       int n = 0;
+                       qsort_string_vector(matches, num_matches);
+                       for (i = 0; i < num_matches - 1; ++i) {
+                               if (matches[i] && matches[i+1]) { /* paranoia */
+                                       if (strcmp(matches[i], matches[i+1]) == 0) {
+                                               free(matches[i]);
+                                               matches[i] = NULL; /* paranoia */
+                                       } else {
+                                               matches[n++] = matches[i];
+                                       }
+                               }
+                       }
+                       matches[n] = matches[i];
+                       num_matches = n + 1;
+               }
+               /* 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);
+                       free(tmp1);
+               } else {                        /* one match */
+                       tmp = add_quote_for_spec_chars(matches[0]);
+                       /* for next completion current found */
+                       *lastWasTab = FALSE;
+
+                       len_found = strlen(tmp);
+                       if (tmp[len_found-1] != '/') {
+                               tmp[len_found] = ' ';
+                               tmp[len_found+1] = '\0';
+                       }
+               }
+               len_found = strlen(tmp);
+               /* have space to placed match? */
+               if ((len_found - strlen(matchBuf) + command_len) < MAX_LINELEN) {
+                       /* 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                         */
+                       command_len = strlen(command_ps);
+                       /* write out the matched command   */
+                       redraw(cmdedit_y, command_len - recalc_pos);
+               }
+               free(tmp);
+#undef matchBuf
+       } 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, command_len - sav_cursor);
+               }
+       }
+}
+
+#endif  /* FEATURE_COMMAND_TAB_COMPLETION */
+
+
+line_input_t* FAST_FUNC new_line_input_t(int flags)
+{
+       line_input_t *n = xzalloc(sizeof(*n));
+       n->flags = flags;
+       return n;
+}
+
+
+#if MAX_HISTORY > 0
+
+static void save_command_ps_at_cur_history(void)
+{
+       if (command_ps[0] != '\0') {
+               int cur = state->cur_history;
+               free(state->history[cur]);
+               state->history[cur] = xstrdup(command_ps);
+       }
+}
+
+/* state->flags is already checked to be nonzero */
+static int get_previous_history(void)
+{
+       if ((state->flags & DO_HISTORY) && state->cur_history) {
+               save_command_ps_at_cur_history();
+               state->cur_history--;
+               return 1;
+       }
+       beep();
+       return 0;
+}
+
+static int get_next_history(void)
+{
+       if (state->flags & DO_HISTORY) {
+               if (state->cur_history < state->cnt_history) {
+                       save_command_ps_at_cur_history(); /* save the current history line */
+                       return ++state->cur_history;
+               }
+       }
+       beep();
+       return 0;
+}
+
+#if ENABLE_FEATURE_EDITING_SAVEHISTORY
+/* We try to ensure that concurrent additions to the history
+ * do not overwrite each other.
+ * Otherwise shell users get unhappy.
+ *
+ * History file is trimmed lazily, when it grows several times longer
+ * than configured MAX_HISTORY lines.
+ */
+
+static void free_line_input_t(line_input_t *n)
+{
+       int i = n->cnt_history;
+       while (i > 0)
+               free(n->history[--i]);
+       free(n);
+}
+
+/* state->flags is already checked to be nonzero */
+static void load_history(line_input_t *st_parm)
+{
+       char *temp_h[MAX_HISTORY];
+       char *line;
+       FILE *fp;
+       unsigned idx, i, line_len;
+
+       /* NB: do not trash old history if file can't be opened */
+
+       fp = fopen_for_read(st_parm->hist_file);
+       if (fp) {
+               /* clean up old history */
+               for (idx = st_parm->cnt_history; idx > 0;) {
+                       idx--;
+                       free(st_parm->history[idx]);
+                       st_parm->history[idx] = NULL;
+               }
+
+               /* fill temp_h[], retaining only last MAX_HISTORY lines */
+               memset(temp_h, 0, sizeof(temp_h));
+               st_parm->cnt_history_in_file = idx = 0;
+               while ((line = xmalloc_fgetline(fp)) != NULL) {
+                       if (line[0] == '\0') {
+                               free(line);
+                               continue;
+                       }
+                       free(temp_h[idx]);
+                       temp_h[idx] = line;
+                       st_parm->cnt_history_in_file++;
+                       idx++;
+                       if (idx == MAX_HISTORY)
+                               idx = 0;
+               }
+               fclose(fp);
+
+               /* find first non-NULL temp_h[], if any */
+               if (st_parm->cnt_history_in_file) {
+                       while (temp_h[idx] == NULL) {
+                               idx++;
+                               if (idx == MAX_HISTORY)
+                                       idx = 0;
+                       }
+               }
+
+               /* copy temp_h[] to st_parm->history[] */
+               for (i = 0; i < MAX_HISTORY;) {
+                       line = temp_h[idx];
+                       if (!line)
+                               break;
+                       idx++;
+                       if (idx == MAX_HISTORY)
+                               idx = 0;
+                       line_len = strlen(line);
+                       if (line_len >= MAX_LINELEN)
+                               line[MAX_LINELEN-1] = '\0';
+                       st_parm->history[i++] = line;
+               }
+               st_parm->cnt_history = i;
+       }
+}
+
+/* state->flags is already checked to be nonzero */
+static void save_history(char *str)
+{
+       int fd;
+       int len, len2;
+
+       fd = open(state->hist_file, O_WRONLY | O_CREAT | O_APPEND, 0666);
+       if (fd < 0)
+               return;
+       xlseek(fd, 0, SEEK_END); /* paranoia */
+       len = strlen(str);
+       str[len] = '\n'; /* we (try to) do atomic write */
+       len2 = full_write(fd, str, len + 1);
+       str[len] = '\0';
+       close(fd);
+       if (len2 != len + 1)
+               return; /* "wtf?" */
+
+       /* did we write so much that history file needs trimming? */
+       state->cnt_history_in_file++;
+       if (state->cnt_history_in_file > MAX_HISTORY * 4) {
+               FILE *fp;
+               char *new_name;
+               line_input_t *st_temp;
+               int i;
+
+               /* we may have concurrently written entries from others.
+                * load them */
+               st_temp = new_line_input_t(state->flags);
+               st_temp->hist_file = state->hist_file;
+               load_history(st_temp);
+
+               /* write out temp file and replace hist_file atomically */
+               new_name = xasprintf("%s.%u.new", state->hist_file, (int) getpid());
+               fp = fopen_for_write(new_name);
+               if (fp) {
+                       for (i = 0; i < st_temp->cnt_history; i++)
+                               fprintf(fp, "%s\n", st_temp->history[i]);
+                       fclose(fp);
+                       if (rename(new_name, state->hist_file) == 0)
+                               state->cnt_history_in_file = st_temp->cnt_history;
+               }
+               free(new_name);
+               free_line_input_t(st_temp);
+       }
+}
+#else
+#define load_history(a) ((void)0)
+#define save_history(a) ((void)0)
+#endif /* FEATURE_COMMAND_SAVEHISTORY */
+
+static void remember_in_history(char *str)
+{
+       int i;
+
+       if (!(state->flags & DO_HISTORY))
+               return;
+       if (str[0] == '\0')
+               return;
+       i = state->cnt_history;
+       /* Don't save dupes */
+       if (i && strcmp(state->history[i-1], str) == 0)
+               return;
+
+       free(state->history[MAX_HISTORY]); /* redundant, paranoia */
+       state->history[MAX_HISTORY] = NULL; /* redundant, paranoia */
+
+       /* If history[] is full, remove the oldest command */
+       /* we need to keep history[MAX_HISTORY] empty, hence >=, not > */
+       if (i >= MAX_HISTORY) {
+               free(state->history[0]);
+               for (i = 0; i < MAX_HISTORY-1; i++)
+                       state->history[i] = state->history[i+1];
+               /* i == MAX_HISTORY-1 */
+       }
+       /* i <= MAX_HISTORY-1 */
+       state->history[i++] = xstrdup(str);
+       /* i <= MAX_HISTORY */
+       state->cur_history = i;
+       state->cnt_history = i;
+#if ENABLE_FEATURE_EDITING_SAVEHISTORY
+       if ((state->flags & SAVE_HISTORY) && state->hist_file)
+               save_history(str);
+#endif
+       USE_FEATURE_EDITING_FANCY_PROMPT(num_ok_lines++;)
+}
+
+#else /* MAX_HISTORY == 0 */
+#define remember_in_history(a) ((void)0)
+#endif /* MAX_HISTORY */
+
+
+/*
+ * 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>
+ */
+
+#if ENABLE_FEATURE_EDITING_VI
+static void
+vi_Word_motion(char *command, int eat)
+{
+       while (cursor < command_len && !isspace(command[cursor]))
+               input_forward();
+       if (eat) while (cursor < command_len && isspace(command[cursor]))
+               input_forward();
+}
+
+static void
+vi_word_motion(char *command, int eat)
+{
+       if (isalnum(command[cursor]) || command[cursor] == '_') {
+               while (cursor < command_len
+                && (isalnum(command[cursor+1]) || command[cursor+1] == '_'))
+                       input_forward();
+       } else if (ispunct(command[cursor])) {
+               while (cursor < command_len && ispunct(command[cursor+1]))
+                       input_forward();
+       }
+
+       if (cursor < command_len)
+               input_forward();
+
+       if (eat && cursor < command_len && isspace(command[cursor]))
+               while (cursor < command_len && isspace(command[cursor]))
+                       input_forward();
+}
+
+static void
+vi_End_motion(char *command)
+{
+       input_forward();
+       while (cursor < command_len && isspace(command[cursor]))
+               input_forward();
+       while (cursor < command_len-1 && !isspace(command[cursor+1]))
+               input_forward();
+}
+
+static void
+vi_end_motion(char *command)
+{
+       if (cursor >= command_len-1)
+               return;
+       input_forward();
+       while (cursor < command_len-1 && isspace(command[cursor]))
+               input_forward();
+       if (cursor >= command_len-1)
+               return;
+       if (isalnum(command[cursor]) || command[cursor] == '_') {
+               while (cursor < command_len-1
+                && (isalnum(command[cursor+1]) || command[cursor+1] == '_')
+               ) {
+                       input_forward();
+               }
+       } else if (ispunct(command[cursor])) {
+               while (cursor < command_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
+
+
+/*
+ * read_line_input and its helpers
+ */
+
+#if !ENABLE_FEATURE_EDITING_FANCY_PROMPT
+static void parse_and_put_prompt(const char *prmt_ptr)
+{
+       cmdedit_prompt = prmt_ptr;
+       cmdedit_prmt_len = strlen(prmt_ptr);
+       put_prompt();
+}
+#else
+static void parse_and_put_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 *cwd_buf = xrealloc_getcwd_or_warn(NULL);
+       char cbuf[2];
+       char c;
+       char *pbuf;
+
+       cmdedit_prmt_len = 0;
+
+       if (!cwd_buf) {
+               cwd_buf = (char *)bb_msg_unknown;
+       }
+
+       cbuf[1] = '\0'; /* never changes */
+
+       while (*prmt_ptr) {
+               char *free_me = NULL;
+
+               pbuf = cbuf;
+               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) {
+#if ENABLE_FEATURE_GETUSERNAME_AND_HOMEDIR
+                               case 'u':
+                                       pbuf = user_buf ? user_buf : (char*)"";
+                                       break;
+#endif
+                               case 'h':
+                                       pbuf = free_me = safe_gethostname();
+                                       *strchrnul(pbuf, '.') = '\0';
+                                       break;
+                               case '$':
+                                       c = (geteuid() == 0 ? '#' : '$');
+                                       break;
+#if ENABLE_FEATURE_GETUSERNAME_AND_HOMEDIR
+                               case 'w':
+                                       /* /home/user[/something] -> ~[/something] */
+                                       pbuf = cwd_buf;
+                                       l = strlen(home_pwd_buf);
+                                       if (l != 0
+                                        && strncmp(home_pwd_buf, cwd_buf, l) == 0
+                                        && (cwd_buf[l]=='/' || cwd_buf[l]=='\0')
+                                        && strlen(cwd_buf + l) < PATH_MAX
+                                       ) {
+                                               pbuf = free_me = xasprintf("~%s", cwd_buf + l);
+                                       }
+                                       break;
+#endif
+                               case 'W':
+                                       pbuf = cwd_buf;
+                                       cp = strrchr(pbuf, '/');
+                                       if (cp != NULL && cp != pbuf)
+                                               pbuf += (cp-pbuf) + 1;
+                                       break;
+                               case '!':
+                                       pbuf = free_me = xasprintf("%d", num_ok_lines);
+                                       break;
+                               case 'e': case 'E':     /* \e \E = \033 */
+                                       c = '\033';
+                                       break;
+                               case 'x': case 'X': {
+                                       char buf2[4];
+                                       for (l = 0; l < 3;) {
+                                               unsigned h;
+                                               buf2[l++] = *prmt_ptr;
+                                               buf2[l] = '\0';
+                                               h = strtoul(buf2, &pbuf, 16);
+                                               if (h > UCHAR_MAX || (pbuf - buf2) < l) {
+                                                       buf2[--l] = '\0';
+                                                       break;
+                                               }
+                                               prmt_ptr++;
+                                       }
+                                       c = (char)strtoul(buf2, NULL, 16);
+                                       if (c == 0)
+                                               c = '?';
+                                       pbuf = cbuf;
+                                       break;
+                               }
+                               case '[': case ']':
+                                       if (c == flg_not_length) {
+                                               flg_not_length = (flg_not_length == '[' ? ']' : '[');
+                                               continue;
+                                       }
+                                       break;
+                               } /* switch */
+                       } /* if */
+               } /* if */
+               cbuf[0] = 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);
+               free(free_me);
+       } /* while */
+
+       if (cwd_buf != (char *)bb_msg_unknown)
+               free(cwd_buf);
+       cmdedit_prompt = prmt_mem_ptr;
+       put_prompt();
+}
+#endif
+
+static void cmdedit_setwidth(unsigned w, int redraw_flg)
+{
+       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), command_len - cursor);
+               fflush(stdout);
+       }
+}
+
+static void win_changed(int nsig)
+{
+       unsigned width;
+       get_terminal_width_height(0, &width, NULL);
+       cmdedit_setwidth(width, nsig /* - just a yes/no flag */);
+       if (nsig == SIGWINCH)
+               signal(SIGWINCH, win_changed); /* rearm ourself */
+}
+
+/*
+ * 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_EDITING(case caselabel)
+
+/* convert uppercase ascii to equivalent control char, for readability */
+#undef CTRL
+#define CTRL(a) ((a) & ~0x40)
+
+/* Returns:
+ * -1 on read errors or EOF, or on bare Ctrl-D,
+ * 0  on ctrl-C (the line entered is still returned in 'command'),
+ * >0 length of input string, including terminating '\n'
+ */
+int FAST_FUNC read_line_input(const char *prompt, char *command, int maxsize, line_input_t *st)
+{
+       int len;
+#if ENABLE_FEATURE_TAB_COMPLETION
+       smallint lastWasTab = FALSE;
+#endif
+       unsigned ic;
+       unsigned char c;
+       smallint break_out = 0;
+#if ENABLE_FEATURE_EDITING_VI
+       smallint vi_cmdmode = 0;
+       smalluint prevc;
+#endif
+       struct termios initial_settings;
+       struct termios new_settings;
+
+       INIT_S();
+
+       if (tcgetattr(STDIN_FILENO, &initial_settings) < 0
+        || !(initial_settings.c_lflag & ECHO)
+       ) {
+               /* Happens when e.g. stty -echo was run before */
+               parse_and_put_prompt(prompt);
+               fflush(stdout);
+               if (fgets(command, maxsize, stdin) == NULL)
+                       len = -1; /* EOF or error */
+               else
+                       len = strlen(command);
+               DEINIT_S();
+               return len;
+       }
+
+// FIXME: audit & improve this
+       if (maxsize > MAX_LINELEN)
+               maxsize = MAX_LINELEN;
+
+       /* With null flags, no other fields are ever used */
+       state = st ? st : (line_input_t*) &const_int_0;
+#if ENABLE_FEATURE_EDITING_SAVEHISTORY
+       if ((state->flags & SAVE_HISTORY) && state->hist_file)
+               if (state->cnt_history == 0)
+                       load_history(state);
+#endif
+       if (state->flags & DO_HISTORY)
+               state->cur_history = state->cnt_history;
+
+       /* prepare before init handlers */
+       cmdedit_y = 0;  /* quasireal y, not true if line > xt*yt */
+       command_len = 0;
+       command_ps = command;
+       command[0] = '\0';
+
+       new_settings = initial_settings;
+       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[] is not parsed if ICANON is off */
+       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;
+       tcsetattr_stdin_TCSANOW(&new_settings);
+
+       /* Now initialize things */
+       previous_SIGWINCH_handler = signal(SIGWINCH, win_changed);
+       win_changed(0); /* do initial resizing */
+#if ENABLE_FEATURE_GETUSERNAME_AND_HOMEDIR
+       {
+               struct passwd *entry;
+
+               entry = getpwuid(geteuid());
+               if (entry) {
+                       user_buf = xstrdup(entry->pw_name);
+                       home_pwd_buf = xstrdup(entry->pw_dir);
+               }
+       }
+#endif
+
+#if 0
+       for (ic = 0; ic <= MAX_HISTORY; ic++)
+               bb_error_msg("history[%d]:'%s'", ic, state->history[ic]);
+       bb_error_msg("cur_history:%d cnt_history:%d", state->cur_history, state->cnt_history);
+#endif
+
+       /* Print out the command prompt */
+       parse_and_put_prompt(prompt);
+
+       while (1) {
+               fflush(NULL);
+
+               if (nonblock_safe_read(STDIN_FILENO, &c, 1) < 1) {
+                       /* if we can't read input then exit */
+                       goto prepare_to_die;
+               }
+
+               ic = c;
+
+#if ENABLE_FEATURE_EDITING_VI
+               newdelflag = 1;
+               if (vi_cmdmode)
+                       ic |= vbit;
+#endif
+               switch (ic) {
+               case '\n':
+               case '\r':
+               vi_case('\n'|vbit:)
+               vi_case('\r'|vbit:)
+                       /* Enter */
+                       goto_new_line();
+                       break_out = 1;
+                       break;
+               case CTRL('A'):
+               vi_case('0'|vbit:)
+                       /* Control-a -- Beginning of line */
+                       input_backward(cursor);
+                       break;
+               case CTRL('B'):
+               vi_case('h'|vbit:)
+               vi_case('\b'|vbit:)
+               vi_case('\x7f'|vbit:) /* DEL */
+                       /* Control-b -- Move back one character */
+                       input_backward(1);
+                       break;
+               case CTRL('C'):
+               vi_case(CTRL('C')|vbit:)
+                       /* Control-c -- stop gathering input */
+                       goto_new_line();
+                       command_len = 0;
+                       break_out = -1; /* "do not append '\n'" */
+                       break;
+               case CTRL('D'):
+                       /* Control-d -- Delete one character, or exit
+                        * if the len=0 and no chars to delete */
+                       if (command_len == 0) {
+                               errno = 0;
+ prepare_to_die:
+                               /* to control stopped jobs */
+                               break_out = command_len = -1;
+                               break;
+                       }
+                       input_delete(0);
+                       break;
+
+               case CTRL('E'):
+               vi_case('$'|vbit:)
+                       /* Control-e -- End of line */
+                       input_end();
+                       break;
+               case CTRL('F'):
+               vi_case('l'|vbit:)
+               vi_case(' '|vbit:)
+                       /* Control-f -- Move forward one character */
+                       input_forward();
+                       break;
+
+               case '\b':
+               case '\x7f': /* DEL */
+                       /* Control-h and DEL */
+                       input_backspace();
+                       break;
+
+#if ENABLE_FEATURE_TAB_COMPLETION
+               case '\t':
+                       input_tab(&lastWasTab);
+                       break;
+#endif
+
+               case CTRL('K'):
+                       /* Control-k -- clear to end of line */
+                       command[cursor] = 0;
+                       command_len = cursor;
+                       printf("\033[J");
+                       break;
+               case CTRL('L'):
+               vi_case(CTRL('L')|vbit:)
+                       /* Control-l -- clear screen */
+                       printf("\033[H");
+                       redraw(0, command_len - cursor);
+                       break;
+
+#if MAX_HISTORY > 0
+               case CTRL('N'):
+               vi_case(CTRL('N')|vbit:)
+               vi_case('j'|vbit:)
+                       /* Control-n -- Get next command in history */
+                       if (get_next_history())
+                               goto rewrite_line;
+                       break;
+               case CTRL('P'):
+               vi_case(CTRL('P')|vbit:)
+               vi_case('k'|vbit:)
+                       /* Control-p -- Get previous command from history */
+                       if (get_previous_history())
+                               goto rewrite_line;
+                       break;
+#endif
+
+               case CTRL('U'):
+               vi_case(CTRL('U')|vbit:)
+                       /* Control-U -- Clear line before cursor */
+                       if (cursor) {
+                               overlapping_strcpy(command, command + cursor);
+                               command_len -= cursor;
+                               redraw(cmdedit_y, command_len);
+                       }
+                       break;
+               case CTRL('W'):
+               vi_case(CTRL('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_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(STDIN_FILENO, &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 < command_len)
+                                       input_delete(1);
+                               break;
+                       }
+                       break;
+               }
+               case 'p'|vbit:
+                       input_forward();
+                       /* fallthrough */
+               case 'P'|vbit:
+                       put();
+                       break;
+               case 'r'|vbit:
+                       if (safe_read(STDIN_FILENO, &c, 1) < 1)
+                               goto prepare_to_die;
+                       if (c == 0)
+                               beep();
+                       else {
+                               *(command + cursor) = c;
+                               bb_putchar(c);
+                               bb_putchar('\b');
+                       }
+                       break;
+#endif /* FEATURE_COMMAND_EDITING_VI */
+
+               case '\x1b': /* ESC */
+
+#if ENABLE_FEATURE_EDITING_VI
+                       if (state->flags & VI_MODE) {
+                               /* ESC: insert mode --> command mode */
+                               vi_cmdmode = 1;
+                               input_backward(1);
+                               break;
+                       }
+#endif
+                       /* escape sequence follows */
+                       if (safe_read(STDIN_FILENO, &c, 1) < 1)
+                               goto prepare_to_die;
+                       /* different vt100 emulations */
+                       if (c == '[' || c == 'O') {
+               vi_case('['|vbit:)
+               vi_case('O'|vbit:)
+                               if (safe_read(STDIN_FILENO, &c, 1) < 1)
+                                       goto prepare_to_die;
+                       }
+                       if (c >= '1' && c <= '9') {
+                               unsigned char dummy;
+
+                               if (safe_read(STDIN_FILENO, &dummy, 1) < 1)
+                                       goto prepare_to_die;
+                               if (dummy != '~')
+                                       c = '\0';
+                       }
+
+                       switch (c) {
+#if ENABLE_FEATURE_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 (get_previous_history())
+                                       goto rewrite_line;
+                               beep();
+                               break;
+                       case 'B':
+                               /* Down Arrow -- Get next command in history */
+                               if (!get_next_history())
+                                       break;
+ rewrite_line:
+                               /* Rewrite the line with the selected history item */
+                               /* change command */
+                               command_len = strlen(strcpy(command, state->history[state->cur_history] ? : ""));
+                               /* redraw and go to eol (bol, in vi */
+                               redraw(cmdedit_y, (state->flags & VI_MODE) ? 9999 : 0);
+                               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': // vt100? linux vt? or what?
+                       case '7': // vt100? linux vt? or what?
+                       case 'H': /* xterm's <Home> */
+                               input_backward(cursor);
+                               break;
+                       case '4': // vt100? linux vt? or what?
+                       case '8': // vt100? linux vt? or what?
+                       case 'F': /* xterm's <End> */
+                               input_end();
+                               break;
+                       default:
+                               c = '\0';
+                               beep();
+                       }
+                       break;
+
+               default:        /* If it's regular input, do the normal thing */
+
+                       /* Control-V -- force insert of next char */
+                       if (c == CTRL('V')) {
+                               if (safe_read(STDIN_FILENO, &c, 1) < 1)
+                                       goto prepare_to_die;
+                               if (c == 0) {
+                                       beep();
+                                       break;
+                               }
+                       }
+
+#if ENABLE_FEATURE_EDITING_VI
+                       if (vi_cmdmode)  /* Don't self-insert */
+                               break;
+#endif
+                       if ((int)command_len >= (maxsize - 2))        /* Need to leave space for enter */
+                               break;
+
+                       command_len++;
+                       if (cursor == (command_len - 1)) {      /* Append if at the end of the line */
+                               command[cursor] = c;
+                               command[cursor+1] = '\0';
+                               cmdedit_set_out_char(' ');
+                       } else {                        /* Insert otherwise */
+                               int sc = cursor;
+
+                               memmove(command + sc + 1, command + sc, command_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 ENABLE_FEATURE_TAB_COMPLETION
+               if (c != '\t')
+                       lastWasTab = FALSE;
+#endif
+       }
+
+       if (command_len > 0)
+               remember_in_history(command);
+
+       if (break_out > 0) {
+               command[command_len++] = '\n';
+               command[command_len] = '\0';
+       }
+
+#if ENABLE_FEATURE_TAB_COMPLETION
+       free_tab_completion_data();
+#endif
+
+       /* restore initial_settings */
+       tcsetattr_stdin_TCSANOW(&initial_settings);
+       /* restore SIGWINCH handler */
+       signal(SIGWINCH, previous_SIGWINCH_handler);
+       fflush(stdout);
+
+       len = command_len;
+       DEINIT_S();
+
+       return len; /* can't return command_len, DEINIT_S() destroys it */
+}
+
+#else
+
+#undef read_line_input
+int FAST_FUNC read_line_input(const char* prompt, char* command, int maxsize)
+{
+       fputs(prompt, stdout);
+       fflush(stdout);
+       fgets(command, maxsize, stdin);
+       return strlen(command);
+}
+
+#endif  /* FEATURE_COMMAND_EDITING */
+
+
+/*
+ * Testing
+ */
+
+#ifdef TEST
+
+#include <locale.h>
+
+const char *applet_name = "debug stuff usage";
+
+int main(int argc, char **argv)
+{
+       char buff[MAX_LINELEN];
+       char *prompt =
+#if ENABLE_FEATURE_EDITING_FANCY_PROMPT
+               "\\[\\033[32;1m\\]\\u@\\[\\x1b[33;1m\\]\\h:"
+               "\\[\\033[34;1m\\]\\w\\[\\033[35;1m\\] "
+               "\\!\\[\\e[36;1m\\]\\$ \\[\\E[0m\\]";
+#else
+               "% ";
+#endif
+
+#if ENABLE_FEATURE_NONPRINTABLE_INVERSE_PUT
+       setlocale(LC_ALL, "");
+#endif
+       while (1) {
+               int l;
+               l = read_line_input(prompt, buff);
+               if (l <= 0 || buff[l-1] != '\n')
+                       break;
+               buff[l-1] = 0;
+               printf("*** read_line_input() returned line =%s=\n", buff);
+       }
+       printf("*** read_line_input() detect ^D\n");
+       return 0;
+}
+
+#endif  /* TEST */
diff --git a/libbb/lineedit_ptr_hack.c b/libbb/lineedit_ptr_hack.c
new file mode 100644 (file)
index 0000000..53716a2
--- /dev/null
@@ -0,0 +1,23 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Copyright (C) 2008 by Denys Vlasenko <vda.linux@googlemail.com>
+ *
+ * Licensed under GPLv2, see file LICENSE in this tarball for details.
+ */
+
+struct lineedit_statics;
+
+#ifndef GCC_COMBINE
+
+/* We cheat here. It is declared as const ptr in libbb.h,
+ * but here we make it live in R/W memory */
+struct lineedit_statics *lineedit_ptr_to_statics;
+
+#else
+
+/* gcc -combine will see through and complain */
+/* Using alternative method which is more likely to break
+ * on weird architectures, compilers, linkers and so on */
+struct lineedit_statics *const lineedit_ptr_to_statics __attribute__ ((section (".data")));
+
+#endif
diff --git a/libbb/llist.c b/libbb/llist.c
new file mode 100644 (file)
index 0000000..51b1ce6
--- /dev/null
@@ -0,0 +1,98 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * linked list helper functions.
+ *
+ * Copyright (C) 2003 Glenn McGrath
+ * Copyright (C) 2005 Vladimir Oleynik
+ * Copyright (C) 2005 Bernhard Reutner-Fischer
+ * Copyright (C) 2006 Rob Landley <rob@landley.net>
+ *
+ * Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
+ */
+
+#include "libbb.h"
+
+/* Add data to the start of the linked list.  */
+void FAST_FUNC 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 FAST_FUNC llist_add_to_end(llist_t **list_head, void *data)
+{
+       while (*list_head)
+               list_head = &(*list_head)->link;
+       *list_head = xzalloc(sizeof(llist_t));
+       (*list_head)->data = data;
+       /*(*list_head)->link = NULL;*/
+}
+
+/* Remove first element from the list and return it */
+void* FAST_FUNC llist_pop(llist_t **head)
+{
+       void *data = NULL;
+       llist_t *temp = *head;
+
+       if (temp) {
+               data = temp->data;
+               *head = temp->link;
+               free(temp);
+       }
+       return data;
+}
+
+/* Unlink arbitrary given element from the list */
+void FAST_FUNC llist_unlink(llist_t **head, llist_t *elm)
+{
+       if (!elm)
+               return;
+       while (*head) {
+               if (*head == elm) {
+                       *head = (*head)->link;
+                       break;
+               }
+               head = &(*head)->link;
+       }
+}
+
+/* Recursively free all elements in the linked list.  If freeit != NULL
+ * call it on each datum in the list */
+void FAST_FUNC llist_free(llist_t *elm, void (*freeit) (void *data))
+{
+       while (elm) {
+               void *data = llist_pop(&elm);
+
+               if (freeit)
+                       freeit(data);
+       }
+}
+
+/* Reverse list order. */
+llist_t* FAST_FUNC llist_rev(llist_t *list)
+{
+       llist_t *rev = NULL;
+
+       while (list) {
+               llist_t *next = list->link;
+
+               list->link = rev;
+               rev = list;
+               list = next;
+       }
+       return rev;
+}
+
+llist_t* FAST_FUNC llist_find_str(llist_t *list, const char *str)
+{
+       while (list) {
+               if (strcmp(list->data, str) == 0)
+                       break;
+               list = list->link;
+       }
+       return list;
+}
diff --git a/libbb/login.c b/libbb/login.c
new file mode 100644 (file)
index 0000000..b3e199c
--- /dev/null
@@ -0,0 +1,129 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * issue.c: issue printing code
+ *
+ * Copyright (C) 2003 Bastian Blank <waldi@tuxbox.org>
+ *
+ * Optimize and correcting OCRNL by Vladimir Oleynik <dzo@simtreas.ru>
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+#include <sys/param.h>  /* MAXHOSTNAMELEN */
+#include <sys/utsname.h>
+#include "libbb.h"
+
+#define LOGIN " login: "
+
+static const char fmtstr_d[] ALIGN1 = "%A, %d %B %Y";
+static const char fmtstr_t[] ALIGN1 = "%H:%M:%S";
+
+void FAST_FUNC print_login_issue(const char *issue_file, const char *tty)
+{
+       FILE *fp;
+       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 */
+
+       fp = fopen_for_read(issue_file);
+       if (!fp)
+               return;
+       while ((c = fgetc(fp)) != EOF) {
+               outbuf = buf;
+               buf[0] = c;
+               buf[1] = '\0';
+               if (c == '\n') {
+                       buf[1] = '\r';
+                       buf[2] = '\0';
+               }
+               if (c == '\\' || c == '%') {
+                       c = fgetc(fp);
+                       switch (c) {
+                       case 's':
+                               outbuf = uts.sysname;
+                               break;
+                       case 'n':
+                       case 'h':
+                               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':
+                               outbuf = uts.domainname;
+                               break;
+                       case 'd':
+                               strftime(buf, sizeof(buf), fmtstr_d, localtime(&t));
+                               break;
+                       case 't':
+                               strftime(buf, sizeof(buf), fmtstr_t, localtime(&t));
+                               break;
+                       case 'l':
+                               outbuf = tty;
+                               break;
+                       default:
+                               buf[0] = c;
+                       }
+               }
+               fputs(outbuf, stdout);
+       }
+       fclose(fp);
+       fflush(stdout);
+}
+
+void FAST_FUNC print_login_prompt(void)
+{
+       char *hostname = safe_gethostname();
+
+       fputs(hostname, stdout);
+       fputs(LOGIN, stdout);
+       fflush(stdout);
+       free(hostname);
+}
+
+/* Clear dangerous stuff, set PATH */
+static const char forbid[] ALIGN1 =
+       "ENV" "\0"
+       "BASH_ENV" "\0"
+       "HOME" "\0"
+       "IFS" "\0"
+       "SHELL" "\0"
+       "LD_LIBRARY_PATH" "\0"
+       "LD_PRELOAD" "\0"
+       "LD_TRACE_LOADED_OBJECTS" "\0"
+       "LD_BIND_NOW" "\0"
+       "LD_AOUT_LIBRARY_PATH" "\0"
+       "LD_AOUT_PRELOAD" "\0"
+       "LD_NOWARN" "\0"
+       "LD_KEEPDIR" "\0";
+
+int FAST_FUNC sanitize_env_if_suid(void)
+{
+       const char *p;
+
+       if (getuid() == geteuid())
+               return 0;
+
+       p = forbid;
+       do {
+               unsetenv(p);
+               p += strlen(p) + 1;
+       } while (*p);
+       putenv((char*)bb_PATH_root_path);
+
+       return 1; /* we indeed were run by different user! */
+}
diff --git a/libbb/loop.c b/libbb/loop.c
new file mode 100644 (file)
index 0000000..7d2b420
--- /dev/null
@@ -0,0 +1,154 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Utility routines.
+ *
+ * Copyright (C) 1999-2004 by Erik Andersen <andersen@codepoet.org>
+ * Copyright (C) 2005 by Rob Landley <rob@landley.net>
+ *
+ * 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 <linux/version.h>
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)
+#include <linux/loop.h>
+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 <linux/posix_types.h>
+#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* FAST_FUNC 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 FAST_FUNC 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 FAST_FUNC set_loop(char **device, const char *file, unsigned long long offset)
+{
+       char dev[LOOP_NAMESIZE];
+       char *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 is 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) != 0
+               || offset != loopinfo.lo_offset) {
+                       rc = -1;
+               }
+               close(dfd);
+ try_again:
+               if (*device) break;
+       }
+       close(ffd);
+       if (!rc) {
+               if (!*device)
+                       *device = xstrdup(dev);
+               return (mode == O_RDONLY); /* 1:ro, 0:rw */
+       }
+       return rc;
+}
diff --git a/libbb/make_directory.c b/libbb/make_directory.c
new file mode 100644 (file)
index 0000000..391493c
--- /dev/null
@@ -0,0 +1,98 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * parse_mode implementation for busybox
+ *
+ * Copyright (C) 2003  Manuel Novoa III  <mjn3@codepoet.org>
+ *
+ * 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 "libbb.h"
+
+/* This function is used from NOFORK applets. It must not allocate anything */
+
+int FAST_FUNC 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);
+       umask(mask & ~0300); /* Ensure intermediate dirs are wx */
+
+       while (1) {
+               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 (!c) /* Last component uses orig umask */
+                       umask(mask);
+
+               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 condition. */
+                       if (!c) {
+                               umask(mask);
+                               return 0;
+                       }
+               }
+
+               if (!c) {
+                       /* Done.  If necessary, update perms on the newly
+                        * created directory.  Failure to update here _is_
+                        * an error. */
+                       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 (file)
index 0000000..ca71fdb
--- /dev/null
@@ -0,0 +1,24 @@
+/*
+ * Utility routines.
+ *
+ * Copyright (C) 2006 Denys 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 "platform.h"
+#include <features.h>
+#include <sys/sysmacros.h>
+
+#ifdef __GLIBC__
+/* At least glibc has horrendously large inline for this, so wrap it */
+/* uclibc people please check - do we need "&& !__UCLIBC__" above? */
+
+/* suppress gcc "no previous prototype" warning */
+unsigned long long FAST_FUNC bb_makedev(unsigned int major, unsigned int minor);
+unsigned long long FAST_FUNC bb_makedev(unsigned int major, unsigned int minor)
+{
+       return makedev(major, minor);
+}
+#endif
diff --git a/libbb/match_fstype.c b/libbb/match_fstype.c
new file mode 100644 (file)
index 0000000..9360e75
--- /dev/null
@@ -0,0 +1,42 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Match fstypes for use in mount unmount
+ * We accept notmpfs,nfs but not notmpfs,nonfs
+ * This allows us to match fstypes that start with no like so
+ *   mount -at ,noddy
+ *
+ * Returns 1 for a match, otherwise 0
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.h"
+
+int FAST_FUNC match_fstype(const struct mntent *mt, const char *t_fstype)
+{
+       int match = 1;
+       int len;
+
+       if (!t_fstype)
+               return match;
+
+       if (t_fstype[0] == 'n' && t_fstype[1] == 'o') {
+               match--;
+               t_fstype += 2;
+       }
+
+       len = strlen(mt->mnt_type);
+       while (1) {
+               if (strncmp(mt->mnt_type, t_fstype, len) == 0
+                && (t_fstype[len] == '\0' || t_fstype[len] == ',')
+               ) {
+                       return match;
+               }
+               t_fstype = strchr(t_fstype, ',');
+               if (!t_fstype)
+                       break;
+               t_fstype++;
+       }
+
+       return !match;
+}
diff --git a/libbb/md5.c b/libbb/md5.c
new file mode 100644 (file)
index 0000000..768dfbc
--- /dev/null
@@ -0,0 +1,429 @@
+/* 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 <drepper@gnu.ai.mit.edu>, 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"
+
+/* 0: fastest, 3: smallest */
+#if CONFIG_MD5_SIZE_VS_SPEED < 0
+# define MD5_SIZE_VS_SPEED 0
+#elif CONFIG_MD5_SIZE_VS_SPEED > 3
+# define MD5_SIZE_VS_SPEED 3
+#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 FAST_FUNC 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))
+
+#define rotl32(w, s) (((w) << (s)) | ((w) >> (32 - (s))))
+
+/* 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[] ALIGN1 = {
+# if MD5_SIZE_VS_SPEED > 1
+               0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,   /* 1 */
+# endif
+               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[] ALIGN1 = {
+               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
+       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++;
+               temp = rotl32(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++;
+               temp = rotl32(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++;
+               temp = rotl32(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++;
+               temp = rotl32(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++;
+               temp = rotl32(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; \
+               a = rotl32(a, s); \
+               a += b; \
+       } while (0)
+
+       /* 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; \
+               a = rotl32(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 FAST_FUNC 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) {
+               unsigned 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 FAST_FUNC 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);
+
+       /* The MD5 result is in little endian byte order.
+        * We (ab)use the fact that A-D are consecutive in memory.
+        */
+#if BB_BIG_ENDIAN
+       ctx->A = SWAP_LE32(ctx->A);
+       ctx->B = SWAP_LE32(ctx->B);
+       ctx->C = SWAP_LE32(ctx->C);
+       ctx->D = SWAP_LE32(ctx->D);
+#endif
+       memcpy(resbuf, &ctx->A, sizeof(ctx->A) * 4);
+}
diff --git a/libbb/md5prime.c b/libbb/md5prime.c
new file mode 100644 (file)
index 0000000..7986f4d
--- /dev/null
@@ -0,0 +1,460 @@
+/* This file is not used by busybox right now.
+ * However, the code here seems to be a tiny bit smaller
+ * than one in md5.c. Need to investigate which one
+ * is better overall...
+ * Hint: grep for md5prime to find places where you can switch
+ * md5.c/md5prime.c
+ */
+
+/*
+ * MD5C.C - RSA Data Security, Inc., MD5 message-digest algorithm
+ *
+ * Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All
+ * rights reserved.
+ *
+ * License to copy and use this software is granted provided that it
+ * is identified as the "RSA Data Security, Inc. MD5 Message-Digest
+ * Algorithm" in all material mentioning or referencing this software
+ * or this function.
+ *
+ * License is also granted to make and use derivative works provided
+ * that such works are identified as "derived from the RSA Data
+ * Security, Inc. MD5 Message-Digest Algorithm" in all material
+ * mentioning or referencing the derived work.
+ *
+ * RSA Data Security, Inc. makes no representations concerning either
+ * the merchantability of this software or the suitability of this
+ * software for any particular purpose. It is provided "as is"
+ * without express or implied warranty of any kind.
+ *
+ * These notices must be retained in any copies of any part of this
+ * documentation and/or software.
+ *
+ * $FreeBSD: src/lib/libmd/md5c.c,v 1.9.2.1 1999/08/29 14:57:12 peter Exp $
+ *
+ * This code is the same as the code published by RSA Inc.  It has been
+ * edited for clarity and style only.
+ *
+ * ----------------------------------------------------------------------------
+ * The md5_crypt() function was taken from freeBSD's libcrypt and contains
+ * this license:
+ *    "THE BEER-WARE LICENSE" (Revision 42):
+ *     <phk@login.dknet.dk> wrote this file.  As long as you retain this notice you
+ *     can do whatever you want with this stuff. If we meet some day, and you think
+ *     this stuff is worth it, you can buy me a beer in return.   Poul-Henning Kamp
+ *
+ * $FreeBSD: src/lib/libcrypt/crypt.c,v 1.7.2.1 1999/08/29 14:56:33 peter Exp $
+ *
+ * ----------------------------------------------------------------------------
+ * On April 19th, 2001 md5_crypt() was modified to make it reentrant
+ * by Erik Andersen <andersen@uclibc.org>
+ *
+ * June 28, 2001             Manuel Novoa III
+ *
+ * "Un-inlined" code using loops and static const tables in order to
+ * reduce generated code size (on i386 from approx 4k to approx 2.5k).
+ *
+ * June 29, 2001             Manuel Novoa III
+ *
+ * Completely removed static PADDING array.
+ *
+ * Reintroduced the loop unrolling in md5_transform and added the
+ * MD5_SIZE_VS_SPEED option for configurability.  Define below as:
+ *       0    fully unrolled loops
+ *       1    partially unrolled (4 ops per loop)
+ *       2    no unrolling -- introduces the need to swap 4 variables (slow)
+ *       3    no unrolling and all 4 loops merged into one with switch
+ *               in each loop (glacial)
+ * On i386, sizes are roughly (-Os -fno-builtin):
+ *     0: 3k     1: 2.5k     2: 2.2k     3: 2k
+ *
+ * Since SuSv3 does not require crypt_r, modified again August 7, 2002
+ * by Erik Andersen to remove reentrance stuff...
+ */
+
+#include "libbb.h"
+
+/* 1: fastest, 3: smallest */
+#if CONFIG_MD5_SIZE_VS_SPEED < 1
+# define MD5_SIZE_VS_SPEED 1
+#elif CONFIG_MD5_SIZE_VS_SPEED > 3
+# define MD5_SIZE_VS_SPEED 3
+#else
+# define MD5_SIZE_VS_SPEED CONFIG_MD5_SIZE_VS_SPEED
+#endif
+
+#if BB_LITTLE_ENDIAN
+#define memcpy32_cpu2le memcpy
+#define memcpy32_le2cpu memcpy
+#else
+/* Encodes input (uint32_t) into output (unsigned char).
+ * Assumes len is a multiple of 4. */
+static void
+memcpy32_cpu2le(unsigned char *output, uint32_t *input, unsigned len)
+{
+       unsigned i, j;
+       for (i = 0, j = 0; j < len; i++, j += 4) {
+               output[j] = input[i];
+               output[j+1] = (input[i] >> 8);
+               output[j+2] = (input[i] >> 16);
+               output[j+3] = (input[i] >> 24);
+       }
+}
+/* Decodes input (unsigned char) into output (uint32_t).
+ * Assumes len is a multiple of 4. */
+static void
+memcpy32_le2cpu(uint32_t *output, const unsigned char *input, unsigned len)
+{
+       unsigned i, j;
+       for (i = 0, j = 0; j < len; i++, j += 4)
+               output[i] = ((uint32_t)input[j])
+                       | (((uint32_t)input[j+1]) << 8)
+                       | (((uint32_t)input[j+2]) << 16)
+                       | (((uint32_t)input[j+3]) << 24);
+}
+#endif /* i386 */
+
+/* F, G, H and I are basic MD5 functions. */
+#define F(x, y, z) (((x) & (y)) | (~(x) & (z)))
+#define G(x, y, z) (((x) & (z)) | ((y) & ~(z)))
+#define H(x, y, z) ((x) ^ (y) ^ (z))
+#define I(x, y, z) ((y) ^ ((x) | ~(z)))
+
+/* rotl32 rotates x left n bits. */
+#define rotl32(x, n) (((x) << (n)) | ((x) >> (32 - (n))))
+
+/*
+ * FF, GG, HH, and II transformations for rounds 1, 2, 3, and 4.
+ * Rotation is separate from addition to prevent recomputation.
+ */
+#define FF(a, b, c, d, x, s, ac) { \
+       (a) += F((b), (c), (d)) + (x) + (uint32_t)(ac); \
+       (a) = rotl32((a), (s)); \
+       (a) += (b); \
+       }
+#define GG(a, b, c, d, x, s, ac) { \
+       (a) += G((b), (c), (d)) + (x) + (uint32_t)(ac); \
+       (a) = rotl32((a), (s)); \
+       (a) += (b); \
+       }
+#define HH(a, b, c, d, x, s, ac) { \
+       (a) += H((b), (c), (d)) + (x) + (uint32_t)(ac); \
+       (a) = rotl32((a), (s)); \
+       (a) += (b); \
+       }
+#define II(a, b, c, d, x, s, ac) { \
+       (a) += I((b), (c), (d)) + (x) + (uint32_t)(ac); \
+       (a) = rotl32((a), (s)); \
+       (a) += (b); \
+       }
+
+/* MD5 basic transformation. Transforms state based on block. */
+static void md5_transform(uint32_t state[4], const unsigned char block[64])
+{
+       uint32_t a, b, c, d, x[16];
+#if MD5_SIZE_VS_SPEED > 1
+       uint32_t temp;
+       const unsigned char *ps;
+
+       static const unsigned char S[] = {
+               7, 12, 17, 22,
+               5, 9, 14, 20,
+               4, 11, 16, 23,
+               6, 10, 15, 21
+       };
+#endif /* MD5_SIZE_VS_SPEED > 1 */
+
+#if MD5_SIZE_VS_SPEED > 0
+       const uint32_t *pc;
+       const unsigned char *pp;
+       int i;
+
+       static const uint32_t C[] = {
+               /* 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 unsigned char P[] = {
+               0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, /* 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 */
+       };
+
+#endif /* MD5_SIZE_VS_SPEED > 0 */
+
+       memcpy32_le2cpu(x, block, 64);
+
+       a = state[0];
+       b = state[1];
+       c = state[2];
+       d = state[3];
+
+#if MD5_SIZE_VS_SPEED > 2
+       pc = C;
+       pp = P;
+       ps = S - 4;
+       for (i = 0; i < 64; i++) {
+               if ((i & 0x0f) == 0) ps += 4;
+               temp = a;
+               switch (i>>4) {
+                       case 0:
+                               temp += F(b, c, d);
+                               break;
+                       case 1:
+                               temp += G(b, c, d);
+                               break;
+                       case 2:
+                               temp += H(b, c, d);
+                               break;
+                       case 3:
+                               temp += I(b, c, d);
+                               break;
+               }
+               temp += x[*pp++] + *pc++;
+               temp = rotl32(temp, ps[i & 3]);
+               temp += b;
+               a = d; d = c; c = b; b = temp;
+       }
+#elif MD5_SIZE_VS_SPEED > 1
+       pc = C;
+       pp = P;
+       ps = S;
+       /* Round 1 */
+       for (i = 0; i < 16; i++) {
+               FF(a, b, c, d, x[*pp], ps[i & 0x3], *pc); pp++; pc++;
+               temp = d; d = c; c = b; b = a; a = temp;
+       }
+       /* Round 2 */
+       ps += 4;
+       for (; i < 32; i++) {
+               GG(a, b, c, d, x[*pp], ps[i & 0x3], *pc); pp++; pc++;
+               temp = d; d = c; c = b; b = a; a = temp;
+       }
+       /* Round 3 */
+       ps += 4;
+       for (; i < 48; i++) {
+               HH(a, b, c, d, x[*pp], ps[i & 0x3], *pc); pp++; pc++;
+               temp = d; d = c; c = b; b = a; a = temp;
+       }
+       /* Round 4 */
+       ps += 4;
+       for (; i < 64; i++) {
+               II(a, b, c, d, x[*pp], ps[i & 0x3], *pc); pp++; pc++;
+               temp = d; d = c; c = b; b = a; a = temp;
+       }
+#elif MD5_SIZE_VS_SPEED > 0
+       pc = C;
+       pp = P;
+       /* Round 1 */
+       for (i = 0; i < 4; i++) {
+               FF(a, b, c, d, x[*pp],  7, *pc); pp++; pc++;
+               FF(d, a, b, c, x[*pp], 12, *pc); pp++; pc++;
+               FF(c, d, a, b, x[*pp], 17, *pc); pp++; pc++;
+               FF(b, c, d, a, x[*pp], 22, *pc); pp++; pc++;
+       }
+       /* Round 2 */
+       for (i = 0; i < 4; i++) {
+               GG(a, b, c, d, x[*pp],  5, *pc); pp++; pc++;
+               GG(d, a, b, c, x[*pp],  9, *pc); pp++; pc++;
+               GG(c, d, a, b, x[*pp], 14, *pc); pp++; pc++;
+               GG(b, c, d, a, x[*pp], 20, *pc); pp++; pc++;
+       }
+       /* Round 3 */
+       for (i = 0; i < 4; i++) {
+               HH(a, b, c, d, x[*pp],  4, *pc); pp++; pc++;
+               HH(d, a, b, c, x[*pp], 11, *pc); pp++; pc++;
+               HH(c, d, a, b, x[*pp], 16, *pc); pp++; pc++;
+               HH(b, c, d, a, x[*pp], 23, *pc); pp++; pc++;
+       }
+       /* Round 4 */
+       for (i = 0; i < 4; i++) {
+               II(a, b, c, d, x[*pp],  6, *pc); pp++; pc++;
+               II(d, a, b, c, x[*pp], 10, *pc); pp++; pc++;
+               II(c, d, a, b, x[*pp], 15, *pc); pp++; pc++;
+               II(b, c, d, a, x[*pp], 21, *pc); pp++; pc++;
+       }
+#else
+       /* Round 1 */
+#define S11 7
+#define S12 12
+#define S13 17
+#define S14 22
+       FF(a, b, c, d, x[ 0], S11, 0xd76aa478); /* 1 */
+       FF(d, a, b, c, x[ 1], S12, 0xe8c7b756); /* 2 */
+       FF(c, d, a, b, x[ 2], S13, 0x242070db); /* 3 */
+       FF(b, c, d, a, x[ 3], S14, 0xc1bdceee); /* 4 */
+       FF(a, b, c, d, x[ 4], S11, 0xf57c0faf); /* 5 */
+       FF(d, a, b, c, x[ 5], S12, 0x4787c62a); /* 6 */
+       FF(c, d, a, b, x[ 6], S13, 0xa8304613); /* 7 */
+       FF(b, c, d, a, x[ 7], S14, 0xfd469501); /* 8 */
+       FF(a, b, c, d, x[ 8], S11, 0x698098d8); /* 9 */
+       FF(d, a, b, c, x[ 9], S12, 0x8b44f7af); /* 10 */
+       FF(c, d, a, b, x[10], S13, 0xffff5bb1); /* 11 */
+       FF(b, c, d, a, x[11], S14, 0x895cd7be); /* 12 */
+       FF(a, b, c, d, x[12], S11, 0x6b901122); /* 13 */
+       FF(d, a, b, c, x[13], S12, 0xfd987193); /* 14 */
+       FF(c, d, a, b, x[14], S13, 0xa679438e); /* 15 */
+       FF(b, c, d, a, x[15], S14, 0x49b40821); /* 16 */
+       /* Round 2 */
+#define S21 5
+#define S22 9
+#define S23 14
+#define S24 20
+       GG(a, b, c, d, x[ 1], S21, 0xf61e2562); /* 17 */
+       GG(d, a, b, c, x[ 6], S22, 0xc040b340); /* 18 */
+       GG(c, d, a, b, x[11], S23, 0x265e5a51); /* 19 */
+       GG(b, c, d, a, x[ 0], S24, 0xe9b6c7aa); /* 20 */
+       GG(a, b, c, d, x[ 5], S21, 0xd62f105d); /* 21 */
+       GG(d, a, b, c, x[10], S22,  0x2441453); /* 22 */
+       GG(c, d, a, b, x[15], S23, 0xd8a1e681); /* 23 */
+       GG(b, c, d, a, x[ 4], S24, 0xe7d3fbc8); /* 24 */
+       GG(a, b, c, d, x[ 9], S21, 0x21e1cde6); /* 25 */
+       GG(d, a, b, c, x[14], S22, 0xc33707d6); /* 26 */
+       GG(c, d, a, b, x[ 3], S23, 0xf4d50d87); /* 27 */
+       GG(b, c, d, a, x[ 8], S24, 0x455a14ed); /* 28 */
+       GG(a, b, c, d, x[13], S21, 0xa9e3e905); /* 29 */
+       GG(d, a, b, c, x[ 2], S22, 0xfcefa3f8); /* 30 */
+       GG(c, d, a, b, x[ 7], S23, 0x676f02d9); /* 31 */
+       GG(b, c, d, a, x[12], S24, 0x8d2a4c8a); /* 32 */
+       /* Round 3 */
+#define S31 4
+#define S32 11
+#define S33 16
+#define S34 23
+       HH(a, b, c, d, x[ 5], S31, 0xfffa3942); /* 33 */
+       HH(d, a, b, c, x[ 8], S32, 0x8771f681); /* 34 */
+       HH(c, d, a, b, x[11], S33, 0x6d9d6122); /* 35 */
+       HH(b, c, d, a, x[14], S34, 0xfde5380c); /* 36 */
+       HH(a, b, c, d, x[ 1], S31, 0xa4beea44); /* 37 */
+       HH(d, a, b, c, x[ 4], S32, 0x4bdecfa9); /* 38 */
+       HH(c, d, a, b, x[ 7], S33, 0xf6bb4b60); /* 39 */
+       HH(b, c, d, a, x[10], S34, 0xbebfbc70); /* 40 */
+       HH(a, b, c, d, x[13], S31, 0x289b7ec6); /* 41 */
+       HH(d, a, b, c, x[ 0], S32, 0xeaa127fa); /* 42 */
+       HH(c, d, a, b, x[ 3], S33, 0xd4ef3085); /* 43 */
+       HH(b, c, d, a, x[ 6], S34,  0x4881d05); /* 44 */
+       HH(a, b, c, d, x[ 9], S31, 0xd9d4d039); /* 45 */
+       HH(d, a, b, c, x[12], S32, 0xe6db99e5); /* 46 */
+       HH(c, d, a, b, x[15], S33, 0x1fa27cf8); /* 47 */
+       HH(b, c, d, a, x[ 2], S34, 0xc4ac5665); /* 48 */
+       /* Round 4 */
+#define S41 6
+#define S42 10
+#define S43 15
+#define S44 21
+       II(a, b, c, d, x[ 0], S41, 0xf4292244); /* 49 */
+       II(d, a, b, c, x[ 7], S42, 0x432aff97); /* 50 */
+       II(c, d, a, b, x[14], S43, 0xab9423a7); /* 51 */
+       II(b, c, d, a, x[ 5], S44, 0xfc93a039); /* 52 */
+       II(a, b, c, d, x[12], S41, 0x655b59c3); /* 53 */
+       II(d, a, b, c, x[ 3], S42, 0x8f0ccc92); /* 54 */
+       II(c, d, a, b, x[10], S43, 0xffeff47d); /* 55 */
+       II(b, c, d, a, x[ 1], S44, 0x85845dd1); /* 56 */
+       II(a, b, c, d, x[ 8], S41, 0x6fa87e4f); /* 57 */
+       II(d, a, b, c, x[15], S42, 0xfe2ce6e0); /* 58 */
+       II(c, d, a, b, x[ 6], S43, 0xa3014314); /* 59 */
+       II(b, c, d, a, x[13], S44, 0x4e0811a1); /* 60 */
+       II(a, b, c, d, x[ 4], S41, 0xf7537e82); /* 61 */
+       II(d, a, b, c, x[11], S42, 0xbd3af235); /* 62 */
+       II(c, d, a, b, x[ 2], S43, 0x2ad7d2bb); /* 63 */
+       II(b, c, d, a, x[ 9], S44, 0xeb86d391); /* 64 */
+#endif
+
+       state[0] += a;
+       state[1] += b;
+       state[2] += c;
+       state[3] += d;
+
+       /* Zeroize sensitive information. */
+       memset(x, 0, sizeof(x));
+}
+
+
+/* MD5 initialization. */
+void FAST_FUNC md5_begin(md5_ctx_t *context)
+{
+       context->count[0] = context->count[1] = 0;
+       /* Load magic initialization constants.  */
+       context->state[0] = 0x67452301;
+       context->state[1] = 0xefcdab89;
+       context->state[2] = 0x98badcfe;
+       context->state[3] = 0x10325476;
+}
+
+/*
+ * MD5 block update operation. Continues an MD5 message-digest
+ * operation, processing another message block, and updating
+ * the context.
+ */
+void FAST_FUNC md5_hash(const void *buffer, size_t inputLen, md5_ctx_t *context)
+{
+       unsigned i, idx, partLen;
+       const unsigned char *input = buffer;
+
+       /* Compute number of bytes mod 64 */
+       idx = (context->count[0] >> 3) & 0x3F;
+
+       /* Update number of bits */
+       context->count[0] += (inputLen << 3);
+       if (context->count[0] < (inputLen << 3))
+               context->count[1]++;
+       context->count[1] += (inputLen >> 29);
+
+       /* Transform as many times as possible. */
+       i = 0;
+       partLen = 64 - idx;
+       if (inputLen >= partLen) {
+               memcpy(&context->buffer[idx], input, partLen);
+               md5_transform(context->state, context->buffer);
+               for (i = partLen; i + 63 < inputLen; i += 64)
+                       md5_transform(context->state, &input[i]);
+               idx = 0;
+       }
+
+       /* Buffer remaining input */
+       memcpy(&context->buffer[idx], &input[i], inputLen - i);
+}
+
+/*
+ * MD5 finalization. Ends an MD5 message-digest operation,
+ * writing the message digest.
+ */
+void FAST_FUNC md5_end(void *digest, md5_ctx_t *context)
+{
+       unsigned idx, padLen;
+       unsigned char bits[8];
+       unsigned char padding[64];
+
+       /* Add padding followed by original length. */
+       memset(padding, 0, sizeof(padding));
+       padding[0] = 0x80;
+       /* save number of bits */
+       memcpy32_cpu2le(bits, context->count, 8);
+       /* pad out to 56 mod 64 */
+       idx = (context->count[0] >> 3) & 0x3f;
+       padLen = (idx < 56) ? (56 - idx) : (120 - idx);
+       md5_hash(padding, padLen, context);
+       /* append length (before padding) */
+       md5_hash(bits, 8, context);
+
+       /* Store state in digest */
+       memcpy32_cpu2le(digest, context->state, 16);
+}
diff --git a/libbb/messages.c b/libbb/messages.c
new file mode 100644 (file)
index 0000000..9009028
--- /dev/null
@@ -0,0 +1,73 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Copyright (C) 1999-2004 by Erik Andersen <andersen@codepoet.org>
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.h"
+
+/* allow default system PATH to be extended via CFLAGS */
+#ifndef BB_ADDITIONAL_PATH
+#define BB_ADDITIONAL_PATH ""
+#endif
+
+/* allow version to be extended, via CFLAGS */
+#ifndef BB_EXTRA_VERSION
+#define BB_EXTRA_VERSION BB_BT
+#endif
+
+#define BANNER "BusyBox v" BB_VER " (" BB_EXTRA_VERSION ")"
+
+const char bb_banner[] ALIGN1 = BANNER;
+
+
+const char bb_msg_memory_exhausted[] ALIGN1 = "memory exhausted";
+const char bb_msg_invalid_date[] ALIGN1 = "invalid date '%s'";
+const char bb_msg_write_error[] ALIGN1 = "write error";
+const char bb_msg_read_error[] ALIGN1 = "read error";
+const char bb_msg_unknown[] ALIGN1 = "(unknown)";
+const char bb_msg_can_not_create_raw_socket[] ALIGN1 = "can't create raw socket";
+const char bb_msg_perm_denied_are_you_root[] ALIGN1 = "permission denied. (are you root?)";
+const char bb_msg_requires_arg[] ALIGN1 = "%s requires an argument";
+const char bb_msg_invalid_arg[] ALIGN1 = "invalid argument '%s' to '%s'";
+const char bb_msg_standard_input[] ALIGN1 = "standard input";
+const char bb_msg_standard_output[] ALIGN1 = "standard output";
+
+const char bb_str_default[] ALIGN1 = "default";
+const char bb_hexdigits_upcase[] ALIGN1 = "0123456789ABCDEF";
+
+const char bb_path_passwd_file[] ALIGN1 = "/etc/passwd";
+const char bb_path_shadow_file[] ALIGN1 = "/etc/shadow";
+const char bb_path_group_file[] ALIGN1 = "/etc/group";
+const char bb_path_gshadow_file[] ALIGN1 = "/etc/gshadow";
+const char bb_path_motd_file[] ALIGN1 = "/etc/motd";
+const char bb_dev_null[] ALIGN1 = "/dev/null";
+const char bb_busybox_exec_path[] ALIGN1 = CONFIG_BUSYBOX_EXEC_PATH;
+const char bb_default_login_shell[] ALIGN1 = LIBBB_DEFAULT_LOGIN_SHELL;
+/* util-linux manpage says /sbin:/bin:/usr/sbin:/usr/bin,
+ * but I want to save a few bytes here. Check libbb.h before changing! */
+const char bb_PATH_root_path[] ALIGN1 =
+       "PATH=/sbin:/usr/sbin:/bin:/usr/bin" BB_ADDITIONAL_PATH;
+
+
+const int const_int_1 = 1;
+/* explicitly = 0, otherwise gcc may make it a common variable
+ * and it will end up in bss */
+const int const_int_0 = 0;
+
+#include <utmp.h>
+/* This is usually something like "/var/adm/wtmp" or "/var/log/wtmp" */
+const char bb_path_wtmp_file[] ALIGN1 =
+#if defined _PATH_WTMP
+       _PATH_WTMP;
+#elif defined WTMP_FILE
+       WTMP_FILE;
+#else
+#error unknown path to wtmp file
+#endif
+
+/* We use it for "global" data via *(struct global*)&bb_common_bufsiz1.
+ * Since gcc insists on aligning struct global's members, it would be a pity
+ * (and an alignment fault on some CPUs) to mess it up. */
+char bb_common_bufsiz1[COMMON_BUFSIZE] ALIGNED(sizeof(long long));
diff --git a/libbb/mode_string.c b/libbb/mode_string.c
new file mode 100644 (file)
index 0000000..7d4e514
--- /dev/null
@@ -0,0 +1,128 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * mode_string implementation for busybox
+ *
+ * Copyright (C) 2003  Manuel Novoa III  <mjn3@codepoet.org>
+ *
+ * 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 <assert.h>
+#include <sys/stat.h>
+
+#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] ALIGN1 = "?pc?d?b?-?l?s???";
+/***************************************** 0123456789abcdef */
+static const char mode_chars[7] ALIGN1 = "rwxSTst";
+
+const char* FAST_FUNC bb_mode_string(mode_t 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* FAST_FUNC bb_mode_string(mode_t 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 (file)
index 0000000..586a661
--- /dev/null
@@ -0,0 +1,55 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Utility routines.
+ *
+ * Copyright (C) 1999-2004 by Erik Andersen <andersen@codepoet.org>
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+#include <mntent.h>
+#include "libbb.h"
+
+#if ENABLE_FEATURE_MTAB_SUPPORT
+void FAST_FUNC erase_mtab(const char *name)
+{
+       struct mntent *entries;
+       int i, count;
+       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;
+       }
+
+       entries = NULL;
+       count = 0;
+       while ((m = getmntent(mountTable)) != 0) {
+               entries = xrealloc_vector(entries, 3, count);
+               entries[count].mnt_fsname = xstrdup(m->mnt_fsname);
+               entries[count].mnt_dir = xstrdup(m->mnt_dir);
+               entries[count].mnt_type = xstrdup(m->mnt_type);
+               entries[count].mnt_opts = xstrdup(m->mnt_opts);
+               entries[count].mnt_freq = m->mnt_freq;
+               entries[count].mnt_passno = m->mnt_passno;
+               count++;
+       }
+       endmntent(mountTable);
+
+//TODO: make update atomic
+       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 (file)
index 0000000..030b148
--- /dev/null
@@ -0,0 +1,15 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Utility routines.
+ *
+ * Copyright (C) 1999-2004 by Erik Andersen <andersen@codepoet.org>
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+#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[] ALIGN1 =
+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 (file)
index 0000000..19b8752
--- /dev/null
@@ -0,0 +1,170 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Mini weak password checker implementation for busybox
+ *
+ * Copyright (C) 2006 Tito Ragusa <farmatito@tiscali.it>
+ *
+ * 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;
+
+       /* 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 */
+       hostname = safe_gethostname();
+       i = string_checker(new_p, hostname);
+       free(hostname);
+       if (i)
+               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) {
+                       p = strchr(p, new_p[i]);
+                       if (p == 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<<i)) size -= 2;
+       if (length < size)
+               return "too weak";
+
+       if (old_p && old_p[0] != '\0') {
+               /* check vs. old password */
+               if (string_checker(new_p, old_p)) {
+                       return "similar to old password";
+               }
+       }
+       return NULL;
+}
+
+int FAST_FUNC obscure(const char *old, const char *newval, const struct passwd *pw)
+{
+       const char *msg;
+
+       msg = obscure_msg(old, newval, pw);
+       if (msg) {
+               printf("Bad password: %s\n", msg);
+               return 1;
+       }
+       return 0;
+}
diff --git a/libbb/parse_config.c b/libbb/parse_config.c
new file mode 100644 (file)
index 0000000..74f0524
--- /dev/null
@@ -0,0 +1,219 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * config file parser helper
+ *
+ * Copyright (C) 2008 by Vladimir Dronnikov <dronnikov@gmail.com>
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ * Also for use in uClibc (http://uclibc.org/) licensed under LGPLv2.1 or later.
+ */
+
+#include "libbb.h"
+
+#if defined ENABLE_PARSE && ENABLE_PARSE
+int parse_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int parse_main(int argc UNUSED_PARAM, char **argv)
+{
+       const char *delims = "# \t";
+       unsigned flags = PARSE_NORMAL;
+       int mintokens = 0, ntokens = 128;
+
+       opt_complementary = "-1:n+:m+:f+";
+       getopt32(argv, "n:m:d:f:", &ntokens, &mintokens, &delims, &flags);
+       //argc -= optind;
+       argv += optind;
+       while (*argv) {
+               parser_t *p = config_open(*argv);
+               if (p) {
+                       int n;
+                       char **t = xmalloc(sizeof(char *) * ntokens);
+                       while ((n = config_read(p, t, ntokens, mintokens, delims, flags)) != 0) {
+                               for (int i = 0; i < n; ++i)
+                                       printf("[%s]", t[i]);
+                               puts("");
+                       }
+                       config_close(p);
+               }
+               argv++;
+       }
+       return EXIT_SUCCESS;
+}
+#endif
+
+/*
+
+Typical usage:
+
+----- CUT -----
+       char *t[3];     // tokens placeholder
+       parser_t *p = config_open(filename);
+       if (p) {
+               // parse line-by-line
+               while (config_read(p, t, 3, 0, delimiters, flags)) { // 1..3 tokens
+                       // use tokens
+                       bb_error_msg("TOKENS: [%s][%s][%s]", t[0], t[1], t[2]);
+               }
+               ...
+               // free parser
+               config_close(p);
+       }
+----- CUT -----
+
+*/
+
+parser_t* FAST_FUNC config_open2(const char *filename, FILE* FAST_FUNC (*fopen_func)(const char *path))
+{
+       FILE* fp;
+       parser_t *parser;
+
+       fp = fopen_func(filename);
+       if (!fp)
+               return NULL;
+       parser = xzalloc(sizeof(*parser));
+       parser->fp = fp;
+       return parser;
+}
+
+parser_t* FAST_FUNC config_open(const char *filename)
+{
+       return config_open2(filename, fopen_or_warn_stdin);
+}
+
+static void config_free_data(parser_t *const parser)
+{
+       free(parser->line);
+       parser->line = NULL;
+       if (PARSE_KEEP_COPY) { /* compile-time constant */
+               free(parser->data);
+               parser->data = NULL;
+       }
+}
+
+void FAST_FUNC config_close(parser_t *parser)
+{
+       if (parser) {
+               config_free_data(parser);
+               fclose(parser->fp);
+               free(parser);
+       }
+}
+
+/*
+0. If parser is NULL return 0.
+1. Read a line from config file. If nothing to read then return 0.
+   Handle continuation character. Advance lineno for each physical line.
+   Discard everything past comment characher.
+2. if PARSE_TRIM is set (default), remove leading and trailing delimiters.
+3. If resulting line is empty goto 1.
+4. Look for first delimiter. If !PARSE_COLLAPSE or !PARSE_TRIM is set then
+   remember the token as empty.
+5. Else (default) if number of seen tokens is equal to max number of tokens
+   (token is the last one) and PARSE_GREEDY is set then the remainder
+   of the line is the last token.
+   Else (token is not last or PARSE_GREEDY is not set) just replace
+   first delimiter with '\0' thus delimiting the token.
+6. Advance line pointer past the end of token. If number of seen tokens
+   is less than required number of tokens then goto 4.
+7. Check the number of seen tokens is not less the min number of tokens.
+   Complain or die otherwise depending on PARSE_MIN_DIE.
+8. Return the number of seen tokens.
+
+mintokens > 0 make config_read() print error message if less than mintokens
+(but more than 0) are found. Empty lines are always skipped (not warned about).
+*/
+#undef config_read
+int FAST_FUNC config_read(parser_t *parser, char **tokens, unsigned flags, const char *delims)
+{
+       char *line;
+       int ntokens, mintokens;
+       int t, len;
+
+       ntokens = flags & 0xFF;
+       mintokens = (flags & 0xFF00) >> 8;
+
+       if (parser == NULL)
+               return 0;
+
+again:
+       memset(tokens, 0, sizeof(tokens[0]) * ntokens);
+       config_free_data(parser);
+
+       /* Read one line (handling continuations with backslash) */
+       line = bb_get_chunk_with_continuation(parser->fp, &len, &parser->lineno);
+       if (line == NULL)
+               return 0;
+       parser->line = line;
+
+       /* Strip trailing line-feed if any */
+       if (len && line[len-1] == '\n')
+               line[len-1] = '\0';
+
+       /* Skip token in the start of line? */
+       if (flags & PARSE_TRIM)
+               line += strspn(line, delims + 1);
+
+       if (line[0] == '\0' || line[0] == delims[0])
+               goto again;
+
+       if (flags & PARSE_KEEP_COPY)
+               parser->data = xstrdup(line);
+
+       /* Tokenize the line */
+       for (t = 0; *line && *line != delims[0] && t < ntokens; t++) {
+               /* Pin token */
+               tokens[t] = line;
+
+               /* Combine remaining arguments? */
+               if ((t != (ntokens-1)) || !(flags & PARSE_GREEDY)) {
+                       /* Vanilla token, find next delimiter */
+                       line += strcspn(line, delims[0] ? delims : delims + 1);
+               } else {
+                       /* Combining, find comment char if any */
+                       line = strchrnul(line, delims[0]);
+
+                       /* Trim any extra delimiters from the end */
+                       if (flags & PARSE_TRIM) {
+                               while (strchr(delims + 1, line[-1]) != NULL)
+                                       line--;
+                       }
+               }
+
+               /* Token not terminated? */
+               if (line[0] == delims[0])
+                       *line = '\0';
+               else if (line[0] != '\0')
+                       *(line++) = '\0';
+
+#if 0 /* unused so far */
+               if (flags & PARSE_ESCAPE) {
+                       const char *from;
+                       char *to;
+
+                       from = to = tokens[t];
+                       while (*from) {
+                               if (*from == '\\') {
+                                       from++;
+                                       *to++ = bb_process_escape_sequence(&from);
+                               } else {
+                                       *to++ = *from++;
+                               }
+                       }
+                       *to = '\0';
+               }
+#endif
+
+               /* Skip possible delimiters */
+               if (flags & PARSE_COLLAPSE)
+                       line += strspn(line, delims + 1);
+       }
+
+       if (t < mintokens) {
+               bb_error_msg("bad line %u: %d tokens found, %d needed",
+                               parser->lineno, t, mintokens);
+               if (flags & PARSE_MIN_DIE)
+                       xfunc_die();
+               goto again;
+       }
+
+       return t;
+}
diff --git a/libbb/parse_mode.c b/libbb/parse_mode.c
new file mode 100644 (file)
index 0000000..40105dd
--- /dev/null
@@ -0,0 +1,150 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * parse_mode implementation for busybox
+ *
+ * Copyright (C) 2003  Manuel Novoa III  <mjn3@codepoet.org>
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+/* http://www.opengroup.org/onlinepubs/007904975/utilities/chmod.html */
+
+#include "libbb.h"
+
+/* This function is used from NOFORK applets. It must not allocate anything */
+
+#define FILEMODEBITS (S_ISUID | S_ISGID | S_ISVTX | S_IRWXU | S_IRWXG | S_IRWXO)
+
+int FAST_FUNC 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[] ALIGN1 = "augo";
+       static const char perm_chars[] ALIGN1 = "rwxXst";
+
+       const char *p;
+       mode_t wholist;
+       mode_t permlist;
+       mode_t new_mode;
+       char op;
+
+       if (((unsigned int)(*s - '0')) < 8) {
+               unsigned long tmp;
+               char *e;
+
+               tmp = strtoul(s, &e, 8);
+               if (*e || (tmp > 07777U)) { /* Check range and trailing chars. */
+                       return 0;
+               }
+               *current_mode = tmp;
+               return 1;
+       }
+
+       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, or 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 = wholist;
+                               if (!wholist) {
+                                       mode_t u_mask = umask(0);
+                                       umask(u_mask);
+                                       tmp = ~u_mask;
+                               }
+                               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 (file)
index 0000000..6c8e1b5
--- /dev/null
@@ -0,0 +1,25 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Utility routines.
+ *
+ * Copyright (C) 1999-2004 by Erik Andersen <andersen@codepoet.org>
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.h"
+
+void FAST_FUNC bb_perror_msg(const char *s, ...)
+{
+       va_list p;
+
+       va_start(p, s);
+       /* Guard against "<error message>: Success" */
+       bb_verror_msg(s, p, errno ? strerror(errno) : NULL);
+       va_end(p);
+}
+
+void FAST_FUNC bb_simple_perror_msg(const char *s)
+{
+       bb_perror_msg("%s", s);
+}
diff --git a/libbb/perror_msg_and_die.c b/libbb/perror_msg_and_die.c
new file mode 100644 (file)
index 0000000..15615fa
--- /dev/null
@@ -0,0 +1,26 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Utility routines.
+ *
+ * Copyright (C) 1999-2004 by Erik Andersen <andersen@codepoet.org>
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.h"
+
+void FAST_FUNC bb_perror_msg_and_die(const char *s, ...)
+{
+       va_list p;
+
+       va_start(p, s);
+       /* Guard against "<error message>: Success" */
+       bb_verror_msg(s, p, errno ? strerror(errno) : NULL);
+       va_end(p);
+       xfunc_die();
+}
+
+void FAST_FUNC bb_simple_perror_msg_and_die(const char *s)
+{
+       bb_perror_msg_and_die("%s", s);
+}
diff --git a/libbb/perror_nomsg.c b/libbb/perror_nomsg.c
new file mode 100644 (file)
index 0000000..a157caa
--- /dev/null
@@ -0,0 +1,22 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * bb_perror_nomsg implementation for busybox
+ *
+ * Copyright (C) 2003  Manuel Novoa III  <mjn3@codepoet.org>
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+/* gcc warns about a null format string, therefore we provide
+ * modified definition without "attribute (format)"
+ * instead of including libbb.h */
+//#include "libbb.h"
+#include "platform.h"
+extern void bb_perror_msg(const char *s, ...) FAST_FUNC;
+
+/* suppress gcc "no previous prototype" warning */
+void FAST_FUNC bb_perror_nomsg(void);
+void FAST_FUNC bb_perror_nomsg(void)
+{
+       bb_perror_msg(0);
+}
diff --git a/libbb/perror_nomsg_and_die.c b/libbb/perror_nomsg_and_die.c
new file mode 100644 (file)
index 0000000..d56e05d
--- /dev/null
@@ -0,0 +1,22 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * bb_perror_nomsg_and_die implementation for busybox
+ *
+ * Copyright (C) 2003  Manuel Novoa III  <mjn3@codepoet.org>
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+/* gcc warns about a null format string, therefore we provide
+ * modified definition without "attribute (format)"
+ * instead of including libbb.h */
+//#include "libbb.h"
+#include "platform.h"
+extern void bb_perror_msg_and_die(const char *s, ...) FAST_FUNC;
+
+/* suppress gcc "no previous prototype" warning */
+void FAST_FUNC bb_perror_nomsg_and_die(void);
+void FAST_FUNC bb_perror_nomsg_and_die(void)
+{
+       bb_perror_msg_and_die(0);
+}
diff --git a/libbb/pidfile.c b/libbb/pidfile.c
new file mode 100644 (file)
index 0000000..7b8fee2
--- /dev/null
@@ -0,0 +1,40 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * pid file routines
+ *
+ * Copyright (C) 2007 by Stephane Billiart <stephane.billiart@gmail.com>
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+/* Override ENABLE_FEATURE_PIDFILE */
+#define WANT_PIDFILE 1
+#include "libbb.h"
+
+smallint wrote_pidfile;
+
+void FAST_FUNC write_pidfile(const char *path)
+{
+       int pid_fd;
+       char *end;
+       char buf[sizeof(int)*3 + 2];
+       struct stat sb;
+
+       if (!path)
+               return;
+       /* we will overwrite stale pidfile */
+       pid_fd = open(path, O_WRONLY|O_CREAT|O_TRUNC, 0666);
+       if (pid_fd < 0)
+               return;
+
+       /* path can be "/dev/null"! Test for such cases */
+       wrote_pidfile = (fstat(pid_fd, &sb) == 0) && S_ISREG(sb.st_mode);
+
+       if (wrote_pidfile) {
+               /* few bytes larger, but doesn't use stdio */
+               end = utoa_to_buf(getpid(), buf, sizeof(buf));
+               *end = '\n';
+               full_write(pid_fd, buf, end - buf + 1);
+       }
+       close(pid_fd);
+}
diff --git a/libbb/print_flags.c b/libbb/print_flags.c
new file mode 100644 (file)
index 0000000..afa7550
--- /dev/null
@@ -0,0 +1,32 @@
+/* vi: set sw=4 ts=4: */
+/* Print string that matches bit masked flags
+ *
+ * Copyright (C) 2008 Natanael Copa <natanael.copa@gmail.com>
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+#include <libbb.h>
+
+/* returns a set with the flags not printed */
+int FAST_FUNC print_flags_separated(const int *masks, const char *labels, int flags, const char *separator)
+{
+       const char *need_separator = NULL;
+       while (*labels) {
+               if (flags & *masks) {
+                       printf("%s%s",
+                               need_separator ? need_separator : "",
+                               labels);
+                       need_separator = separator;
+                       flags &= ~ *masks;
+               }
+               masks++;
+               labels += strlen(labels) + 1;
+       }
+       return flags;
+}
+
+int FAST_FUNC print_flags(const masks_labels_t *ml, int flags)
+{
+       return print_flags_separated(ml->masks, ml->labels, flags, NULL);
+}
diff --git a/libbb/printable.c b/libbb/printable.c
new file mode 100644 (file)
index 0000000..ae93359
--- /dev/null
@@ -0,0 +1,34 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Utility routines.
+ *
+ * Copyright (C) 2007 Denys Vlasenko
+ *
+ * Licensed under GPL version 2, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.h"
+
+void FAST_FUNC fputc_printable(int ch, FILE *file)
+{
+       if ((ch & (0x80 + PRINTABLE_META)) == (0x80 + PRINTABLE_META)) {
+               fputs("M-", file);
+               ch &= 0x7f;
+       }
+       ch = (unsigned char) ch;
+       if (ch == 0x9b) {
+               /* VT100's CSI, aka Meta-ESC, is not printable on vt-100 */
+               ch = '{';
+               goto print_caret;
+       }
+       if (ch < ' ') {
+               ch += '@';
+               goto print_caret;
+       }
+       if (ch == 0x7f) {
+               ch = '?';
+ print_caret:
+               fputc('^', file);
+       }
+       fputc(ch, file);
+}
diff --git a/libbb/process_escape_sequence.c b/libbb/process_escape_sequence.c
new file mode 100644 (file)
index 0000000..6de2cac
--- /dev/null
@@ -0,0 +1,89 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Utility routines.
+ *
+ * Copyright (C) Manuel Novoa III <mjn3@codepoet.org>
+ * and Vladimir Oleynik <dzo@simtreas.ru>
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+#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 FAST_FUNC bb_process_escape_sequence(const char **ptr)
+{
+       /* bash builtin "echo -e '\ec'" interprets \e as ESC,
+        * but coreutils "/bin/echo -e '\ec'" does not.
+        * manpages tend to support coreutils way. */
+       static const char charmap[] ALIGN1 = {
+               'a',  'b', /*'e',*/ 'f',  'n',  'r',  't',  'v',  '\\', 0,
+               '\a', '\b', /*27,*/ '\f', '\n', '\r', '\t', '\v', '\\', '\\' };
+
+       const char *p;
+       const char *q;
+       unsigned num_digits;
+       unsigned r;
+       unsigned n;
+       unsigned d;
+       unsigned 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 char)(*q) - '0';
+#ifdef WANT_HEX_ESCAPES
+               if (d >= 10) {
+                       d = (unsigned char)(_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 (file)
index 0000000..c5e40bf
--- /dev/null
@@ -0,0 +1,476 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Utility routines.
+ *
+ * Copyright 1998 by Albert Cahalan; all rights reserved.
+ * Copyright (C) 2002 by Vladimir Oleynik <dzo@simtreas.ru>
+ * SELinux support: (c) 2007 by Yuichi Nakamura <ynakam@hitachisoft.jp>
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.h"
+
+
+typedef struct unsigned_to_name_map_t {
+       long id;
+       char name[USERNAME_MAX_SIZE];
+} 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 FAST_FUNC 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_vector(cp->cache, 2, i);
+       cp->cache[i++].id = id;
+       return -i;
+}
+#endif
+
+static char* get_cached(cache_t *cp, long id,
+                       char* FAST_FUNC x2x_utoa(long id))
+{
+       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_vector(cp->cache, 2, i);
+       cp->cache[i].id = id;
+       /* Never fails. Generates numeric string if name isn't found */
+       safe_strncpy(cp->cache[i].name, x2x_utoa(id), sizeof(cp->cache[i].name));
+       return cp->cache[i].name;
+}
+const char* FAST_FUNC get_cached_username(uid_t uid)
+{
+       return get_cached(&username, uid, uid2uname_utoa);
+}
+const char* FAST_FUNC get_cached_groupname(gid_t gid)
+{
+       return get_cached(&groupname, gid, gid2group_utoa);
+}
+
+
+#define PROCPS_BUFSIZE 1024
+
+static int read_to_buf(const char *filename, void *buf)
+{
+       int fd;
+       /* open_read_close() would do two reads, checking for EOF.
+        * When you have 10000 /proc/$NUM/stat to read, it isn't desirable */
+       ssize_t ret = -1;
+       fd = open(filename, O_RDONLY);
+       if (fd >= 0) {
+               ret = read(fd, buf, PROCPS_BUFSIZE-1);
+               close(fd);
+       }
+       ((char *)buf)[ret > 0 ? ret : 0] = '\0';
+       return ret;
+}
+
+static procps_status_t* FAST_FUNC alloc_procps_scan(void)
+{
+       unsigned n = getpagesize();
+       procps_status_t* sp = xzalloc(sizeof(procps_status_t));
+       sp->dir = xopendir("/proc");
+       while (1) {
+               n >>= 1;
+               if (!n) break;
+               sp->shift_pages_to_bytes++;
+       }
+       sp->shift_pages_to_kb = sp->shift_pages_to_bytes - 10;
+       return sp;
+}
+
+void FAST_FUNC free_procps_scan(procps_status_t* sp)
+{
+       closedir(sp->dir);
+       free(sp->argv0);
+       USE_SELINUX(free(sp->context);)
+       free(sp);
+}
+
+#if ENABLE_FEATURE_TOPMEM
+static unsigned long fast_strtoul_16(char **endptr)
+{
+       unsigned char c;
+       char *str = *endptr;
+       unsigned long n = 0;
+
+       while ((c = *str++) != ' ') {
+               c = ((c|0x20) - '0');
+               if (c > 9)
+                       // c = c + '0' - 'a' + 10:
+                       c = c - ('a' - '0' - 10);
+               n = n*16 + c;
+       }
+       *endptr = str; /* We skip trailing space! */
+       return n;
+}
+/* TOPMEM uses fast_strtoul_10, so... */
+#undef ENABLE_FEATURE_FAST_TOP
+#define ENABLE_FEATURE_FAST_TOP 1
+#endif
+
+#if ENABLE_FEATURE_FAST_TOP
+/* We cut a lot of corners here for speed */
+static unsigned long fast_strtoul_10(char **endptr)
+{
+       char c;
+       char *str = *endptr;
+       unsigned long n = *str - '0';
+
+       while ((c = *++str) != ' ')
+               n = n*10 + (c - '0');
+
+       *endptr = str + 1; /* We skip trailing space! */
+       return n;
+}
+static char *skip_fields(char *str, int count)
+{
+       do {
+               while (*str++ != ' ')
+                       continue;
+               /* we found a space char, str points after it */
+       } while (--count);
+       return str;
+}
+#endif
+
+void BUG_comm_size(void);
+procps_status_t* FAST_FUNC 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();
+
+       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->vsz, 0, sizeof(*sp) - offsetof(procps_status_t, vsz));
+
+               sp->pid = pid;
+               if (!(flags & ~PSSCAN_PID)) break;
+
+#if ENABLE_SELINUX
+               if (flags & PSSCAN_CONTEXT) {
+                       if (getpidcon(sp->pid, &sp->context) < 0)
+                               sp->context = NULL;
+               }
+#endif
+
+               filename_tail = filename + sprintf(filename, "/proc/%d", pid);
+
+               if (flags & PSSCAN_UIDGID) {
+                       if (stat(filename, &sb))
+                               break;
+                       /* Need comment - is this effective or real UID/GID? */
+                       sp->uid = sb.st_uid;
+                       sp->gid = sb.st_gid;
+               }
+
+               if (flags & PSSCAN_STAT) {
+                       char *cp, *comm1;
+                       int tty;
+#if !ENABLE_FEATURE_FAST_TOP
+                       unsigned long vsz, rss;
+#endif
+                       /* 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 "<rest>" */
+                       /*if (!cp || cp[1] != ' ')
+                               break;*/
+                       cp[0] = '\0';
+                       if (sizeof(sp->comm) < 16)
+                               BUG_comm_size();
+                       comm1 = strchr(buf, '(');
+                       /*if (comm1)*/
+                               safe_strncpy(sp->comm, comm1 + 1, sizeof(sp->comm));
+
+#if !ENABLE_FEATURE_FAST_TOP
+                       n = sscanf(cp+2,
+                               "%c %u "               /* state, ppid */
+                               "%u %u %d %*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 "             /* timeout, it_real_value */
+                               "%lu "                 /* start_time */
+                               "%lu "                 /* vsize */
+                               "%lu "                 /* rss */
+#if ENABLE_FEATURE_TOP_SMP_PROCESS
+                               "%*s %*s %*s %*s %*s %*s " /*rss_rlim, start_code, end_code, start_stack, kstk_esp, kstk_eip */
+                               "%*s %*s %*s %*s "         /*signal, blocked, sigignore, sigcatch */
+                               "%*s %*s %*s %*s "         /*wchan, nswap, cnswap, exit_signal */
+                               "%d"                       /*cpu last seen on*/
+#endif
+                               ,
+                               sp->state, &sp->ppid,
+                               &sp->pgid, &sp->sid, &tty,
+                               &sp->utime, &sp->stime,
+                               &tasknice,
+                               &sp->start_time,
+                               &vsz,
+                               &rss
+#if ENABLE_FEATURE_TOP_SMP_PROCESS
+                               , &sp->last_seen_on_cpu
+#endif
+                               );
+
+                       if (n < 11)
+                               break;
+#if ENABLE_FEATURE_TOP_SMP_PROCESS
+                       if (n < 11+15)
+                               sp->last_seen_on_cpu = 0;
+#endif
+
+                       /* vsz is in bytes and we want kb */
+                       sp->vsz = vsz >> 10;
+                       /* vsz is in bytes but rss is in *PAGES*! Can you believe that? */
+                       sp->rss = rss << sp->shift_pages_to_kb;
+                       sp->tty_major = (tty >> 8) & 0xfff;
+                       sp->tty_minor = (tty & 0xff) | ((tty >> 12) & 0xfff00);
+#else
+/* This costs ~100 bytes more but makes top faster by 20%
+ * If you run 10000 processes, this may be important for you */
+                       sp->state[0] = cp[2];
+                       cp += 4;
+                       sp->ppid = fast_strtoul_10(&cp);
+                       sp->pgid = fast_strtoul_10(&cp);
+                       sp->sid = fast_strtoul_10(&cp);
+                       tty = fast_strtoul_10(&cp);
+                       sp->tty_major = (tty >> 8) & 0xfff;
+                       sp->tty_minor = (tty & 0xff) | ((tty >> 12) & 0xfff00);
+                       cp = skip_fields(cp, 6); /* tpgid, flags, min_flt, cmin_flt, maj_flt, cmaj_flt */
+                       sp->utime = fast_strtoul_10(&cp);
+                       sp->stime = fast_strtoul_10(&cp);
+                       cp = skip_fields(cp, 3); /* cutime, cstime, priority */
+                       tasknice = fast_strtoul_10(&cp);
+                       cp = skip_fields(cp, 2); /* timeout, it_real_value */
+                       sp->start_time = fast_strtoul_10(&cp);
+                       /* vsz is in bytes and we want kb */
+                       sp->vsz = fast_strtoul_10(&cp) >> 10;
+                       /* vsz is in bytes but rss is in *PAGES*! Can you believe that? */
+                       sp->rss = fast_strtoul_10(&cp) << sp->shift_pages_to_kb;
+#if ENABLE_FEATURE_TOP_SMP_PROCESS
+                       /* (6): rss_rlim, start_code, end_code, start_stack, kstk_esp, kstk_eip */
+                       /* (4): signal, blocked, sigignore, sigcatch */
+                       /* (4): wchan, nswap, cnswap, exit_signal */
+                       cp = skip_fields(cp, 14);
+//FIXME: is it safe to assume this field exists?
+                       sp->last_seen_on_cpu = fast_strtoul_10(&cp);
+#endif
+#endif /* end of !ENABLE_FEATURE_TOP_SMP_PROCESS */
+
+                       if (sp->vsz == 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] = ' ';
+               }
+
+#if ENABLE_FEATURE_TOPMEM
+               if (flags & (PSSCAN_SMAPS)) {
+                       FILE *file;
+
+                       strcpy(filename_tail, "/smaps");
+                       file = fopen_for_read(filename);
+                       if (!file)
+                               break;
+                       while (fgets(buf, sizeof(buf), file)) {
+                               unsigned long sz;
+                               char *tp;
+                               char w;
+#define SCAN(str, name) \
+       if (strncmp(buf, str, sizeof(str)-1) == 0) { \
+               tp = skip_whitespace(buf + sizeof(str)-1); \
+               sp->name += fast_strtoul_10(&tp); \
+               continue; \
+       }
+                               SCAN("Shared_Clean:" , shared_clean );
+                               SCAN("Shared_Dirty:" , shared_dirty );
+                               SCAN("Private_Clean:", private_clean);
+                               SCAN("Private_Dirty:", private_dirty);
+#undef SCAN
+                               // f7d29000-f7d39000 rw-s ADR M:m OFS FILE
+                               tp = strchr(buf, '-');
+                               if (tp) {
+                                       *tp = ' ';
+                                       tp = buf;
+                                       sz = fast_strtoul_16(&tp); /* start */
+                                       sz = (fast_strtoul_16(&tp) - sz) >> 10; /* end - start */
+                                       // tp -> "rw-s" string
+                                       w = tp[1];
+                                       // skipping "rw-s ADR M:m OFS "
+                                       tp = skip_whitespace(skip_fields(tp, 4));
+                                       // filter out /dev/something (something != zero)
+                                       if (strncmp(tp, "/dev/", 5) != 0 || strcmp(tp, "/dev/zero\n") == 0) {
+                                               if (w == 'w') {
+                                                       sp->mapped_rw += sz;
+                                               } else if (w == '-') {
+                                                       sp->mapped_ro += sz;
+                                               }
+                                       }
+//else printf("DROPPING %s (%s)\n", buf, tp);
+                                       if (strcmp(tp, "[stack]\n") == 0)
+                                               sp->stack += sz;
+                               }
+                       }
+                       fclose(file);
+               }
+#endif /* TOPMEM */
+
+#if 0 /* PSSCAN_CMD is not used */
+               if (flags & (PSSCAN_CMD|PSSCAN_ARGV0)) {
+                       free(sp->argv0);
+                       sp->argv0 = NULL;
+                       free(sp->cmd);
+                       sp->cmd = NULL;
+                       strcpy(filename_tail, "/cmdline");
+                       /* TODO: to get rid of size limits, read into malloc buf,
+                        * then realloc it down to real size. */
+                       n = read_to_buf(filename, buf);
+                       if (n <= 0)
+                               break;
+                       if (flags & PSSCAN_ARGV0)
+                               sp->argv0 = xstrdup(buf);
+                       if (flags & PSSCAN_CMD) {
+                               do {
+                                       n--;
+                                       if ((unsigned char)(buf[n]) < ' ')
+                                               buf[n] = ' ';
+                               } while (n);
+                               sp->cmd = xstrdup(buf);
+                       }
+               }
+#else
+               if (flags & (PSSCAN_ARGV0|PSSCAN_ARGVN)) {
+                       free(sp->argv0);
+                       sp->argv0 = NULL;
+                       strcpy(filename_tail, "/cmdline");
+                       n = read_to_buf(filename, buf);
+                       if (n <= 0)
+                               break;
+                       if (flags & PSSCAN_ARGVN) {
+                               sp->argv_len = n;
+                               sp->argv0 = xmalloc(n + 1);
+                               memcpy(sp->argv0, buf, n + 1);
+                               /* sp->argv0[n] = '\0'; - buf has it */
+                       } else {
+                               sp->argv_len = 0;
+                               sp->argv0 = xstrdup(buf);
+                       }
+               }
+#endif
+               break;
+       }
+       return sp;
+}
+
+void FAST_FUNC read_cmdline(char *buf, int col, unsigned pid, const char *comm)
+{
+       ssize_t sz;
+       char filename[sizeof("/proc//cmdline") + sizeof(int)*3];
+
+       sprintf(filename, "/proc/%u/cmdline", pid);
+       sz = open_read_close(filename, buf, col);
+       if (sz > 0) {
+               buf[sz] = '\0';
+               while (--sz >= 0)
+                       if ((unsigned char)(buf[sz]) < ' ')
+                               buf[sz] = ' ';
+       } else {
+               snprintf(buf, col, "[%s]", comm);
+       }
+}
+
+/* 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/ptr_to_globals.c b/libbb/ptr_to_globals.c
new file mode 100644 (file)
index 0000000..5f30e2a
--- /dev/null
@@ -0,0 +1,35 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Copyright (C) 2008 by Denys Vlasenko <vda.linux@googlemail.com>
+ *
+ * Licensed under GPLv2, see file LICENSE in this tarball for details.
+ */
+
+#include <errno.h>
+
+struct globals;
+
+#ifndef GCC_COMBINE
+
+/* We cheat here. It is declared as const ptr in libbb.h,
+ * but here we make it live in R/W memory */
+struct globals *ptr_to_globals;
+
+#ifdef __GLIBC__
+int *bb_errno;
+#endif
+
+
+#else
+
+
+/* gcc -combine will see through and complain */
+/* Using alternative method which is more likely to break
+ * on weird architectures, compilers, linkers and so on */
+struct globals *const ptr_to_globals __attribute__ ((section (".data")));
+
+#ifdef __GLIBC__
+int *const bb_errno __attribute__ ((section (".data")));
+#endif
+
+#endif
diff --git a/libbb/pw_encrypt.c b/libbb/pw_encrypt.c
new file mode 100644 (file)
index 0000000..6fc0ba8
--- /dev/null
@@ -0,0 +1,127 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Utility routines.
+ *
+ * Copyright (C) 1999-2004 by Erik Andersen <andersen@codepoet.org>
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.h"
+
+/* static const uint8_t ascii64[] =
+ * "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
+ */
+
+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);
+}
+
+int FAST_FUNC crypt_make_salt(char *p, int cnt, int x)
+{
+       x += getpid() + time(NULL);
+       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';
+       return x;
+}
+
+#if ENABLE_USE_BB_CRYPT
+
+static char*
+to64(char *s, unsigned v, int n)
+{
+       while (--n >= 0) {
+               /* *s++ = ascii64[v & 0x3f]; */
+               *s++ = i64c(v);
+               v >>= 6;
+       }
+       return s;
+}
+
+/*
+ * DES and MD5 crypt implementations are taken from uclibc.
+ * They were modified to not use static buffers.
+ */
+
+#include "pw_encrypt_des.c"
+#include "pw_encrypt_md5.c"
+#if ENABLE_USE_BB_CRYPT_SHA
+#include "pw_encrypt_sha.c"
+#endif
+
+/* Other advanced crypt ids (TODO?): */
+/* $2$ or $2a$: Blowfish */
+
+static struct const_des_ctx *des_cctx;
+static struct des_ctx *des_ctx;
+
+/* my_crypt returns malloc'ed data */
+static char *my_crypt(const char *key, const char *salt)
+{
+       /* MD5 or SHA? */
+       if (salt[0] == '$' && salt[1] && salt[2] == '$') {
+               if (salt[1] == '1')
+                       return md5_crypt(xzalloc(MD5_OUT_BUFSIZE), (unsigned char*)key, (unsigned char*)salt);
+#if ENABLE_USE_BB_CRYPT_SHA
+               if (salt[1] == '5' || salt[1] == '6')
+                       return sha_crypt((char*)key, (char*)salt);
+#endif
+       }
+
+       if (!des_cctx)
+               des_cctx = const_des_init();
+       des_ctx = des_init(des_ctx, des_cctx);
+       return des_crypt(des_ctx, xzalloc(DES_OUT_BUFSIZE), (unsigned char*)key, (unsigned char*)salt);
+}
+
+/* So far nobody wants to have it public */
+static void my_crypt_cleanup(void)
+{
+       free(des_cctx);
+       free(des_ctx);
+       des_cctx = NULL;
+       des_ctx = NULL;
+}
+
+char* FAST_FUNC pw_encrypt(const char *clear, const char *salt, int cleanup)
+{
+       char *encrypted;
+
+       encrypted = my_crypt(clear, salt);
+
+       if (cleanup)
+               my_crypt_cleanup();
+
+       return encrypted;
+}
+
+#else /* if !ENABLE_USE_BB_CRYPT */
+
+char* FAST_FUNC pw_encrypt(const char *clear, const char *salt, int cleanup)
+{
+       return xstrdup(crypt(clear, salt));
+}
+
+#endif
diff --git a/libbb/pw_encrypt_des.c b/libbb/pw_encrypt_des.c
new file mode 100644 (file)
index 0000000..c8e02dd
--- /dev/null
@@ -0,0 +1,820 @@
+/*
+ * FreeSec: libcrypt for NetBSD
+ *
+ * Copyright (c) 1994 David Burren
+ * All rights reserved.
+ *
+ * Adapted for FreeBSD-2.0 by Geoffrey M. Rehmet
+ *     this file should now *only* export crypt(), in order to make
+ *     binaries of libcrypt exportable from the USA
+ *
+ * Adapted for FreeBSD-4.0 by Mark R V Murray
+ *     this file should now *only* export crypt_des(), in order to make
+ *     a module that can be optionally included in libcrypt.
+ *
+ * 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 author nor the names of other contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
+ *
+ * This is an original implementation of the DES and the crypt(3) interfaces
+ * by David Burren <davidb@werj.com.au>.
+ *
+ * An excellent reference on the underlying algorithm (and related
+ * algorithms) is:
+ *
+ *     B. Schneier, Applied Cryptography: protocols, algorithms,
+ *     and source code in C, John Wiley & Sons, 1994.
+ *
+ * Note that in that book's description of DES the lookups for the initial,
+ * pbox, and final permutations are inverted (this has been brought to the
+ * attention of the author).  A list of errata for this book has been
+ * posted to the sci.crypt newsgroup by the author and is available for FTP.
+ *
+ * ARCHITECTURE ASSUMPTIONS:
+ *     It is assumed that the 8-byte arrays passed by reference can be
+ *     addressed as arrays of uint32_t's (ie. the CPU is not picky about
+ *     alignment).
+ */
+
+
+/* Parts busybox doesn't need or had optimized */
+#define USE_PRECOMPUTED_u_sbox 1
+#define USE_REPETITIVE_SPEEDUP 0
+#define USE_ip_mask 0
+#define USE_de_keys 0
+
+
+/* A pile of data */
+static const uint8_t IP[64] = {
+       58, 50, 42, 34, 26, 18, 10,  2, 60, 52, 44, 36, 28, 20, 12,  4,
+       62, 54, 46, 38, 30, 22, 14,  6, 64, 56, 48, 40, 32, 24, 16,  8,
+       57, 49, 41, 33, 25, 17,  9,  1, 59, 51, 43, 35, 27, 19, 11,  3,
+       61, 53, 45, 37, 29, 21, 13,  5, 63, 55, 47, 39, 31, 23, 15,  7
+};
+
+static const uint8_t key_perm[56] = {
+       57, 49, 41, 33, 25, 17,  9,  1, 58, 50, 42, 34, 26, 18,
+       10,  2, 59, 51, 43, 35, 27, 19, 11,  3, 60, 52, 44, 36,
+       63, 55, 47, 39, 31, 23, 15,  7, 62, 54, 46, 38, 30, 22,
+       14,  6, 61, 53, 45, 37, 29, 21, 13,  5, 28, 20, 12,  4
+};
+
+static const uint8_t key_shifts[16] = {
+       1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1
+};
+
+static const uint8_t comp_perm[48] = {
+       14, 17, 11, 24,  1,  5,  3, 28, 15,  6, 21, 10,
+       23, 19, 12,  4, 26,  8, 16,  7, 27, 20, 13,  2,
+       41, 52, 31, 37, 47, 55, 30, 40, 51, 45, 33, 48,
+       44, 49, 39, 56, 34, 53, 46, 42, 50, 36, 29, 32
+};
+
+/*
+ * No E box is used, as it's replaced by some ANDs, shifts, and ORs.
+ */
+#if !USE_PRECOMPUTED_u_sbox
+static const uint8_t sbox[8][64] = {
+       {       14,  4, 13,  1,  2, 15, 11,  8,  3, 10,  6, 12,  5,  9,  0,  7,
+                0, 15,  7,  4, 14,  2, 13,  1, 10,  6, 12, 11,  9,  5,  3,  8,
+                4,  1, 14,  8, 13,  6,  2, 11, 15, 12,  9,  7,  3, 10,  5,  0,
+               15, 12,  8,  2,  4,  9,  1,  7,  5, 11,  3, 14, 10,  0,  6, 13
+       },
+       {       15,  1,  8, 14,  6, 11,  3,  4,  9,  7,  2, 13, 12,  0,  5, 10,
+                3, 13,  4,  7, 15,  2,  8, 14, 12,  0,  1, 10,  6,  9, 11,  5,
+                0, 14,  7, 11, 10,  4, 13,  1,  5,  8, 12,  6,  9,  3,  2, 15,
+               13,  8, 10,  1,  3, 15,  4,  2, 11,  6,  7, 12,  0,  5, 14,  9
+       },
+       {       10,  0,  9, 14,  6,  3, 15,  5,  1, 13, 12,  7, 11,  4,  2,  8,
+               13,  7,  0,  9,  3,  4,  6, 10,  2,  8,  5, 14, 12, 11, 15,  1,
+               13,  6,  4,  9,  8, 15,  3,  0, 11,  1,  2, 12,  5, 10, 14,  7,
+                1, 10, 13,  0,  6,  9,  8,  7,  4, 15, 14,  3, 11,  5,  2, 12
+       },
+       {        7, 13, 14,  3,  0,  6,  9, 10,  1,  2,  8,  5, 11, 12,  4, 15,
+               13,  8, 11,  5,  6, 15,  0,  3,  4,  7,  2, 12,  1, 10, 14,  9,
+               10,  6,  9,  0, 12, 11,  7, 13, 15,  1,  3, 14,  5,  2,  8,  4,
+                3, 15,  0,  6, 10,  1, 13,  8,  9,  4,  5, 11, 12,  7,  2, 14
+       },
+       {        2, 12,  4,  1,  7, 10, 11,  6,  8,  5,  3, 15, 13,  0, 14,  9,
+               14, 11,  2, 12,  4,  7, 13,  1,  5,  0, 15, 10,  3,  9,  8,  6,
+                4,  2,  1, 11, 10, 13,  7,  8, 15,  9, 12,  5,  6,  3,  0, 14,
+               11,  8, 12,  7,  1, 14,  2, 13,  6, 15,  0,  9, 10,  4,  5,  3
+       },
+       {       12,  1, 10, 15,  9,  2,  6,  8,  0, 13,  3,  4, 14,  7,  5, 11,
+               10, 15,  4,  2,  7, 12,  9,  5,  6,  1, 13, 14,  0, 11,  3,  8,
+                9, 14, 15,  5,  2,  8, 12,  3,  7,  0,  4, 10,  1, 13, 11,  6,
+                4,  3,  2, 12,  9,  5, 15, 10, 11, 14,  1,  7,  6,  0,  8, 13
+       },
+       {        4, 11,  2, 14, 15,  0,  8, 13,  3, 12,  9,  7,  5, 10,  6,  1,
+               13,  0, 11,  7,  4,  9,  1, 10, 14,  3,  5, 12,  2, 15,  8,  6,
+                1,  4, 11, 13, 12,  3,  7, 14, 10, 15,  6,  8,  0,  5,  9,  2,
+                6, 11, 13,  8,  1,  4, 10,  7,  9,  5,  0, 15, 14,  2,  3, 12
+       },
+       {       13,  2,  8,  4,  6, 15, 11,  1, 10,  9,  3, 14,  5,  0, 12,  7,
+                1, 15, 13,  8, 10,  3,  7,  4, 12,  5,  6, 11,  0, 14,  9,  2,
+                7, 11,  4,  1,  9, 12, 14,  2,  0,  6, 10, 13, 15,  3,  5,  8,
+                2,  1, 14,  7,  4, 10,  8, 13, 15, 12,  9,  0,  3,  5,  6, 11
+       }
+};
+#else /* precomputed, with half-bytes packed into one byte */
+static const uint8_t u_sbox[8][32] = {
+       {       0x0e, 0xf4, 0x7d, 0x41, 0xe2, 0x2f, 0xdb, 0x18,
+               0xa3, 0x6a, 0xc6, 0xbc, 0x95, 0x59, 0x30, 0x87,
+               0xf4, 0xc1, 0x8e, 0x28, 0x4d, 0x96, 0x12, 0x7b,
+               0x5f, 0xbc, 0x39, 0xe7, 0xa3, 0x0a, 0x65, 0xd0,
+       },
+       {       0x3f, 0xd1, 0x48, 0x7e, 0xf6, 0x2b, 0x83, 0xe4,
+               0xc9, 0x07, 0x12, 0xad, 0x6c, 0x90, 0xb5, 0x5a,
+               0xd0, 0x8e, 0xa7, 0x1b, 0x3a, 0xf4, 0x4d, 0x21,
+               0xb5, 0x68, 0x7c, 0xc6, 0x09, 0x53, 0xe2, 0x9f,
+       },
+       {       0xda, 0x70, 0x09, 0x9e, 0x36, 0x43, 0x6f, 0xa5,
+               0x21, 0x8d, 0x5c, 0xe7, 0xcb, 0xb4, 0xf2, 0x18,
+               0x1d, 0xa6, 0xd4, 0x09, 0x68, 0x9f, 0x83, 0x70,
+               0x4b, 0xf1, 0xe2, 0x3c, 0xb5, 0x5a, 0x2e, 0xc7,
+       },
+       {       0xd7, 0x8d, 0xbe, 0x53, 0x60, 0xf6, 0x09, 0x3a,
+               0x41, 0x72, 0x28, 0xc5, 0x1b, 0xac, 0xe4, 0x9f,
+               0x3a, 0xf6, 0x09, 0x60, 0xac, 0x1b, 0xd7, 0x8d,
+               0x9f, 0x41, 0x53, 0xbe, 0xc5, 0x72, 0x28, 0xe4,
+       },
+       {       0xe2, 0xbc, 0x24, 0xc1, 0x47, 0x7a, 0xdb, 0x16,
+               0x58, 0x05, 0xf3, 0xaf, 0x3d, 0x90, 0x8e, 0x69,
+               0xb4, 0x82, 0xc1, 0x7b, 0x1a, 0xed, 0x27, 0xd8,
+               0x6f, 0xf9, 0x0c, 0x95, 0xa6, 0x43, 0x50, 0x3e,
+       },
+       {       0xac, 0xf1, 0x4a, 0x2f, 0x79, 0xc2, 0x96, 0x58,
+               0x60, 0x1d, 0xd3, 0xe4, 0x0e, 0xb7, 0x35, 0x8b,
+               0x49, 0x3e, 0x2f, 0xc5, 0x92, 0x58, 0xfc, 0xa3,
+               0xb7, 0xe0, 0x14, 0x7a, 0x61, 0x0d, 0x8b, 0xd6,
+       },
+       {       0xd4, 0x0b, 0xb2, 0x7e, 0x4f, 0x90, 0x18, 0xad,
+               0xe3, 0x3c, 0x59, 0xc7, 0x25, 0xfa, 0x86, 0x61,
+               0x61, 0xb4, 0xdb, 0x8d, 0x1c, 0x43, 0xa7, 0x7e,
+               0x9a, 0x5f, 0x06, 0xf8, 0xe0, 0x25, 0x39, 0xc2,
+       },
+       {       0x1d, 0xf2, 0xd8, 0x84, 0xa6, 0x3f, 0x7b, 0x41,
+               0xca, 0x59, 0x63, 0xbe, 0x05, 0xe0, 0x9c, 0x27,
+               0x27, 0x1b, 0xe4, 0x71, 0x49, 0xac, 0x8e, 0xd2,
+               0xf0, 0xc6, 0x9a, 0x0d, 0x3f, 0x53, 0x65, 0xb8,
+       },
+};
+#endif
+
+static const uint8_t pbox[32] = {
+       16,  7, 20, 21, 29, 12, 28, 17,  1, 15, 23, 26,  5, 18, 31, 10,
+        2,  8, 24, 14, 32, 27,  3,  9, 19, 13, 30,  6, 22, 11,  4, 25
+};
+
+static const uint32_t bits32[32] =
+{
+       0x80000000, 0x40000000, 0x20000000, 0x10000000,
+       0x08000000, 0x04000000, 0x02000000, 0x01000000,
+       0x00800000, 0x00400000, 0x00200000, 0x00100000,
+       0x00080000, 0x00040000, 0x00020000, 0x00010000,
+       0x00008000, 0x00004000, 0x00002000, 0x00001000,
+       0x00000800, 0x00000400, 0x00000200, 0x00000100,
+       0x00000080, 0x00000040, 0x00000020, 0x00000010,
+       0x00000008, 0x00000004, 0x00000002, 0x00000001
+};
+
+static const uint8_t bits8[8] = { 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01 };
+
+
+static int
+ascii_to_bin(char ch)
+{
+       if (ch > 'z')
+               return 0;
+       if (ch >= 'a')
+               return (ch - 'a' + 38);
+       if (ch > 'Z')
+               return 0;
+       if (ch >= 'A')
+               return (ch - 'A' + 12);
+       if (ch > '9')
+               return 0;
+       if (ch >= '.')
+               return (ch - '.');
+       return 0;
+}
+
+
+/* Static stuff that stays resident and doesn't change after
+ * being initialized, and therefore doesn't need to be made
+ * reentrant. */
+struct const_des_ctx {
+#if USE_ip_mask
+       uint8_t init_perm[64]; /* referenced 2 times */
+#endif
+       uint8_t final_perm[64]; /* 2 times */
+       uint8_t m_sbox[4][4096]; /* 5 times */
+};
+#define C (*cctx)
+#define init_perm  (C.init_perm )
+#define final_perm (C.final_perm)
+#define m_sbox     (C.m_sbox    )
+
+static struct const_des_ctx*
+const_des_init(void)
+{
+       unsigned i, j, b;
+       struct const_des_ctx *cctx;
+
+#if !USE_PRECOMPUTED_u_sbox
+       uint8_t u_sbox[8][64];
+
+       cctx = xmalloc(sizeof(*cctx));
+
+       /* Invert the S-boxes, reordering the input bits. */
+       for (i = 0; i < 8; i++) {
+               for (j = 0; j < 64; j++) {
+                       b = (j & 0x20) | ((j & 1) << 4) | ((j >> 1) & 0xf);
+                       u_sbox[i][j] = sbox[i][b];
+               }
+       }
+       for (i = 0; i < 8; i++) {
+               fprintf(stderr, "\t{\t");
+               for (j = 0; j < 64; j+=2)
+                       fprintf(stderr, " 0x%02x,", u_sbox[i][j] + u_sbox[i][j+1]*16);
+               fprintf(stderr, "\n\t},\n");
+       }
+       /*
+        * Convert the inverted S-boxes into 4 arrays of 8 bits.
+        * Each will handle 12 bits of the S-box input.
+        */
+       for (b = 0; b < 4; b++)
+               for (i = 0; i < 64; i++)
+                       for (j = 0; j < 64; j++)
+                               m_sbox[b][(i << 6) | j] =
+                                       (uint8_t)((u_sbox[(b << 1)][i] << 4) |
+                                               u_sbox[(b << 1) + 1][j]);
+#else
+       cctx = xmalloc(sizeof(*cctx));
+
+       /*
+        * Convert the inverted S-boxes into 4 arrays of 8 bits.
+        * Each will handle 12 bits of the S-box input.
+        */
+       for (b = 0; b < 4; b++)
+        for (i = 0; i < 64; i++)
+         for (j = 0; j < 64; j++) {
+               uint8_t lo, hi;
+               hi = u_sbox[(b << 1)][i / 2];
+               if (!(i & 1))
+                       hi <<= 4;
+               lo = u_sbox[(b << 1) + 1][j / 2];
+               if (j & 1)
+                       lo >>= 4;
+               m_sbox[b][(i << 6) | j] = (hi & 0xf0) | (lo & 0x0f);
+       }
+#endif
+
+       /*
+        * Set up the initial & final permutations into a useful form.
+        */
+       for (i = 0; i < 64; i++) {
+               final_perm[i] = IP[i] - 1;
+#if USE_ip_mask
+               init_perm[final_perm[i]] = (uint8_t)i;
+#endif
+       }
+
+       return cctx;
+}
+
+
+struct des_ctx {
+       const struct const_des_ctx *const_ctx;
+       uint32_t saltbits; /* referenced 5 times */
+#if USE_REPETITIVE_SPEEDUP
+       uint32_t old_salt; /* 3 times */
+       uint32_t old_rawkey0, old_rawkey1; /* 3 times each */
+#endif
+       uint8_t un_pbox[32]; /* 2 times */
+       uint8_t inv_comp_perm[56]; /* 3 times */
+       uint8_t inv_key_perm[64]; /* 3 times */
+       uint32_t en_keysl[16], en_keysr[16]; /* 2 times each */
+#if USE_de_keys
+       uint32_t de_keysl[16], de_keysr[16]; /* 2 times each */
+#endif
+#if USE_ip_mask
+       uint32_t ip_maskl[8][256], ip_maskr[8][256]; /* 9 times each */
+#endif
+       uint32_t fp_maskl[8][256], fp_maskr[8][256]; /* 9 times each */
+       uint32_t key_perm_maskl[8][128], key_perm_maskr[8][128]; /* 9 times */
+       uint32_t comp_maskl[8][128], comp_maskr[8][128]; /* 9 times each */
+       uint32_t psbox[4][256]; /* 5 times */
+};
+#define D (*ctx)
+#define const_ctx       (D.const_ctx      )
+#define saltbits        (D.saltbits       )
+#define old_salt        (D.old_salt       )
+#define old_rawkey0     (D.old_rawkey0    )
+#define old_rawkey1     (D.old_rawkey1    )
+#define un_pbox         (D.un_pbox        )
+#define inv_comp_perm   (D.inv_comp_perm  )
+#define inv_key_perm    (D.inv_key_perm   )
+#define en_keysl        (D.en_keysl       )
+#define en_keysr        (D.en_keysr       )
+#define de_keysl        (D.de_keysl       )
+#define de_keysr        (D.de_keysr       )
+#define ip_maskl        (D.ip_maskl       )
+#define ip_maskr        (D.ip_maskr       )
+#define fp_maskl        (D.fp_maskl       )
+#define fp_maskr        (D.fp_maskr       )
+#define key_perm_maskl  (D.key_perm_maskl )
+#define key_perm_maskr  (D.key_perm_maskr )
+#define comp_maskl      (D.comp_maskl     )
+#define comp_maskr      (D.comp_maskr     )
+#define psbox           (D.psbox          )
+
+static struct des_ctx*
+des_init(struct des_ctx *ctx, const struct const_des_ctx *cctx)
+{
+       int i, j, b, k, inbit, obit;
+       uint32_t p;
+       const uint32_t *bits28, *bits24;
+
+       if (!ctx)
+               ctx = xmalloc(sizeof(*ctx));
+       const_ctx = cctx;
+
+#if USE_REPETITIVE_SPEEDUP
+       old_rawkey0 = old_rawkey1 = 0;
+       old_salt = 0;
+#endif
+       saltbits = 0;
+       bits28 = bits32 + 4;
+       bits24 = bits28 + 4;
+
+       /* Initialise the inverted key permutation. */
+       for (i = 0; i < 64; i++) {
+               inv_key_perm[i] = 255;
+       }
+
+       /*
+        * Invert the key permutation and initialise the inverted key
+        * compression permutation.
+        */
+       for (i = 0; i < 56; i++) {
+               inv_key_perm[key_perm[i] - 1] = (uint8_t)i;
+               inv_comp_perm[i] = 255;
+       }
+
+       /* Invert the key compression permutation. */
+       for (i = 0; i < 48; i++) {
+               inv_comp_perm[comp_perm[i] - 1] = (uint8_t)i;
+       }
+
+       /*
+        * Set up the OR-mask arrays for the initial and final permutations,
+        * and for the key initial and compression permutations.
+        */
+       for (k = 0; k < 8; k++) {
+               uint32_t il, ir;
+               uint32_t fl, fr;
+               for (i = 0; i < 256; i++) {
+#if USE_ip_mask
+                       il = 0;
+                       ir = 0;
+#endif
+                       fl = 0;
+                       fr = 0;
+                       for (j = 0; j < 8; j++) {
+                               inbit = 8 * k + j;
+                               if (i & bits8[j]) {
+#if USE_ip_mask
+                                       obit = init_perm[inbit];
+                                       if (obit < 32)
+                                               il |= bits32[obit];
+                                       else
+                                               ir |= bits32[obit - 32];
+#endif
+                                       obit = final_perm[inbit];
+                                       if (obit < 32)
+                                               fl |= bits32[obit];
+                                       else
+                                               fr |= bits32[obit - 32];
+                               }
+                       }
+#if USE_ip_mask
+                       ip_maskl[k][i] = il;
+                       ip_maskr[k][i] = ir;
+#endif
+                       fp_maskl[k][i] = fl;
+                       fp_maskr[k][i] = fr;
+               }
+               for (i = 0; i < 128; i++) {
+                       il = 0;
+                       ir = 0;
+                       for (j = 0; j < 7; j++) {
+                               inbit = 8 * k + j;
+                               if (i & bits8[j + 1]) {
+                                       obit = inv_key_perm[inbit];
+                                       if (obit == 255)
+                                               continue;
+                                       if (obit < 28)
+                                               il |= bits28[obit];
+                                       else
+                                               ir |= bits28[obit - 28];
+                               }
+                       }
+                       key_perm_maskl[k][i] = il;
+                       key_perm_maskr[k][i] = ir;
+                       il = 0;
+                       ir = 0;
+                       for (j = 0; j < 7; j++) {
+                               inbit = 7 * k + j;
+                               if (i & bits8[j + 1]) {
+                                       obit = inv_comp_perm[inbit];
+                                       if (obit == 255)
+                                               continue;
+                                       if (obit < 24)
+                                               il |= bits24[obit];
+                                       else
+                                               ir |= bits24[obit - 24];
+                               }
+                       }
+                       comp_maskl[k][i] = il;
+                       comp_maskr[k][i] = ir;
+               }
+       }
+
+       /*
+        * Invert the P-box permutation, and convert into OR-masks for
+        * handling the output of the S-box arrays setup above.
+        */
+       for (i = 0; i < 32; i++)
+               un_pbox[pbox[i] - 1] = (uint8_t)i;
+
+       for (b = 0; b < 4; b++) {
+               for (i = 0; i < 256; i++) {
+                       p = 0;
+                       for (j = 0; j < 8; j++) {
+                               if (i & bits8[j])
+                                       p |= bits32[un_pbox[8 * b + j]];
+                       }
+                       psbox[b][i] = p;
+               }
+       }
+
+       return ctx;
+}
+
+
+static void
+setup_salt(struct des_ctx *ctx, uint32_t salt)
+{
+       uint32_t obit, saltbit;
+       int i;
+
+#if USE_REPETITIVE_SPEEDUP
+       if (salt == old_salt)
+               return;
+       old_salt = salt;
+#endif
+
+       saltbits = 0;
+       saltbit = 1;
+       obit = 0x800000;
+       for (i = 0; i < 24; i++) {
+               if (salt & saltbit)
+                       saltbits |= obit;
+               saltbit <<= 1;
+               obit >>= 1;
+       }
+}
+
+static void
+des_setkey(struct des_ctx *ctx, const char *key)
+{
+       uint32_t k0, k1, rawkey0, rawkey1;
+       int shifts, round;
+
+       rawkey0 = ntohl(*(const uint32_t *) key);
+       rawkey1 = ntohl(*(const uint32_t *) (key + 4));
+
+#if USE_REPETITIVE_SPEEDUP
+       if ((rawkey0 | rawkey1)
+        && rawkey0 == old_rawkey0
+        && rawkey1 == old_rawkey1
+       ) {
+               /*
+                * Already setup for this key.
+                * This optimisation fails on a zero key (which is weak and
+                * has bad parity anyway) in order to simplify the starting
+                * conditions.
+                */
+               return;
+       }
+       old_rawkey0 = rawkey0;
+       old_rawkey1 = rawkey1;
+#endif
+
+       /*
+        * Do key permutation and split into two 28-bit subkeys.
+        */
+       k0 = key_perm_maskl[0][rawkey0 >> 25]
+          | key_perm_maskl[1][(rawkey0 >> 17) & 0x7f]
+          | key_perm_maskl[2][(rawkey0 >> 9) & 0x7f]
+          | key_perm_maskl[3][(rawkey0 >> 1) & 0x7f]
+          | key_perm_maskl[4][rawkey1 >> 25]
+          | key_perm_maskl[5][(rawkey1 >> 17) & 0x7f]
+          | key_perm_maskl[6][(rawkey1 >> 9) & 0x7f]
+          | key_perm_maskl[7][(rawkey1 >> 1) & 0x7f];
+       k1 = key_perm_maskr[0][rawkey0 >> 25]
+          | key_perm_maskr[1][(rawkey0 >> 17) & 0x7f]
+          | key_perm_maskr[2][(rawkey0 >> 9) & 0x7f]
+          | key_perm_maskr[3][(rawkey0 >> 1) & 0x7f]
+          | key_perm_maskr[4][rawkey1 >> 25]
+          | key_perm_maskr[5][(rawkey1 >> 17) & 0x7f]
+          | key_perm_maskr[6][(rawkey1 >> 9) & 0x7f]
+          | key_perm_maskr[7][(rawkey1 >> 1) & 0x7f];
+       /*
+        * Rotate subkeys and do compression permutation.
+        */
+       shifts = 0;
+       for (round = 0; round < 16; round++) {
+               uint32_t t0, t1;
+
+               shifts += key_shifts[round];
+
+               t0 = (k0 << shifts) | (k0 >> (28 - shifts));
+               t1 = (k1 << shifts) | (k1 >> (28 - shifts));
+
+#if USE_de_keys
+               de_keysl[15 - round] =
+#endif
+               en_keysl[round] = comp_maskl[0][(t0 >> 21) & 0x7f]
+                               | comp_maskl[1][(t0 >> 14) & 0x7f]
+                               | comp_maskl[2][(t0 >> 7) & 0x7f]
+                               | comp_maskl[3][t0 & 0x7f]
+                               | comp_maskl[4][(t1 >> 21) & 0x7f]
+                               | comp_maskl[5][(t1 >> 14) & 0x7f]
+                               | comp_maskl[6][(t1 >> 7) & 0x7f]
+                               | comp_maskl[7][t1 & 0x7f];
+
+#if USE_de_keys
+               de_keysr[15 - round] =
+#endif
+               en_keysr[round] = comp_maskr[0][(t0 >> 21) & 0x7f]
+                               | comp_maskr[1][(t0 >> 14) & 0x7f]
+                               | comp_maskr[2][(t0 >> 7) & 0x7f]
+                               | comp_maskr[3][t0 & 0x7f]
+                               | comp_maskr[4][(t1 >> 21) & 0x7f]
+                               | comp_maskr[5][(t1 >> 14) & 0x7f]
+                               | comp_maskr[6][(t1 >> 7) & 0x7f]
+                               | comp_maskr[7][t1 & 0x7f];
+       }
+}
+
+
+static void
+do_des(struct des_ctx *ctx, /*uint32_t l_in, uint32_t r_in,*/ uint32_t *l_out, uint32_t *r_out, int count)
+{
+       const struct const_des_ctx *cctx = const_ctx;
+       /*
+        * l_in, r_in, l_out, and r_out are in pseudo-"big-endian" format.
+        */
+       uint32_t l, r, *kl, *kr;
+       uint32_t f = f; /* silence gcc */
+       uint32_t r48l, r48r;
+       int round;
+
+       /* Do initial permutation (IP). */
+#if USE_ip_mask
+       uint32_t l_in = 0;
+       uint32_t r_in = 0;
+       l = ip_maskl[0][l_in >> 24]
+         | ip_maskl[1][(l_in >> 16) & 0xff]
+         | ip_maskl[2][(l_in >> 8) & 0xff]
+         | ip_maskl[3][l_in & 0xff]
+         | ip_maskl[4][r_in >> 24]
+         | ip_maskl[5][(r_in >> 16) & 0xff]
+         | ip_maskl[6][(r_in >> 8) & 0xff]
+         | ip_maskl[7][r_in & 0xff];
+       r = ip_maskr[0][l_in >> 24]
+         | ip_maskr[1][(l_in >> 16) & 0xff]
+         | ip_maskr[2][(l_in >> 8) & 0xff]
+         | ip_maskr[3][l_in & 0xff]
+         | ip_maskr[4][r_in >> 24]
+         | ip_maskr[5][(r_in >> 16) & 0xff]
+         | ip_maskr[6][(r_in >> 8) & 0xff]
+         | ip_maskr[7][r_in & 0xff];
+#elif 0 /* -65 bytes (using the fact that l_in == r_in == 0) */
+       l = r = 0;
+       for (round = 0; round < 8; round++) {
+               l |= ip_maskl[round][0];
+               r |= ip_maskr[round][0];
+       }
+       bb_error_msg("l:%x r:%x", l, r); /* reports 0, 0 always! */
+#else /* using the fact that ip_maskX[] is constant (written to by des_init) */
+       l = r = 0;
+#endif
+
+       do {
+               /* Do each round. */
+               kl = en_keysl;
+               kr = en_keysr;
+               round = 16;
+               do {
+                       /* Expand R to 48 bits (simulate the E-box). */
+                       r48l    = ((r & 0x00000001) << 23)
+                               | ((r & 0xf8000000) >> 9)
+                               | ((r & 0x1f800000) >> 11)
+                               | ((r & 0x01f80000) >> 13)
+                               | ((r & 0x001f8000) >> 15);
+
+                       r48r    = ((r & 0x0001f800) << 7)
+                               | ((r & 0x00001f80) << 5)
+                               | ((r & 0x000001f8) << 3)
+                               | ((r & 0x0000001f) << 1)
+                               | ((r & 0x80000000) >> 31);
+                       /*
+                        * Do salting for crypt() and friends, and
+                        * XOR with the permuted key.
+                        */
+                       f = (r48l ^ r48r) & saltbits;
+                       r48l ^= f ^ *kl++;
+                       r48r ^= f ^ *kr++;
+                       /*
+                        * Do sbox lookups (which shrink it back to 32 bits)
+                        * and do the pbox permutation at the same time.
+                        */
+                       f = psbox[0][m_sbox[0][r48l >> 12]]
+                         | psbox[1][m_sbox[1][r48l & 0xfff]]
+                         | psbox[2][m_sbox[2][r48r >> 12]]
+                         | psbox[3][m_sbox[3][r48r & 0xfff]];
+                       /* Now that we've permuted things, complete f(). */
+                       f ^= l;
+                       l = r;
+                       r = f;
+               } while (--round);
+               r = l;
+               l = f;
+       } while (--count);
+
+       /* Do final permutation (inverse of IP). */
+       *l_out  = fp_maskl[0][l >> 24]
+               | fp_maskl[1][(l >> 16) & 0xff]
+               | fp_maskl[2][(l >> 8) & 0xff]
+               | fp_maskl[3][l & 0xff]
+               | fp_maskl[4][r >> 24]
+               | fp_maskl[5][(r >> 16) & 0xff]
+               | fp_maskl[6][(r >> 8) & 0xff]
+               | fp_maskl[7][r & 0xff];
+       *r_out  = fp_maskr[0][l >> 24]
+               | fp_maskr[1][(l >> 16) & 0xff]
+               | fp_maskr[2][(l >> 8) & 0xff]
+               | fp_maskr[3][l & 0xff]
+               | fp_maskr[4][r >> 24]
+               | fp_maskr[5][(r >> 16) & 0xff]
+               | fp_maskr[6][(r >> 8) & 0xff]
+               | fp_maskr[7][r & 0xff];
+}
+
+#define DES_OUT_BUFSIZE 21
+
+static void
+to64_msb_first(char *s, unsigned v)
+{
+#if 0
+       *s++ = ascii64[(v >> 18) & 0x3f]; /* bits 23..18 */
+       *s++ = ascii64[(v >> 12) & 0x3f]; /* bits 17..12 */
+       *s++ = ascii64[(v >> 6) & 0x3f]; /* bits 11..6 */
+       *s   = ascii64[v & 0x3f]; /* bits 5..0 */
+#endif
+       *s++ = i64c(v >> 18); /* bits 23..18 */
+       *s++ = i64c(v >> 12); /* bits 17..12 */
+       *s++ = i64c(v >> 6); /* bits 11..6 */
+       *s   = i64c(v); /* bits 5..0 */
+}
+
+static char *
+NOINLINE
+des_crypt(struct des_ctx *ctx, char output[DES_OUT_BUFSIZE],
+               const unsigned char *key, const unsigned char *setting)
+{
+       uint32_t salt, r0, r1, keybuf[2];
+       uint8_t *q;
+
+       /*
+        * Copy the key, shifting each character up by one bit
+        * and padding with zeros.
+        */
+       q = (uint8_t *)keybuf;
+       while (q - (uint8_t *)keybuf != 8) {
+               *q = *key << 1;
+               if (*q)
+                       key++;
+               q++;
+       }
+       des_setkey(ctx, (char *)keybuf);
+
+       /*
+        * setting - 2 bytes of salt
+        * key - up to 8 characters
+        */
+       salt = (ascii_to_bin(setting[1]) << 6)
+            |  ascii_to_bin(setting[0]);
+
+       output[0] = setting[0];
+       /*
+        * If the encrypted password that the salt was extracted from
+        * is only 1 character long, the salt will be corrupted.  We
+        * need to ensure that the output string doesn't have an extra
+        * NUL in it!
+        */
+       output[1] = setting[1] ? setting[1] : output[0];
+
+       setup_salt(ctx, salt);
+       /* Do it. */
+       do_des(ctx, /*0, 0,*/ &r0, &r1, 25 /* count */);
+
+       /* Now encode the result. */
+#if 0
+{
+       uint32_t l = (r0 >> 8);
+       q = (uint8_t *)output + 2;
+       *q++ = ascii64[(l >> 18) & 0x3f]; /* bits 31..26 of r0 */
+       *q++ = ascii64[(l >> 12) & 0x3f]; /* bits 25..20 of r0 */
+       *q++ = ascii64[(l >> 6) & 0x3f]; /* bits 19..14 of r0 */
+       *q++ = ascii64[l & 0x3f]; /* bits 13..8 of r0 */
+       l = ((r0 << 16) | (r1 >> 16));
+       *q++ = ascii64[(l >> 18) & 0x3f]; /* bits 7..2 of r0 */
+       *q++ = ascii64[(l >> 12) & 0x3f]; /* bits 1..2 of r0 and 31..28 of r1 */
+       *q++ = ascii64[(l >> 6) & 0x3f]; /* bits 27..22 of r1 */
+       *q++ = ascii64[l & 0x3f]; /* bits 21..16 of r1 */
+       l = r1 << 2;
+       *q++ = ascii64[(l >> 12) & 0x3f]; /* bits 15..10 of r1 */
+       *q++ = ascii64[(l >> 6) & 0x3f]; /* bits 9..4 of r1 */
+       *q++ = ascii64[l & 0x3f]; /* bits 3..0 of r1 + 00 */
+       *q = 0;
+}
+#else
+       /* Each call takes low-order 24 bits and stores 4 chars */
+       /* bits 31..8 of r0 */
+       to64_msb_first(output + 2, (r0 >> 8));
+       /* bits 7..0 of r0 and 31..16 of r1 */
+       to64_msb_first(output + 6, (r0 << 16) | (r1 >> 16));
+       /* bits 15..0 of r1 and two zero bits (plus extra zero byte) */
+       to64_msb_first(output + 10, (r1 << 8));
+       /* extra zero byte is encoded as '.', fixing it */
+       output[13] = '\0';
+#endif
+
+       return output;
+}
+
+#undef USE_PRECOMPUTED_u_sbox
+#undef USE_REPETITIVE_SPEEDUP
+#undef USE_ip_mask
+#undef USE_de_keys
+
+#undef C
+#undef init_perm
+#undef final_perm
+#undef m_sbox
+#undef D
+#undef const_ctx
+#undef saltbits
+#undef old_salt
+#undef old_rawkey0
+#undef old_rawkey1
+#undef un_pbox
+#undef inv_comp_perm
+#undef inv_key_perm
+#undef en_keysl
+#undef en_keysr
+#undef de_keysl
+#undef de_keysr
+#undef ip_maskl
+#undef ip_maskr
+#undef fp_maskl
+#undef fp_maskr
+#undef key_perm_maskl
+#undef key_perm_maskr
+#undef comp_maskl
+#undef comp_maskr
+#undef psbox
diff --git a/libbb/pw_encrypt_md5.c b/libbb/pw_encrypt_md5.c
new file mode 100644 (file)
index 0000000..58964b5
--- /dev/null
@@ -0,0 +1,161 @@
+/*
+ * MD5C.C - RSA Data Security, Inc., MD5 message-digest algorithm
+ *
+ * Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All
+ * rights reserved.
+ *
+ * License to copy and use this software is granted provided that it
+ * is identified as the "RSA Data Security, Inc. MD5 Message-Digest
+ * Algorithm" in all material mentioning or referencing this software
+ * or this function.
+ *
+ * License is also granted to make and use derivative works provided
+ * that such works are identified as "derived from the RSA Data
+ * Security, Inc. MD5 Message-Digest Algorithm" in all material
+ * mentioning or referencing the derived work.
+ *
+ * RSA Data Security, Inc. makes no representations concerning either
+ * the merchantability of this software or the suitability of this
+ * software for any particular purpose. It is provided "as is"
+ * without express or implied warranty of any kind.
+ *
+ * These notices must be retained in any copies of any part of this
+ * documentation and/or software.
+ *
+ * $FreeBSD: src/lib/libmd/md5c.c,v 1.9.2.1 1999/08/29 14:57:12 peter Exp $
+ *
+ * This code is the same as the code published by RSA Inc.  It has been
+ * edited for clarity and style only.
+ *
+ * ----------------------------------------------------------------------------
+ * The md5_crypt() function was taken from freeBSD's libcrypt and contains
+ * this license:
+ *    "THE BEER-WARE LICENSE" (Revision 42):
+ *     <phk@login.dknet.dk> wrote this file.  As long as you retain this notice you
+ *     can do whatever you want with this stuff. If we meet some day, and you think
+ *     this stuff is worth it, you can buy me a beer in return.   Poul-Henning Kamp
+ *
+ * $FreeBSD: src/lib/libcrypt/crypt.c,v 1.7.2.1 1999/08/29 14:56:33 peter Exp $
+ *
+ * ----------------------------------------------------------------------------
+ * On April 19th, 2001 md5_crypt() was modified to make it reentrant
+ * by Erik Andersen <andersen@uclibc.org>
+ *
+ *
+ * June 28, 2001             Manuel Novoa III
+ *
+ * "Un-inlined" code using loops and static const tables in order to
+ * reduce generated code size (on i386 from approx 4k to approx 2.5k).
+ *
+ * June 29, 2001             Manuel Novoa III
+ *
+ * Completely removed static PADDING array.
+ *
+ * Reintroduced the loop unrolling in MD5_Transform and added the
+ * MD5_SIZE_OVER_SPEED option for configurability.  Define below as:
+ *       0    fully unrolled loops
+ *       1    partially unrolled (4 ops per loop)
+ *       2    no unrolling -- introduces the need to swap 4 variables (slow)
+ *       3    no unrolling and all 4 loops merged into one with switch
+ *               in each loop (glacial)
+ * On i386, sizes are roughly (-Os -fno-builtin):
+ *     0: 3k     1: 2.5k     2: 2.2k     3: 2k
+ *
+ * Since SuSv3 does not require crypt_r, modified again August 7, 2002
+ * by Erik Andersen to remove reentrance stuff...
+ */
+
+/*
+ * UNIX password
+ *
+ * Use MD5 for what it is best at...
+ */
+#define MD5_OUT_BUFSIZE 36
+static char *
+NOINLINE
+md5_crypt(char result[MD5_OUT_BUFSIZE], const unsigned char *pw, const unsigned char *salt)
+{
+       char *p;
+       unsigned char final[17]; /* final[16] exists only to aid in looping */
+       int sl, pl, i, pw_len;
+       md5_ctx_t ctx, ctx1;
+
+       /* NB: in busybox, "$1$" in salt is always present */
+
+       /* Refine the Salt first */
+
+       /* Get the length of the salt including "$1$" */
+       sl = 3;
+       while (salt[sl] && salt[sl] != '$' && sl < (3 + 8))
+               sl++;
+
+       /* Hash. the password first, since that is what is most unknown */
+       md5_begin(&ctx);
+       pw_len = strlen((char*)pw);
+       md5_hash(pw, pw_len, &ctx);
+
+       /* Then the salt including "$1$" */
+       md5_hash(salt, sl, &ctx);
+
+       /* Copy salt to result; skip "$1$" */
+       memcpy(result, salt, sl);
+       result[sl] = '$';
+       salt += 3;
+       sl -= 3;
+
+       /* Then just as many characters of the MD5(pw, salt, pw) */
+       md5_begin(&ctx1);
+       md5_hash(pw, pw_len, &ctx1);
+       md5_hash(salt, sl, &ctx1);
+       md5_hash(pw, pw_len, &ctx1);
+       md5_end(final, &ctx1);
+       for (pl = pw_len; pl > 0; pl -= 16)
+               md5_hash(final, pl > 16 ? 16 : pl, &ctx);
+
+       /* Then something really weird... */
+       memset(final, 0, sizeof(final));
+       for (i = pw_len; i; i >>= 1) {
+               md5_hash(((i & 1) ? final : (const unsigned char *) pw), 1, &ctx);
+       }
+       md5_end(final, &ctx);
+
+       /* And now, just to make sure things don't run too fast.
+        * On a 60 Mhz Pentium this takes 34 msec, so you would
+        * need 30 seconds to build a 1000 entry dictionary...
+        */
+       for (i = 0; i < 1000; i++) {
+               md5_begin(&ctx1);
+               if (i & 1)
+                       md5_hash(pw, pw_len, &ctx1);
+               else
+                       md5_hash(final, 16, &ctx1);
+
+               if (i % 3)
+                       md5_hash(salt, sl, &ctx1);
+
+               if (i % 7)
+                       md5_hash(pw, pw_len, &ctx1);
+
+               if (i & 1)
+                       md5_hash(final, 16, &ctx1);
+               else
+                       md5_hash(pw, pw_len, &ctx1);
+               md5_end(final, &ctx1);
+       }
+
+       p = result + sl + 4; /* 12 bytes max (sl is up to 8 bytes) */
+
+       /* Add 5*4+2 = 22 bytes of hash, + NUL byte. */
+       final[16] = final[5];
+       for (i = 0; i < 5; i++) {
+               unsigned l = (final[i] << 16) | (final[i+6] << 8) | final[i+12];
+               p = to64(p, l, 4);
+       }
+       p = to64(p, final[11], 2);
+       *p = '\0';
+
+       /* Don't leave anything around in vm they could use. */
+       memset(final, 0, sizeof(final));
+
+       return result;
+}
diff --git a/libbb/pw_encrypt_sha.c b/libbb/pw_encrypt_sha.c
new file mode 100644 (file)
index 0000000..070e0d4
--- /dev/null
@@ -0,0 +1,286 @@
+/* SHA256 and SHA512-based Unix crypt implementation.
+ * Released into the Public Domain by Ulrich Drepper <drepper@redhat.com>.
+ */
+
+/* Prefix for optional rounds specification.  */
+static const char str_rounds[] = "rounds=%u$";
+
+/* Maximum salt string length.  */
+#define SALT_LEN_MAX 16
+/* Default number of rounds if not explicitly specified.  */
+#define ROUNDS_DEFAULT 5000
+/* Minimum number of rounds.  */
+#define ROUNDS_MIN 1000
+/* Maximum number of rounds.  */
+#define ROUNDS_MAX 999999999
+
+static char *
+NOINLINE
+sha_crypt(/*const*/ char *key_data, /*const*/ char *salt_data)
+{
+       void (*sha_begin)(void *ctx) FAST_FUNC;
+       void (*sha_hash)(const void *buffer, size_t len, void *ctx) FAST_FUNC;
+       void (*sha_end)(void *resbuf, void *ctx) FAST_FUNC;
+       int _32or64;
+
+       char *result, *resptr;
+
+       /* btw, sha256 needs [32] and uint32_t only */
+       struct {
+               unsigned char alt_result[64];
+               unsigned char temp_result[64];
+               union {
+                       sha256_ctx_t x;
+                       sha512_ctx_t y;
+               } ctx;
+               union {
+                       sha256_ctx_t x;
+                       sha512_ctx_t y;
+               } alt_ctx;
+       } L __attribute__((__aligned__(__alignof__(uint64_t))));
+#define alt_result  (L.alt_result )
+#define temp_result (L.temp_result)
+#define ctx         (L.ctx        )
+#define alt_ctx     (L.alt_ctx    )
+       unsigned salt_len;
+       unsigned key_len;
+       unsigned cnt;
+       unsigned rounds;
+       char *cp;
+       char is_sha512;
+
+       /* Analyze salt, construct already known part of result */
+       cnt = strlen(salt_data) + 1 + 43 + 1;
+       is_sha512 = salt_data[1];
+       if (is_sha512 == '6')
+               cnt += 43;
+       result = resptr = xzalloc(cnt); /* will provide NUL terminator */
+       *resptr++ = '$';
+       *resptr++ = is_sha512;
+       *resptr++ = '$';
+       rounds = ROUNDS_DEFAULT;
+       salt_data += 3;
+       if (strncmp(salt_data, str_rounds, 7) == 0) {
+               /* 7 == strlen("rounds=") */
+               char *endp;
+               cnt = bb_strtou(salt_data + 7, &endp, 10);
+               if (*endp == '$') {
+                       salt_data = endp + 1;
+                       rounds = cnt;
+                       if (rounds < ROUNDS_MIN)
+                               rounds = ROUNDS_MIN;
+                       if (rounds > ROUNDS_MAX)
+                               rounds = ROUNDS_MAX;
+                       /* add "rounds=NNNNN$" to result */
+                       resptr += sprintf(resptr, str_rounds, rounds);
+               }
+       }
+       salt_len = strchrnul(salt_data, '$') - salt_data;
+       if (salt_len > SALT_LEN_MAX)
+               salt_len = SALT_LEN_MAX;
+       /* xstrdup assures suitable alignment; also we will use it
+          as a scratch space later. */
+       salt_data = xstrndup(salt_data, salt_len);
+       /* add "salt$" to result */
+       strcpy(resptr, salt_data);
+       resptr += salt_len;
+       *resptr++ = '$';
+       /* key data doesn't need much processing */
+       key_len = strlen(key_data);
+       key_data = xstrdup(key_data);
+
+       /* Which flavor of SHAnnn ops to use? */
+       sha_begin = (void*)sha256_begin;
+       sha_hash = (void*)sha256_hash;
+       sha_end = (void*)sha256_end;
+       _32or64 = 32;
+       if (is_sha512 == '6') {
+               sha_begin = (void*)sha512_begin;
+               sha_hash = (void*)sha512_hash;
+               sha_end = (void*)sha512_end;
+               _32or64 = 64;
+       }
+
+       /* Add KEY, SALT.  */
+       sha_begin(&ctx);
+       sha_hash(key_data, key_len, &ctx);
+       sha_hash(salt_data, salt_len, &ctx);
+
+       /* Compute alternate SHA sum with input KEY, SALT, and KEY.
+          The final result will be added to the first context.  */
+       sha_begin(&alt_ctx);
+       sha_hash(key_data, key_len, &alt_ctx);
+       sha_hash(salt_data, salt_len, &alt_ctx);
+       sha_hash(key_data, key_len, &alt_ctx);
+       sha_end(alt_result, &alt_ctx);
+
+       /* Add result of this to the other context.  */
+       /* Add for any character in the key one byte of the alternate sum.  */
+       for (cnt = key_len; cnt > _32or64; cnt -= _32or64)
+               sha_hash(alt_result, _32or64, &ctx);
+       sha_hash(alt_result, cnt, &ctx);
+
+       /* Take the binary representation of the length of the key and for every
+          1 add the alternate sum, for every 0 the key.  */
+       for (cnt = key_len; cnt != 0; cnt >>= 1)
+               if ((cnt & 1) != 0)
+                       sha_hash(alt_result, _32or64, &ctx);
+               else
+                       sha_hash(key_data, key_len, &ctx);
+
+       /* Create intermediate result.  */
+       sha_end(alt_result, &ctx);
+
+       /* Start computation of P byte sequence.  */
+       /* For every character in the password add the entire password.  */
+       sha_begin(&alt_ctx);
+       for (cnt = 0; cnt < key_len; ++cnt)
+               sha_hash(key_data, key_len, &alt_ctx);
+       sha_end(temp_result, &alt_ctx);
+
+       /* NB: past this point, raw key_data is not used anymore */
+
+       /* Create byte sequence P.  */
+#define p_bytes key_data /* reuse the buffer as it is of the key_len size */
+       cp = p_bytes; /* was: ... = alloca(key_len); */
+       for (cnt = key_len; cnt >= _32or64; cnt -= _32or64) {
+               cp = memcpy(cp, temp_result, _32or64);
+               cp += _32or64;
+       }
+       memcpy(cp, temp_result, cnt);
+
+       /* Start computation of S byte sequence.  */
+       /* For every character in the password add the entire password.  */
+       sha_begin(&alt_ctx);
+       for (cnt = 0; cnt < 16 + alt_result[0]; ++cnt)
+               sha_hash(salt_data, salt_len, &alt_ctx);
+       sha_end(temp_result, &alt_ctx);
+
+       /* NB: past this point, raw salt_data is not used anymore */
+
+       /* Create byte sequence S.  */
+#define s_bytes salt_data /* reuse the buffer as it is of the salt_len size */
+       cp = s_bytes; /* was: ... = alloca(salt_len); */
+       for (cnt = salt_len; cnt >= _32or64; cnt -= _32or64) {
+               cp = memcpy(cp, temp_result, _32or64);
+               cp += _32or64;
+       }
+       memcpy(cp, temp_result, cnt);
+
+       /* Repeatedly run the collected hash value through SHA to burn
+          CPU cycles.  */
+       for (cnt = 0; cnt < rounds; ++cnt) {
+               sha_begin(&ctx);
+
+               /* Add key or last result.  */
+               if ((cnt & 1) != 0)
+                       sha_hash(p_bytes, key_len, &ctx);
+               else
+                       sha_hash(alt_result, _32or64, &ctx);
+               /* Add salt for numbers not divisible by 3.  */
+               if (cnt % 3 != 0)
+                       sha_hash(s_bytes, salt_len, &ctx);
+               /* Add key for numbers not divisible by 7.  */
+               if (cnt % 7 != 0)
+                       sha_hash(p_bytes, key_len, &ctx);
+               /* Add key or last result.  */
+               if ((cnt & 1) != 0)
+                       sha_hash(alt_result, _32or64, &ctx);
+               else
+                       sha_hash(p_bytes, key_len, &ctx);
+
+               sha_end(alt_result, &ctx);
+       }
+
+       /* Append encrypted password to result buffer */
+//TODO: replace with something like
+//     bb_uuencode(cp, src, length, bb_uuenc_tbl_XXXbase64);
+#define b64_from_24bit(B2, B1, B0, N) \
+do {                                                   \
+       unsigned w = ((B2) << 16) | ((B1) << 8) | (B0); \
+       resptr = to64(resptr, w, N);                    \
+} while (0)
+       if (is_sha512 == '5') {
+               unsigned i = 0;
+               while (1) {
+                       unsigned j = i + 10;
+                       unsigned k = i + 20;
+                       if (j >= 30) j -= 30;
+                       if (k >= 30) k -= 30;
+                       b64_from_24bit(alt_result[i], alt_result[j], alt_result[k], 4);
+                       if (k == 29)
+                               break;
+                       i = k + 1;
+               }
+               b64_from_24bit(0, alt_result[31], alt_result[30], 3);
+               /* was:
+               b64_from_24bit(alt_result[0], alt_result[10], alt_result[20], 4);
+               b64_from_24bit(alt_result[21], alt_result[1], alt_result[11], 4);
+               b64_from_24bit(alt_result[12], alt_result[22], alt_result[2], 4);
+               b64_from_24bit(alt_result[3], alt_result[13], alt_result[23], 4);
+               b64_from_24bit(alt_result[24], alt_result[4], alt_result[14], 4);
+               b64_from_24bit(alt_result[15], alt_result[25], alt_result[5], 4);
+               b64_from_24bit(alt_result[6], alt_result[16], alt_result[26], 4);
+               b64_from_24bit(alt_result[27], alt_result[7], alt_result[17], 4);
+               b64_from_24bit(alt_result[18], alt_result[28], alt_result[8], 4);
+               b64_from_24bit(alt_result[9], alt_result[19], alt_result[29], 4);
+               b64_from_24bit(0, alt_result[31], alt_result[30], 3);
+               */
+       } else {
+               unsigned i = 0;
+               while (1) {
+                       unsigned j = i + 21;
+                       unsigned k = i + 42;
+                       if (j >= 63) j -= 63;
+                       if (k >= 63) k -= 63;
+                       b64_from_24bit(alt_result[i], alt_result[j], alt_result[k], 4);
+                       if (j == 20)
+                               break;
+                       i = j + 1;
+               }
+               b64_from_24bit(0, 0, alt_result[63], 2);
+               /* was:
+               b64_from_24bit(alt_result[0], alt_result[21], alt_result[42], 4);
+               b64_from_24bit(alt_result[22], alt_result[43], alt_result[1], 4);
+               b64_from_24bit(alt_result[44], alt_result[2], alt_result[23], 4);
+               b64_from_24bit(alt_result[3], alt_result[24], alt_result[45], 4);
+               b64_from_24bit(alt_result[25], alt_result[46], alt_result[4], 4);
+               b64_from_24bit(alt_result[47], alt_result[5], alt_result[26], 4);
+               b64_from_24bit(alt_result[6], alt_result[27], alt_result[48], 4);
+               b64_from_24bit(alt_result[28], alt_result[49], alt_result[7], 4);
+               b64_from_24bit(alt_result[50], alt_result[8], alt_result[29], 4);
+               b64_from_24bit(alt_result[9], alt_result[30], alt_result[51], 4);
+               b64_from_24bit(alt_result[31], alt_result[52], alt_result[10], 4);
+               b64_from_24bit(alt_result[53], alt_result[11], alt_result[32], 4);
+               b64_from_24bit(alt_result[12], alt_result[33], alt_result[54], 4);
+               b64_from_24bit(alt_result[34], alt_result[55], alt_result[13], 4);
+               b64_from_24bit(alt_result[56], alt_result[14], alt_result[35], 4);
+               b64_from_24bit(alt_result[15], alt_result[36], alt_result[57], 4);
+               b64_from_24bit(alt_result[37], alt_result[58], alt_result[16], 4);
+               b64_from_24bit(alt_result[59], alt_result[17], alt_result[38], 4);
+               b64_from_24bit(alt_result[18], alt_result[39], alt_result[60], 4);
+               b64_from_24bit(alt_result[40], alt_result[61], alt_result[19], 4);
+               b64_from_24bit(alt_result[62], alt_result[20], alt_result[41], 4);
+               b64_from_24bit(0, 0, alt_result[63], 2);
+               */
+       }
+       /* *resptr = '\0'; - xzalloc did it */
+#undef b64_from_24bit
+
+       /* Clear the buffer for the intermediate result so that people
+          attaching to processes or reading core dumps cannot get any
+          information.  */
+       memset(&L, 0, sizeof(L)); /* [alt]_ctx and XXX_result buffers */
+       memset(key_data, 0, key_len); /* also p_bytes */
+       memset(salt_data, 0, salt_len); /* also s_bytes */
+       free(key_data);
+       free(salt_data);
+#undef p_bytes
+#undef s_bytes
+
+       return result;
+#undef alt_result
+#undef temp_result
+#undef ctx
+#undef alt_ctx
+}
diff --git a/libbb/read.c b/libbb/read.c
new file mode 100644 (file)
index 0000000..4654f73
--- /dev/null
@@ -0,0 +1,394 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Utility routines.
+ *
+ * Copyright (C) 1999-2004 by Erik Andersen <andersen@codepoet.org>
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.h"
+
+#define ZIPPED (ENABLE_FEATURE_SEAMLESS_LZMA \
+       || ENABLE_FEATURE_SEAMLESS_BZ2 \
+       || ENABLE_FEATURE_SEAMLESS_GZ \
+       /* || ENABLE_FEATURE_SEAMLESS_Z */ \
+)
+
+#if ZIPPED
+#include "unarchive.h"
+#endif
+
+ssize_t FAST_FUNC 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;
+}
+
+/* Suppose that you are a shell. You start child processes.
+ * They work and eventually exit. You want to get user input.
+ * You read stdin. But what happens if last child switched
+ * its stdin into O_NONBLOCK mode?
+ *
+ * *** SURPRISE! It will affect the parent too! ***
+ * *** BIG SURPRISE! It stays even after child exits! ***
+ *
+ * This is a design bug in UNIX API.
+ *      fcntl(0, F_SETFL, fcntl(0, F_GETFL, 0) | O_NONBLOCK);
+ * will set nonblocking mode not only on _your_ stdin, but
+ * also on stdin of your parent, etc.
+ *
+ * In general,
+ *      fd2 = dup(fd1);
+ *      fcntl(fd2, F_SETFL, fcntl(fd2, F_GETFL, 0) | O_NONBLOCK);
+ * sets both fd1 and fd2 to O_NONBLOCK. This includes cases
+ * where duping is done implicitly by fork() etc.
+ *
+ * We need
+ *      fcntl(fd2, F_SETFD, fcntl(fd2, F_GETFD, 0) | O_NONBLOCK);
+ * (note SETFD, not SETFL!) but such thing doesn't exist.
+ *
+ * Alternatively, we need nonblocking_read(fd, ...) which doesn't
+ * require O_NONBLOCK dance at all. Actually, it exists:
+ *      n = recv(fd, buf, len, MSG_DONTWAIT);
+ *      "MSG_DONTWAIT:
+ *      Enables non-blocking operation; if the operation
+ *      would block, EAGAIN is returned."
+ * but recv() works only for sockets!
+ *
+ * So far I don't see any good solution, I can only propose
+ * that affected readers should be careful and use this routine,
+ * which detects EAGAIN and uses poll() to wait on the fd.
+ * Thankfully, poll() doesn't care about O_NONBLOCK flag.
+ */
+ssize_t FAST_FUNC nonblock_safe_read(int fd, void *buf, size_t count)
+{
+       struct pollfd pfd[1];
+       ssize_t n;
+
+       while (1) {
+               n = safe_read(fd, buf, count);
+               if (n >= 0 || errno != EAGAIN)
+                       return n;
+               /* fd is in O_NONBLOCK mode. Wait using poll and repeat */
+               pfd[0].fd = fd;
+               pfd[0].events = POLLIN;
+               safe_poll(pfd, 1, -1);
+       }
+}
+
+/*
+ * 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 FAST_FUNC 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) {
+                       if (total) {
+                               /* we already have some! */
+                               /* user can do another read to know the error code */
+                               return total;
+                       }
+                       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 FAST_FUNC xread(int fd, void *buf, size_t count)
+{
+       if (count) {
+               ssize_t size = full_read(fd, buf, count);
+               if ((size_t)size != count)
+                       bb_error_msg_and_die("short read");
+       }
+}
+
+/* Die with an error message if we can't read one character. */
+unsigned char FAST_FUNC xread_char(int fd)
+{
+       char tmp;
+       xread(fd, &tmp, 1);
+       return tmp;
+}
+
+// Reads one line a-la fgets (but doesn't save terminating '\n').
+// Reads byte-by-byte. Useful when it is important to not read ahead.
+// Bytes are appended to pfx (which must be malloced, or NULL).
+char* FAST_FUNC xmalloc_reads(int fd, char *buf, size_t *maxsz_p)
+{
+       char *p;
+       size_t sz = buf ? strlen(buf) : 0;
+       size_t maxsz = maxsz_p ? *maxsz_p : (INT_MAX - 4095);
+
+       goto jump_in;
+       while (sz < maxsz) {
+               if ((size_t)(p - buf) == sz) {
+ jump_in:
+                       buf = xrealloc(buf, sz + 128);
+                       p = buf + sz;
+                       sz += 128;
+               }
+               /* nonblock_safe_read() because we are used by e.g. shells */
+               if (nonblock_safe_read(fd, p, 1) != 1) { /* EOF/error */
+                       if (p == buf) { /* we read nothing */
+                               free(buf);
+                               return NULL;
+                       }
+                       break;
+               }
+               if (*p == '\n')
+                       break;
+               p++;
+       }
+       *p = '\0';
+       if (maxsz_p)
+               *maxsz_p  = p - buf;
+       p++;
+       return xrealloc(buf, p - buf);
+}
+
+ssize_t FAST_FUNC 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 FAST_FUNC 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);
+}
+
+
+// Read (potentially big) files in one go. File size is estimated
+// by stat. Extra '\0' byte is appended.
+void* FAST_FUNC xmalloc_read(int fd, size_t *maxsz_p)
+{
+       char *buf;
+       size_t size, rd_size, total;
+       size_t to_read;
+       struct stat st;
+
+       to_read = maxsz_p ? *maxsz_p : (INT_MAX - 4095); /* max to read */
+
+       /* Estimate file size */
+       st.st_size = 0; /* in case fstat fails, assume 0 */
+       fstat(fd, &st);
+       /* /proc/N/stat files report st_size 0 */
+       /* In order to make such files readable, we add small const */
+       size = (st.st_size | 0x3ff) + 1;
+
+       total = 0;
+       buf = NULL;
+       while (1) {
+               if (to_read < size)
+                       size = to_read;
+               buf = xrealloc(buf, total + size + 1);
+               rd_size = full_read(fd, buf + total, size);
+               if ((ssize_t)rd_size == (ssize_t)(-1)) { /* error */
+                       free(buf);
+                       return NULL;
+               }
+               total += rd_size;
+               if (rd_size < size) /* EOF */
+                       break;
+               if (to_read <= rd_size)
+                       break;
+               to_read -= rd_size;
+               /* grow by 1/8, but in [1k..64k] bounds */
+               size = ((total / 8) | 0x3ff) + 1;
+               if (size > 64*1024)
+                       size = 64*1024;
+       }
+       buf = xrealloc(buf, total + 1);
+       buf[total] = '\0';
+
+       if (maxsz_p)
+               *maxsz_p = total;
+       return buf;
+}
+
+#ifdef USING_LSEEK_TO_GET_SIZE
+/* Alternatively, file size can be obtained by lseek to the end.
+ * The code is slightly bigger. Retained in case fstat approach
+ * will not work for some weird cases (/proc, block devices, etc).
+ * (NB: lseek also can fail to work for some weird files) */
+
+// Read (potentially big) files in one go. File size is estimated by
+// lseek to end.
+void* FAST_FUNC xmalloc_open_read_close(const char *filename, size_t *maxsz_p)
+{
+       char *buf;
+       size_t size;
+       int fd;
+       off_t len;
+
+       fd = open(filename, O_RDONLY);
+       if (fd < 0)
+               return NULL;
+
+       /* /proc/N/stat files report len 0 here */
+       /* In order to make such files readable, we add small const */
+       size = 0x3ff; /* read only 1k on unseekable files */
+       len = lseek(fd, 0, SEEK_END) | 0x3ff; /* + up to 1k */
+       if (len != (off_t)-1) {
+               xlseek(fd, 0, SEEK_SET);
+               size = maxsz_p ? *maxsz_p : (INT_MAX - 4095);
+               if (len < size)
+                       size = len;
+       }
+
+       buf = xmalloc(size + 1);
+       size = read_close(fd, buf, size);
+       if ((ssize_t)size < 0) {
+               free(buf);
+               return NULL;
+       }
+       buf = xrealloc(buf, size + 1);
+       buf[size] = '\0';
+
+       if (maxsz_p)
+               *maxsz_p = size;
+       return buf;
+}
+#endif
+
+// Read (potentially big) files in one go. File size is estimated
+// by stat.
+void* FAST_FUNC xmalloc_open_read_close(const char *filename, size_t *maxsz_p)
+{
+       char *buf;
+       int fd;
+
+       fd = open(filename, O_RDONLY);
+       if (fd < 0)
+               return NULL;
+
+       buf = xmalloc_read(fd, maxsz_p);
+       close(fd);
+       return buf;
+}
+
+void* FAST_FUNC xmalloc_xopen_read_close(const char *filename, size_t *maxsz_p)
+{
+       void *buf = xmalloc_open_read_close(filename, maxsz_p);
+       if (!buf)
+               bb_perror_msg_and_die("can't read '%s'", filename);
+       return buf;
+}
+
+int FAST_FUNC open_zipped(const char *fname)
+{
+#if !ZIPPED
+       return open(fname, O_RDONLY);
+#else
+       unsigned char magic[2];
+       char *sfx;
+       int fd;
+#if BB_MMU
+       USE_DESKTOP(long long) int FAST_FUNC (*xformer)(int src_fd, int dst_fd);
+       enum { xformer_prog = 0 };
+#else
+       enum { xformer = 0 };
+       const char *xformer_prog;
+#endif
+
+       fd = open(fname, O_RDONLY);
+       if (fd < 0)
+               return fd;
+
+       sfx = strrchr(fname, '.');
+       if (sfx) {
+               if (ENABLE_FEATURE_SEAMLESS_LZMA && strcmp(sfx, ".lzma") == 0)
+                       /* .lzma has no header/signature, just trust it */
+                       open_transformer(fd, unpack_lzma_stream, "unlzma");
+               else
+               if ((ENABLE_FEATURE_SEAMLESS_GZ && strcmp(sfx, ".gz") == 0)
+                || (ENABLE_FEATURE_SEAMLESS_BZ2 && strcmp(sfx, ".bz2") == 0)
+               ) {
+                       /* .gz and .bz2 both have 2-byte signature, and their
+                        * unpack_XXX_stream want this header skipped. */
+                       xread(fd, &magic, 2);
+#if ENABLE_FEATURE_SEAMLESS_GZ
+#if BB_MMU
+                       xformer = unpack_gz_stream;
+#else
+                       xformer_prog = "gunzip";
+#endif
+#endif
+                       if (!ENABLE_FEATURE_SEAMLESS_GZ
+                        || magic[0] != 0x1f || magic[1] != 0x8b
+                       ) {
+                               if (!ENABLE_FEATURE_SEAMLESS_BZ2
+                                || magic[0] != 'B' || magic[1] != 'Z'
+                               ) {
+                                       bb_error_msg_and_die("no gzip"
+                                               USE_FEATURE_SEAMLESS_BZ2("/bzip2")
+                                               " magic");
+                               }
+#if BB_MMU
+                               xformer = unpack_bz2_stream;
+#else
+                               xformer_prog = "bunzip2";
+#endif
+                       } else {
+#if !BB_MMU
+                               /* NOMMU version of open_transformer execs
+                                * an external unzipper that wants
+                                * file position at the start of the file */
+                               xlseek(fd, 0, SEEK_SET);
+#endif
+                       }
+                       open_transformer(fd, xformer, xformer_prog);
+               }
+       }
+
+       return fd;
+#endif
+}
+
+void* FAST_FUNC xmalloc_open_zipped_read_close(const char *fname, size_t *maxsz_p)
+{
+       int fd;
+       char *image;
+
+       fd = open_zipped(fname);
+       if (fd < 0)
+               return NULL;
+
+       image = xmalloc_read(fd, maxsz_p);
+       if (!image)
+               bb_perror_msg("read error from '%s'", fname);
+       close(fd);
+
+       return image;
+}
diff --git a/libbb/read_key.c b/libbb/read_key.c
new file mode 100644 (file)
index 0000000..0f36d20
--- /dev/null
@@ -0,0 +1,157 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Utility routines.
+ *
+ * Copyright (C) 2008 Rob Landley <rob@landley.net>
+ * Copyright (C) 2008 Denys Vlasenko <vda.linux@googlemail.com>
+ *
+ * Licensed under GPL version 2, see file LICENSE in this tarball for details.
+ */
+#include "libbb.h"
+
+int FAST_FUNC read_key(int fd, smalluint *nbuffered, char *buffer)
+{
+       struct pollfd pfd;
+       const char *seq;
+       int n;
+       int c;
+
+       /* Known escape sequences for cursor and function keys */
+       static const char esccmds[] ALIGN1 = {
+               'O','A'        |0x80,KEYCODE_UP      ,
+               'O','B'        |0x80,KEYCODE_DOWN    ,
+               'O','C'        |0x80,KEYCODE_RIGHT   ,
+               'O','D'        |0x80,KEYCODE_LEFT    ,
+               'O','H'        |0x80,KEYCODE_HOME    ,
+               'O','F'        |0x80,KEYCODE_END     ,
+#if 0
+               'O','P'        |0x80,KEYCODE_FUN1    ,
+               /* [ESC] ESC O [2] P - [Alt-][Shift-]F1 */
+               /* Ctrl- seems to not affect sequences */
+               'O','Q'        |0x80,KEYCODE_FUN2    ,
+               'O','R'        |0x80,KEYCODE_FUN3    ,
+               'O','S'        |0x80,KEYCODE_FUN4    ,
+#endif
+               '[','A'        |0x80,KEYCODE_UP      ,
+               '[','B'        |0x80,KEYCODE_DOWN    ,
+               '[','C'        |0x80,KEYCODE_RIGHT   ,
+               '[','D'        |0x80,KEYCODE_LEFT    ,
+               '[','H'        |0x80,KEYCODE_HOME    , /* xterm */
+               /* [ESC] ESC [ [2] H - [Alt-][Shift-]Home */
+               '[','F'        |0x80,KEYCODE_END     , /* xterm */
+               '[','1','~'    |0x80,KEYCODE_HOME    , /* vt100? linux vt? or what? */
+               '[','2','~'    |0x80,KEYCODE_INSERT  ,
+               '[','3','~'    |0x80,KEYCODE_DELETE  ,
+               /* [ESC] ESC [ 3 [;2] ~ - [Alt-][Shift-]Delete */
+               '[','4','~'    |0x80,KEYCODE_END     , /* vt100? linux vt? or what? */
+               '[','5','~'    |0x80,KEYCODE_PAGEUP  ,
+               '[','6','~'    |0x80,KEYCODE_PAGEDOWN,
+               '[','7','~'    |0x80,KEYCODE_HOME    , /* vt100? linux vt? or what? */
+               '[','8','~'    |0x80,KEYCODE_END     , /* vt100? linux vt? or what? */
+#if 0
+               '[','1','1','~'|0x80,KEYCODE_FUN1    ,
+               '[','1','2','~'|0x80,KEYCODE_FUN2    ,
+               '[','1','3','~'|0x80,KEYCODE_FUN3    ,
+               '[','1','4','~'|0x80,KEYCODE_FUN4    ,
+               '[','1','5','~'|0x80,KEYCODE_FUN5    ,
+               /* [ESC] ESC [ 1 5 [;2] ~ - [Alt-][Shift-]F5 */
+               '[','1','7','~'|0x80,KEYCODE_FUN6    ,
+               '[','1','8','~'|0x80,KEYCODE_FUN7    ,
+               '[','1','9','~'|0x80,KEYCODE_FUN8    ,
+               '[','2','0','~'|0x80,KEYCODE_FUN9    ,
+               '[','2','1','~'|0x80,KEYCODE_FUN10   ,
+               '[','2','3','~'|0x80,KEYCODE_FUN11   ,
+               '[','2','4','~'|0x80,KEYCODE_FUN12   ,
+#endif
+               0
+       };
+
+       n = 0;
+       if (nbuffered)
+               n = *nbuffered;
+       if (n == 0) {
+               /* If no data, block waiting for input. If we read more
+                * than the minimal ESC sequence size, the "n=0" below
+                * would instead have to figure out how much to keep,
+                * resulting in larger code. */
+               n = safe_read(fd, buffer, 3);
+               if (n <= 0)
+                       return -1;
+       }
+
+       /* Grab character to return from buffer */
+       c = (unsigned char)buffer[0];
+       n--;
+       if (n)
+               memmove(buffer, buffer + 1, n);
+
+       /* Only ESC starts ESC sequences */
+       if (c != 27)
+               goto ret;
+
+       /* Loop through known ESC sequences */
+       pfd.fd = fd;
+       pfd.events = POLLIN;
+       seq = esccmds;
+       while (*seq != '\0') {
+               /* n - position in sequence we did not read yet */
+               int i = 0; /* position in sequence to compare */
+
+               /* Loop through chars in this sequence */
+               while (1) {
+                       /* So far escape sequence matched up to [i-1] */
+                       if (n <= i) {
+                               /* Need more chars, read another one if it wouldn't block.
+                                * Note that escape sequences come in as a unit,
+                                * so if we block for long it's not really an escape sequence.
+                                * Timeout is needed to reconnect escape sequences
+                                * split up by transmission over a serial console. */
+                               if (safe_poll(&pfd, 1, 50) == 0) {
+                                       /* No more data!
+                                        * Array is sorted from shortest to longest,
+                                        * we can't match anything later in array,
+                                        * break out of both loops. */
+                                       goto ret;
+                               }
+                               errno = 0;
+                               if (safe_read(fd, buffer + n, 1) <= 0) {
+                                       /* If EAGAIN, then fd is O_NONBLOCK and poll lied:
+                                        * in fact, there is no data. */
+                                       if (errno != EAGAIN)
+                                               c = -1; /* otherwise it's EOF/error */
+                                       goto ret;
+                               }
+                               n++;
+                       }
+                       if (buffer[i] != (seq[i] & 0x7f)) {
+                               /* This seq doesn't match, go to next */
+                               seq += i;
+                               /* Forward to last char */
+                               while (!(*seq & 0x80))
+                                       seq++;
+                               /* Skip it and the keycode which follows */
+                               seq += 2;
+                               break;
+                       }
+                       if (seq[i] & 0x80) {
+                               /* Entire seq matched */
+                               c = (signed char)seq[i+1];
+                               n = 0;
+                               /* n -= i; memmove(...);
+                                * would be more correct,
+                                * but we never read ahead that much,
+                                * and n == i here. */
+                               goto ret;
+                       }
+                       i++;
+               }
+       }
+       /* We did not find matching sequence, it was a bare ESC.
+        * We possibly read and stored more input in buffer[]
+        * by now. */
+
+ ret:
+       if (nbuffered)
+               *nbuffered = n;
+       return c;
+}
diff --git a/libbb/recursive_action.c b/libbb/recursive_action.c
new file mode 100644 (file)
index 0000000..3ec596a
--- /dev/null
@@ -0,0 +1,147 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Utility routines.
+ *
+ * Copyright (C) 1999-2004 by Erik Andersen <andersen@codepoet.org>
+ *
+ * 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 FAST_FUNC true_action(const char *fileName UNUSED_PARAM,
+               struct stat *statbuf UNUSED_PARAM,
+               void* userData UNUSED_PARAM,
+               int depth UNUSED_PARAM)
+{
+       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 !ACTION_RECURSE, dirAction is called on the directory and its
+ * return value is returned from recursive_action(). No recursion.
+ *
+ * If ACTION_RECURSE, recursive_action() is called on each directory.
+ * If any one of these calls returns 0, current recursive_action() returns 0.
+ *
+ * If ACTION_DEPTHFIRST, dirAction is called after recurse.
+ * If it returns 0, the warning is printed and recursive_action() returns 0.
+ *
+ * If !ACTION_DEPTHFIRST, dirAction is called before we recurse.
+ * 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 FAST_FUNC recursive_action(const char *fileName,
+               unsigned flags,
+               int FAST_FUNC (*fileAction)(const char *fileName, struct stat *statbuf, void* userData, int depth),
+               int FAST_FUNC (*dirAction)(const char *fileName, struct stat *statbuf, void* userData, int depth),
+               void* userData,
+               unsigned depth)
+{
+       struct stat statbuf;
+       int status;
+       DIR *dir;
+       struct dirent *next;
+
+       if (!fileAction) fileAction = true_action;
+       if (!dirAction) dirAction = true_action;
+
+       status = ACTION_FOLLOWLINKS; /* hijack a variable for bitmask... */
+       if (!depth)
+               status = ACTION_FOLLOWLINKS | ACTION_FOLLOWLINKS_L0;
+       status = ((flags & status) ? stat : lstat)(fileName, &statbuf);
+       if (status < 0) {
+#ifdef DEBUG_RECURS_ACTION
+               bb_error_msg("status=%d flags=%x", status, flags);
+#endif
+               goto done_nak_warn;
+       }
+
+       /* 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 ( /* (!(flags & ACTION_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 (!(flags & ACTION_RECURSE)) {
+               return dirAction(fileName, &statbuf, userData, depth);
+       }
+
+       if (!(flags & ACTION_DEPTHFIRST)) {
+               status = dirAction(fileName, &statbuf, userData, depth);
+               if (!status)
+                       goto done_nak_warn;
+               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 {} \;" */
+               goto done_nak_warn;
+       }
+       status = TRUE;
+       while ((next = readdir(dir)) != NULL) {
+               char *nextFile;
+
+               nextFile = concat_subpath_file(fileName, next->d_name);
+               if (nextFile == NULL)
+                       continue;
+               /* process every file (NB: ACTION_RECURSE is set in flags) */
+               if (!recursive_action(nextFile, flags, fileAction, dirAction,
+                                               userData, depth + 1))
+                       status = FALSE;
+//             s = recursive_action(nextFile, flags, fileAction, dirAction,
+//                                             userData, depth + 1);
+               free(nextFile);
+//#define RECURSE_RESULT_ABORT 3
+//             if (s == RECURSE_RESULT_ABORT) {
+//                     closedir(dir);
+//                     return s;
+//             }
+//             if (s == FALSE)
+//                     status = FALSE;
+       }
+       closedir(dir);
+
+       if (flags & ACTION_DEPTHFIRST) {
+               if (!dirAction(fileName, &statbuf, userData, depth))
+                       goto done_nak_warn;
+       }
+
+       return status;
+
+ done_nak_warn:
+       if (!(flags & ACTION_QUIET))
+               bb_simple_perror_msg(fileName);
+       return FALSE;
+}
diff --git a/libbb/remove_file.c b/libbb/remove_file.c
new file mode 100644 (file)
index 0000000..8b14f07
--- /dev/null
@@ -0,0 +1,102 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Mini remove_file implementation for busybox
+ *
+ * Copyright (C) 2001 Matt Kraai <kraai@alumni.carnegiemellon.edu>
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.h"
+
+/* Used from NOFORK applets. Must not allocate anything */
+
+int FAST_FUNC remove_file(const char *path, int flags)
+{
+       struct stat path_stat;
+
+       if (lstat(path, &path_stat) < 0) {
+               if (errno != ENOENT) {
+                       bb_perror_msg("cannot stat '%s'", path);
+                       return -1;
+               }
+               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;
+               }
+
+               dp = opendir(path);
+               if (dp == 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;
+       }
+
+       /* !ISDIR */
+       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 (file)
index 0000000..2a5073f
--- /dev/null
@@ -0,0 +1,46 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Copyright 1989 - 1991, Julianne Frances Haugh <jockgrrl@austin.rr.com>
+ * 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"
+
+/* Return 1 if SHELL is a restricted shell (one not returned by
+   getusershell), else 0, meaning it is a standard shell.  */
+int FAST_FUNC restricted_shell(const char *shell)
+{
+       char *line;
+
+       setusershell();
+       while ((line = getusershell())) {
+               if (*line != '#' && strcmp(line, shell) == 0)
+                       return 0;
+       }
+       endusershell();
+       return 1;
+}
diff --git a/libbb/rtc.c b/libbb/rtc.c
new file mode 100644 (file)
index 0000000..51834f8
--- /dev/null
@@ -0,0 +1,90 @@
+/*
+ * Common RTC functions
+ *
+ * Licensed under GPLv2, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.h"
+#include "rtc_.h"
+
+#if ENABLE_FEATURE_HWCLOCK_ADJTIME_FHS
+# define ADJTIME_PATH "/var/lib/hwclock/adjtime"
+#else
+# define ADJTIME_PATH "/etc/adjtime"
+#endif
+
+int FAST_FUNC rtc_adjtime_is_utc(void)
+{
+       int utc = 0;
+       FILE *f = fopen_for_read(ADJTIME_PATH);
+
+       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;
+}
+
+int FAST_FUNC rtc_xopen(const char **default_rtc, int flags)
+{
+       int rtc;
+
+       if (!*default_rtc) {
+               *default_rtc = "/dev/rtc";
+               rtc = open(*default_rtc, flags);
+               if (rtc >= 0)
+                       return rtc;
+               *default_rtc = "/dev/rtc0";
+               rtc = open(*default_rtc, flags);
+               if (rtc >= 0)
+                       return rtc;
+               *default_rtc = "/dev/misc/rtc";
+       }
+
+       return xopen(*default_rtc, flags);
+}
+
+time_t FAST_FUNC rtc_read_time(int fd, int utc)
+{
+       struct tm tm;
+       char *oldtz = 0;
+       time_t t = 0;
+
+       memset(&tm, 0, sizeof(struct tm));
+       xioctl(fd, RTC_RD_TIME, &tm);
+       tm.tm_isdst = -1; /* not known */
+
+       if (utc) {
+               oldtz = getenv("TZ");
+               putenv((char*)"TZ=UTC0");
+               tzset();
+       }
+
+       t = mktime(&tm);
+
+       if (utc) {
+               unsetenv("TZ");
+               if (oldtz)
+                       putenv(oldtz - 3);
+               tzset();
+       }
+
+       return t;
+}
diff --git a/libbb/run_shell.c b/libbb/run_shell.c
new file mode 100644 (file)
index 0000000..2ccb3a1
--- /dev/null
@@ -0,0 +1,90 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Copyright 1989 - 1991, Julianne Frances Haugh <jockgrrl@austin.rr.com>
+ * 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"
+#if ENABLE_SELINUX
+#include <selinux/selinux.h>  /* for setexeccon  */
+#endif
+
+#if ENABLE_SELINUX
+static security_context_t current_sid;
+
+void FAST_FUNC renew_current_security_context(void)
+{
+       freecon(current_sid);  /* Release old context  */
+       getcon(&current_sid);  /* update */
+}
+void FAST_FUNC set_current_security_context(security_context_t 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 FAST_FUNC 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_nostrip(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;
+#if ENABLE_SELINUX
+       if (current_sid)
+               setexeccon(current_sid);
+       if (ENABLE_FEATURE_CLEAN_UP)
+               freecon(current_sid);
+#endif
+       execv(shell, (char **) args);
+       bb_perror_msg_and_die("cannot run %s", shell);
+}
diff --git a/libbb/safe_gethostname.c b/libbb/safe_gethostname.c
new file mode 100644 (file)
index 0000000..7407fb7
--- /dev/null
@@ -0,0 +1,66 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Safe gethostname implementation for busybox
+ *
+ * Copyright (C) 2008 Tito Ragusa <farmatito@tiscali.it>
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+/*
+ * SUSv2 guarantees that "Host names are limited to 255 bytes"
+ * POSIX.1-2001 guarantees that "Host names (not including the terminating
+ * null byte) are limited to HOST_NAME_MAX bytes" (64 bytes on my box).
+ *
+ * RFC1123 says:
+ *
+ * The syntax of a legal Internet host name was specified in RFC-952
+ * [DNS:4].  One aspect of host name syntax is hereby changed: the
+ * restriction on the first character is relaxed to allow either a
+ * letter or a digit.  Host software MUST support this more liberal
+ * syntax.
+ *
+ * Host software MUST handle host names of up to 63 characters and
+ * SHOULD handle host names of up to 255 characters.
+ */
+
+#include "libbb.h"
+#include <sys/utsname.h>
+
+/*
+ * On success return the current malloced and NUL terminated hostname.
+ * On error return malloced and NUL terminated string "?".
+ * This is an illegal first character for a hostname.
+ * The returned malloced string must be freed by the caller.
+ */
+char* FAST_FUNC safe_gethostname(void)
+{
+       struct utsname uts;
+
+       /* The length of the arrays in a struct utsname is unspecified;
+        * the fields are terminated by a null byte.
+        * Note that there is no standard that says that the hostname
+        * set by sethostname(2) is the same string as the nodename field of the
+        * struct returned by uname (indeed, some systems allow a 256-byte host-
+        * name and an 8-byte nodename), but this is true on Linux. The same holds
+        * for setdomainname(2) and the domainname field.
+        */
+
+       /* Uname can fail only if you pass a bad pointer to it. */
+       uname(&uts);
+       return xstrndup(!uts.nodename[0] ? "?" : uts.nodename, sizeof(uts.nodename));
+}
+
+/*
+ * On success return the current malloced and NUL terminated domainname.
+ * On error return malloced and NUL terminated string "?".
+ * This is an illegal first character for a domainname.
+ * The returned malloced string must be freed by the caller.
+ */
+char* FAST_FUNC safe_getdomainname(void)
+{
+       struct utsname uts;
+
+       uname(&uts);
+       return xstrndup(!uts.domainname[0] ? "?" : uts.domainname, sizeof(uts.domainname));
+}
diff --git a/libbb/safe_poll.c b/libbb/safe_poll.c
new file mode 100644 (file)
index 0000000..58c7bda
--- /dev/null
@@ -0,0 +1,34 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Utility routines.
+ *
+ * Copyright (C) 2007 by Denys Vlasenko <vda.linux@googlemail.com>
+ *
+ * Licensed under GPLv2, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.h"
+
+/* Wrapper which restarts poll on EINTR or ENOMEM.
+ * On other errors does perror("poll") and returns.
+ * Warning! May take longer than timeout_ms to return! */
+int FAST_FUNC safe_poll(struct pollfd *ufds, nfds_t nfds, int timeout)
+{
+       while (1) {
+               int n = poll(ufds, nfds, timeout);
+               if (n >= 0)
+                       return n;
+               /* Make sure we inch towards completion */
+               if (timeout > 0)
+                       timeout--;
+               /* E.g. strace causes poll to return this */
+               if (errno == EINTR)
+                       continue;
+               /* Kernel is very low on memory. Retry. */
+               /* I doubt many callers would handle this correctly! */
+               if (errno == ENOMEM)
+                       continue;
+               bb_perror_msg("poll");
+               return n;
+       }
+}
diff --git a/libbb/safe_strncpy.c b/libbb/safe_strncpy.c
new file mode 100644 (file)
index 0000000..4acd976
--- /dev/null
@@ -0,0 +1,27 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Utility routines.
+ *
+ * Copyright (C) 1999-2004 by Erik Andersen <andersen@codepoet.org>
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.h"
+
+/* Like strncpy but make sure the resulting string is always 0 terminated. */
+char* FAST_FUNC safe_strncpy(char *dst, const char *src, size_t size)
+{
+       if (!size) return dst;
+       dst[--size] = '\0';
+       return strncpy(dst, src, size);
+}
+
+/* Like strcpy but can copy overlapping strings. */
+void FAST_FUNC overlapping_strcpy(char *dst, const char *src)
+{
+       while ((*dst = *src) != '\0') {
+               dst++;
+               src++;
+       }
+}
diff --git a/libbb/safe_write.c b/libbb/safe_write.c
new file mode 100644 (file)
index 0000000..e3561f3
--- /dev/null
@@ -0,0 +1,21 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Utility routines.
+ *
+ * Copyright (C) 1999-2004 by Erik Andersen <andersen@codepoet.org>
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.h"
+
+ssize_t FAST_FUNC 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/selinux_common.c b/libbb/selinux_common.c
new file mode 100644 (file)
index 0000000..275a761
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ * libbb/selinux_common.c
+ *   -- common SELinux utility functions
+ *
+ * Copyright 2007 KaiGai Kohei <kaigai@kaigai.gr.jp>
+ *
+ * Licensed under GPLv2, see file LICENSE in this tarball for details.
+ */
+#include "libbb.h"
+#include <selinux/context.h>
+
+context_t FAST_FUNC set_security_context_component(security_context_t cur_context,
+                                        char *user, char *role, char *type, char *range)
+{
+       context_t con = context_new(cur_context);
+       if (!con)
+               return NULL;
+
+       if (user && context_user_set(con, user))
+               goto error;
+       if (type && context_type_set(con, type))
+               goto error;
+       if (range && context_range_set(con, range))
+               goto error;
+       if (role && context_role_set(con, role))
+               goto error;
+       return con;
+
+error:
+       context_free(con);
+       return NULL;
+}
+
+void FAST_FUNC setfscreatecon_or_die(security_context_t scontext)
+{
+       if (setfscreatecon(scontext) < 0) {
+               /* Can be NULL. All known printf implementations
+                * display "(null)", "<null>" etc */
+               bb_perror_msg_and_die("cannot set default "
+                               "file creation context to %s", scontext);
+       }
+}
+
+void FAST_FUNC selinux_preserve_fcontext(int fdesc)
+{
+       security_context_t context;
+
+       if (fgetfilecon(fdesc, &context) < 0) {
+               if (errno == ENODATA || errno == ENOTSUP)
+                       return;
+               bb_perror_msg_and_die("fgetfilecon failed");
+       }
+       setfscreatecon_or_die(context);
+       freecon(context);
+}
+
diff --git a/libbb/setup_environment.c b/libbb/setup_environment.c
new file mode 100644 (file)
index 0000000..78318ce
--- /dev/null
@@ -0,0 +1,70 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Copyright 1989 - 1991, Julianne Frances Haugh <jockgrrl@austin.rr.com>
+ * 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"
+
+void FAST_FUNC setup_environment(const char *shell, int clear_env, int change_env, const struct passwd *pw)
+{
+       /* Change the current working directory to be the home directory
+        * of the user */
+       if (chdir(pw->pw_dir)) {
+               xchdir("/");
+               bb_error_msg("can't chdir to home directory '%s'", pw->pw_dir);
+       }
+
+       if (clear_env) {
+               const char *term;
+
+               /* Leave TERM unchanged. Set HOME, SHELL, USER, LOGNAME, PATH.
+                  Unset all other environment variables.  */
+               term = getenv("TERM");
+               clearenv();
+               if (term)
+                       xsetenv("TERM", term);
+               xsetenv("PATH", (pw->pw_uid ? bb_default_path : bb_default_root_path));
+               goto shortcut;
+               // No, gcc (4.2.1) is not clever enougn to do it itself.
+               //xsetenv("USER",    pw->pw_name);
+               //xsetenv("LOGNAME", pw->pw_name);
+               //xsetenv("HOME",    pw->pw_dir);
+               //xsetenv("SHELL",   shell);
+       }
+       else if (change_env) {
+               /* Set HOME, SHELL, and if not becoming a super-user,
+                  USER and LOGNAME.  */
+               if (pw->pw_uid) {
+ shortcut:
+                       xsetenv("USER",    pw->pw_name);
+                       xsetenv("LOGNAME", pw->pw_name);
+               }
+               xsetenv("HOME",    pw->pw_dir);
+               xsetenv("SHELL",   shell);
+       }
+}
diff --git a/libbb/sha1.c b/libbb/sha1.c
new file mode 100644 (file)
index 0000000..9fa095e
--- /dev/null
@@ -0,0 +1,465 @@
+/* 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 <brg@gladman.me.uk>, 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
+ *
+ * ---------------------------------------------------------------------------
+ *
+ * SHA256 and SHA512 parts are:
+ * Released into the Public Domain by Ulrich Drepper <drepper@redhat.com>.
+ * Shrank by Denys Vlasenko.
+ *
+ * ---------------------------------------------------------------------------
+ *
+ * The best way to test random blocksizes is to go to coreutils/md5_sha1_sum.c
+ * and replace "4096" with something like "2000 + time(NULL) % 2097",
+ * then rebuild and compare "shaNNNsum bigfile" results.
+ */
+
+#include "libbb.h"
+
+#define rotl32(x,n) (((x) << (n)) | ((x) >> (32 - (n))))
+#define rotr32(x,n) (((x) >> (n)) | ((x) << (32 - (n))))
+/* for sha512: */
+#define rotr64(x,n) (((x) >> (n)) | ((x) << (64 - (n))))
+#if BB_LITTLE_ENDIAN
+static inline uint64_t hton64(uint64_t v)
+{
+       return (((uint64_t)htonl(v)) << 32) | htonl(v >> 32);
+}
+#else
+#define hton64(v) (v)
+#endif
+#define ntoh64(v) hton64(v)
+
+/* To check alignment gcc has an appropriate operator.  Other
+   compilers don't.  */
+#if defined(__GNUC__) && __GNUC__ >= 2
+# define UNALIGNED_P(p,type) (((uintptr_t) p) % __alignof__(type) != 0)
+#else
+# define UNALIGNED_P(p,type) (((uintptr_t) p) % sizeof(type) != 0)
+#endif
+
+
+static void FAST_FUNC sha1_process_block64(sha1_ctx_t *ctx)
+{
+       unsigned t;
+       uint32_t W[80], a, b, c, d, e;
+       const uint32_t *words = (uint32_t*) ctx->wbuffer;
+
+       for (t = 0; t < 16; ++t) {
+               W[t] = ntohl(*words);
+               words++;
+       }
+
+       for (/*t = 16*/; t < 80; ++t) {
+               uint32_t T = W[t - 3] ^ W[t - 8] ^ W[t - 14] ^ W[t - 16];
+               W[t] = rotl32(T, 1);
+       }
+
+       a = ctx->hash[0];
+       b = ctx->hash[1];
+       c = ctx->hash[2];
+       d = ctx->hash[3];
+       e = ctx->hash[4];
+
+/* 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 { \
+               uint32_t T = a; \
+               a = rotl32(a, 5) + f(b, c, d) + e + k + W[t]; \
+               e = d; \
+               d = c; \
+               c = rotl32(b, 30); \
+               b = T; \
+       } while (0)
+
+       for (t = 0; t < 20; ++t)
+               rnd(ch, 0x5a827999);
+
+       for (/*t = 20*/; t < 40; ++t)
+               rnd(parity, 0x6ed9eba1);
+
+       for (/*t = 40*/; t < 60; ++t)
+               rnd(maj, 0x8f1bbcdc);
+
+       for (/*t = 60*/; t < 80; ++t)
+               rnd(parity, 0xca62c1d6);
+#undef ch
+#undef parity
+#undef maj
+#undef rnd
+
+       ctx->hash[0] += a;
+       ctx->hash[1] += b;
+       ctx->hash[2] += c;
+       ctx->hash[3] += d;
+       ctx->hash[4] += e;
+}
+
+/* Constants for SHA512 from FIPS 180-2:4.2.3.
+ * SHA256 constants from FIPS 180-2:4.2.2
+ * are the most significant half of first 64 elements
+ * of the same array.
+ */
+static const uint64_t sha_K[80] = {
+       0x428a2f98d728ae22ULL, 0x7137449123ef65cdULL,
+       0xb5c0fbcfec4d3b2fULL, 0xe9b5dba58189dbbcULL,
+       0x3956c25bf348b538ULL, 0x59f111f1b605d019ULL,
+       0x923f82a4af194f9bULL, 0xab1c5ed5da6d8118ULL,
+       0xd807aa98a3030242ULL, 0x12835b0145706fbeULL,
+       0x243185be4ee4b28cULL, 0x550c7dc3d5ffb4e2ULL,
+       0x72be5d74f27b896fULL, 0x80deb1fe3b1696b1ULL,
+       0x9bdc06a725c71235ULL, 0xc19bf174cf692694ULL,
+       0xe49b69c19ef14ad2ULL, 0xefbe4786384f25e3ULL,
+       0x0fc19dc68b8cd5b5ULL, 0x240ca1cc77ac9c65ULL,
+       0x2de92c6f592b0275ULL, 0x4a7484aa6ea6e483ULL,
+       0x5cb0a9dcbd41fbd4ULL, 0x76f988da831153b5ULL,
+       0x983e5152ee66dfabULL, 0xa831c66d2db43210ULL,
+       0xb00327c898fb213fULL, 0xbf597fc7beef0ee4ULL,
+       0xc6e00bf33da88fc2ULL, 0xd5a79147930aa725ULL,
+       0x06ca6351e003826fULL, 0x142929670a0e6e70ULL,
+       0x27b70a8546d22ffcULL, 0x2e1b21385c26c926ULL,
+       0x4d2c6dfc5ac42aedULL, 0x53380d139d95b3dfULL,
+       0x650a73548baf63deULL, 0x766a0abb3c77b2a8ULL,
+       0x81c2c92e47edaee6ULL, 0x92722c851482353bULL,
+       0xa2bfe8a14cf10364ULL, 0xa81a664bbc423001ULL,
+       0xc24b8b70d0f89791ULL, 0xc76c51a30654be30ULL,
+       0xd192e819d6ef5218ULL, 0xd69906245565a910ULL,
+       0xf40e35855771202aULL, 0x106aa07032bbd1b8ULL,
+       0x19a4c116b8d2d0c8ULL, 0x1e376c085141ab53ULL,
+       0x2748774cdf8eeb99ULL, 0x34b0bcb5e19b48a8ULL,
+       0x391c0cb3c5c95a63ULL, 0x4ed8aa4ae3418acbULL,
+       0x5b9cca4f7763e373ULL, 0x682e6ff3d6b2b8a3ULL,
+       0x748f82ee5defb2fcULL, 0x78a5636f43172f60ULL,
+       0x84c87814a1f0ab72ULL, 0x8cc702081a6439ecULL,
+       0x90befffa23631e28ULL, 0xa4506cebde82bde9ULL,
+       0xbef9a3f7b2c67915ULL, 0xc67178f2e372532bULL,
+       0xca273eceea26619cULL, 0xd186b8c721c0c207ULL, /* [64]+ are used for sha512 only */
+       0xeada7dd6cde0eb1eULL, 0xf57d4f7fee6ed178ULL,
+       0x06f067aa72176fbaULL, 0x0a637dc5a2c898a6ULL,
+       0x113f9804bef90daeULL, 0x1b710b35131c471bULL,
+       0x28db77f523047d84ULL, 0x32caab7b40c72493ULL,
+       0x3c9ebe0a15c9bebcULL, 0x431d67c49c100d4cULL,
+       0x4cc5d4becb3e42b6ULL, 0x597f299cfc657e2aULL,
+       0x5fcb6fab3ad6faecULL, 0x6c44198c4a475817ULL
+};
+
+static void FAST_FUNC sha256_process_block64(sha256_ctx_t *ctx)
+{
+       unsigned t;
+       uint32_t W[64], a, b, c, d, e, f, g, h;
+       const uint32_t *words = (uint32_t*) ctx->wbuffer;
+
+       /* Operators defined in FIPS 180-2:4.1.2.  */
+#define Ch(x, y, z) ((x & y) ^ (~x & z))
+#define Maj(x, y, z) ((x & y) ^ (x & z) ^ (y & z))
+#define S0(x) (rotr32(x, 2) ^ rotr32(x, 13) ^ rotr32(x, 22))
+#define S1(x) (rotr32(x, 6) ^ rotr32(x, 11) ^ rotr32(x, 25))
+#define R0(x) (rotr32(x, 7) ^ rotr32(x, 18) ^ (x >> 3))
+#define R1(x) (rotr32(x, 17) ^ rotr32(x, 19) ^ (x >> 10))
+
+       /* Compute the message schedule according to FIPS 180-2:6.2.2 step 2.  */
+       for (t = 0; t < 16; ++t) {
+               W[t] = ntohl(*words);
+               words++;
+       }
+
+       for (/*t = 16*/; t < 64; ++t)
+               W[t] = R1(W[t - 2]) + W[t - 7] + R0(W[t - 15]) + W[t - 16];
+
+       a = ctx->hash[0];
+       b = ctx->hash[1];
+       c = ctx->hash[2];
+       d = ctx->hash[3];
+       e = ctx->hash[4];
+       f = ctx->hash[5];
+       g = ctx->hash[6];
+       h = ctx->hash[7];
+
+       /* The actual computation according to FIPS 180-2:6.2.2 step 3.  */
+       for (t = 0; t < 64; ++t) {
+               /* Need to fetch upper half of sha_K[t]
+                * (I hope compiler is clever enough to just fetch
+                * upper half)
+                */
+               uint32_t K_t = sha_K[t] >> 32;
+               uint32_t T1 = h + S1(e) + Ch(e, f, g) + K_t + W[t];
+               uint32_t T2 = S0(a) + Maj(a, b, c);
+               h = g;
+               g = f;
+               f = e;
+               e = d + T1;
+               d = c;
+               c = b;
+               b = a;
+               a = T1 + T2;
+       }
+#undef Ch
+#undef Maj
+#undef S0
+#undef S1
+#undef R0
+#undef R1
+       /* Add the starting values of the context according to FIPS 180-2:6.2.2
+          step 4.  */
+       ctx->hash[0] += a;
+       ctx->hash[1] += b;
+       ctx->hash[2] += c;
+       ctx->hash[3] += d;
+       ctx->hash[4] += e;
+       ctx->hash[5] += f;
+       ctx->hash[6] += g;
+       ctx->hash[7] += h;
+}
+
+static void FAST_FUNC sha512_process_block128(sha512_ctx_t *ctx)
+{
+       unsigned t;
+       uint64_t W[80];
+       /* On i386, having assignments here (not later as sha256 does)
+        * produces 99 bytes smaller code with gcc 4.3.1
+        */
+       uint64_t a = ctx->hash[0];
+       uint64_t b = ctx->hash[1];
+       uint64_t c = ctx->hash[2];
+       uint64_t d = ctx->hash[3];
+       uint64_t e = ctx->hash[4];
+       uint64_t f = ctx->hash[5];
+       uint64_t g = ctx->hash[6];
+       uint64_t h = ctx->hash[7];
+       const uint64_t *words = (uint64_t*) ctx->wbuffer;
+
+       /* Operators defined in FIPS 180-2:4.1.2.  */
+#define Ch(x, y, z) ((x & y) ^ (~x & z))
+#define Maj(x, y, z) ((x & y) ^ (x & z) ^ (y & z))
+#define S0(x) (rotr64(x, 28) ^ rotr64(x, 34) ^ rotr64(x, 39))
+#define S1(x) (rotr64(x, 14) ^ rotr64(x, 18) ^ rotr64(x, 41))
+#define R0(x) (rotr64(x, 1) ^ rotr64(x, 8) ^ (x >> 7))
+#define R1(x) (rotr64(x, 19) ^ rotr64(x, 61) ^ (x >> 6))
+
+       /* Compute the message schedule according to FIPS 180-2:6.3.2 step 2.  */
+       for (t = 0; t < 16; ++t) {
+               W[t] = ntoh64(*words);
+               words++;
+       }
+       for (/*t = 16*/; t < 80; ++t)
+               W[t] = R1(W[t - 2]) + W[t - 7] + R0(W[t - 15]) + W[t - 16];
+
+       /* The actual computation according to FIPS 180-2:6.3.2 step 3.  */
+       for (t = 0; t < 80; ++t) {
+               uint64_t T1 = h + S1(e) + Ch(e, f, g) + sha_K[t] + W[t];
+               uint64_t T2 = S0(a) + Maj(a, b, c);
+               h = g;
+               g = f;
+               f = e;
+               e = d + T1;
+               d = c;
+               c = b;
+               b = a;
+               a = T1 + T2;
+       }
+#undef Ch
+#undef Maj
+#undef S0
+#undef S1
+#undef R0
+#undef R1
+       /* Add the starting values of the context according to FIPS 180-2:6.3.2
+          step 4.  */
+       ctx->hash[0] += a;
+       ctx->hash[1] += b;
+       ctx->hash[2] += c;
+       ctx->hash[3] += d;
+       ctx->hash[4] += e;
+       ctx->hash[5] += f;
+       ctx->hash[6] += g;
+       ctx->hash[7] += h;
+}
+
+
+void FAST_FUNC sha1_begin(sha1_ctx_t *ctx)
+{
+       ctx->hash[0] = 0x67452301;
+       ctx->hash[1] = 0xefcdab89;
+       ctx->hash[2] = 0x98badcfe;
+       ctx->hash[3] = 0x10325476;
+       ctx->hash[4] = 0xc3d2e1f0;
+       ctx->total64 = 0;
+       ctx->process_block = sha1_process_block64;
+}
+
+static const uint32_t init256[] = {
+       0x6a09e667,
+       0xbb67ae85,
+       0x3c6ef372,
+       0xa54ff53a,
+       0x510e527f,
+       0x9b05688c,
+       0x1f83d9ab,
+       0x5be0cd19
+};
+static const uint32_t init512_lo[] = {
+       0xf3bcc908,
+       0x84caa73b,
+       0xfe94f82b,
+       0x5f1d36f1,
+       0xade682d1,
+       0x2b3e6c1f,
+       0xfb41bd6b,
+       0x137e2179
+};
+
+/* Initialize structure containing state of computation.
+   (FIPS 180-2:5.3.2)  */
+void FAST_FUNC sha256_begin(sha256_ctx_t *ctx)
+{
+       memcpy(ctx->hash, init256, sizeof(init256));
+       ctx->total64 = 0;
+       ctx->process_block = sha256_process_block64;
+}
+
+/* Initialize structure containing state of computation.
+   (FIPS 180-2:5.3.3)  */
+void FAST_FUNC sha512_begin(sha512_ctx_t *ctx)
+{
+       int i;
+       for (i = 0; i < 8; i++)
+               ctx->hash[i] = ((uint64_t)(init256[i]) << 32) + init512_lo[i];
+       ctx->total64[0] = ctx->total64[1] = 0;
+}
+
+
+/* Used also for sha256 */
+void FAST_FUNC sha1_hash(const void *buffer, size_t len, sha1_ctx_t *ctx)
+{
+       unsigned in_buf = ctx->total64 & 63;
+       unsigned add = 64 - in_buf;
+
+       ctx->total64 += len;
+
+       while (len >= add) {    /* transfer whole blocks while possible  */
+               memcpy(ctx->wbuffer + in_buf, buffer, add);
+               buffer = (const char *)buffer + add;
+               len -= add;
+               add = 64;
+               in_buf = 0;
+               ctx->process_block(ctx);
+       }
+
+       memcpy(ctx->wbuffer + in_buf, buffer, len);
+}
+
+void FAST_FUNC sha512_hash(const void *buffer, size_t len, sha512_ctx_t *ctx)
+{
+       unsigned in_buf = ctx->total64[0] & 127;
+       unsigned add = 128 - in_buf;
+
+       /* First increment the byte count.  FIPS 180-2 specifies the possible
+          length of the file up to 2^128 _bits_.
+          We compute the number of _bytes_ and convert to bits later.  */
+       ctx->total64[0] += len;
+       if (ctx->total64[0] < len)
+               ctx->total64[1]++;
+
+       while (len >= add) {    /* transfer whole blocks while possible  */
+               memcpy(ctx->wbuffer + in_buf, buffer, add);
+               buffer = (const char *)buffer + add;
+               len -= add;
+               add = 128;
+               in_buf = 0;
+               sha512_process_block128(ctx);
+       }
+
+       memcpy(ctx->wbuffer + in_buf, buffer, len);
+}
+
+
+/* Used also for sha256 */
+void FAST_FUNC sha1_end(void *resbuf, sha1_ctx_t *ctx)
+{
+       unsigned i, pad, in_buf;
+
+       in_buf = ctx->total64 & 63;
+       /* Pad the buffer to the next 64-byte boundary with 0x80,0,0,0... */
+       ctx->wbuffer[in_buf++] = 0x80;
+
+       /* This loop iterates either once or twice, no more, no less */
+       while (1) {
+               pad = 64 - in_buf;
+               memset(ctx->wbuffer + in_buf, 0, pad);
+               in_buf = 0;
+               /* Do we have enough space for the length count? */
+               if (pad >= 8) {
+                       /* Store the 64-bit counter of bits in the buffer in BE format */
+                       uint64_t t = ctx->total64 << 3;
+                       t = hton64(t);
+                       /* wbuffer is suitably aligned for this */
+                       *(uint64_t *) (&ctx->wbuffer[64 - 8]) = t;
+               }
+               ctx->process_block(ctx);
+               if (pad >= 8)
+                       break;
+       }
+
+       in_buf = (ctx->process_block == sha1_process_block64) ? 5 : 8;
+       /* This way we do not impose alignment constraints on resbuf: */
+#if BB_LITTLE_ENDIAN
+       for (i = 0; i < in_buf; ++i)
+               ctx->hash[i] = htonl(ctx->hash[i]);
+#endif
+       memcpy(resbuf, ctx->hash, sizeof(ctx->hash[0]) * in_buf);
+}
+
+void FAST_FUNC sha512_end(void *resbuf, sha512_ctx_t *ctx)
+{
+       unsigned i, pad, in_buf;
+
+       in_buf = ctx->total64[0] & 127;
+       /* Pad the buffer to the next 128-byte boundary with 0x80,0,0,0...
+        * (FIPS 180-2:5.1.2)
+        */
+       ctx->wbuffer[in_buf++] = 0x80;
+
+       while (1) {
+               pad = 128 - in_buf;
+               memset(ctx->wbuffer + in_buf, 0, pad);
+               in_buf = 0;
+               if (pad >= 16) {
+                       /* Store the 128-bit counter of bits in the buffer in BE format */
+                       uint64_t t;
+                       t = ctx->total64[0] << 3;
+                       t = hton64(t);
+                       *(uint64_t *) (&ctx->wbuffer[128 - 8]) = t;
+                       t = (ctx->total64[1] << 3) | (ctx->total64[0] >> 61);
+                       t = hton64(t);
+                       *(uint64_t *) (&ctx->wbuffer[128 - 16]) = t;
+               }
+               sha512_process_block128(ctx);
+               if (pad >= 16)
+                       break;
+       }
+
+#if BB_LITTLE_ENDIAN
+       for (i = 0; i < ARRAY_SIZE(ctx->hash); ++i)
+               ctx->hash[i] = hton64(ctx->hash[i]);
+#endif
+       memcpy(resbuf, ctx->hash, sizeof(ctx->hash));
+}
diff --git a/libbb/signals.c b/libbb/signals.c
new file mode 100644 (file)
index 0000000..a528756
--- /dev/null
@@ -0,0 +1,121 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Utility routines.
+ *
+ * Copyright (C) 1999-2004 by Erik Andersen <andersen@codepoet.org>
+ * Copyright (C) 2006 Rob Landley
+ * Copyright (C) 2006 Denys Vlasenko
+ *
+ * Licensed under GPL version 2, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.h"
+
+/* All known arches use small ints for signals */
+smallint bb_got_signal;
+
+void record_signo(int signo)
+{
+       bb_got_signal = signo;
+}
+
+/* Saves 2 bytes on x86! Oh my... */
+int FAST_FUNC sigaction_set(int signum, const struct sigaction *act)
+{
+       return sigaction(signum, act, NULL);
+}
+
+int FAST_FUNC sigprocmask_allsigs(int how)
+{
+       sigset_t set;
+       sigfillset(&set);
+       return sigprocmask(how, &set, NULL);
+}
+
+void FAST_FUNC bb_signals(int sigs, void (*f)(int))
+{
+       int sig_no = 0;
+       int bit = 1;
+
+       while (sigs) {
+               if (sigs & bit) {
+                       sigs &= ~bit;
+                       signal(sig_no, f);
+               }
+               sig_no++;
+               bit <<= 1;
+       }
+}
+
+void FAST_FUNC bb_signals_recursive_norestart(int sigs, void (*f)(int))
+{
+       int sig_no = 0;
+       int bit = 1;
+       struct sigaction sa;
+
+       memset(&sa, 0, sizeof(sa));
+       sa.sa_handler = f;
+       /*sa.sa_flags = 0;*/
+       /*sigemptyset(&sa.sa_mask); - hope memset did it*/
+
+       while (sigs) {
+               if (sigs & bit) {
+                       sigs &= ~bit;
+                       sigaction_set(sig_no, &sa);
+               }
+               sig_no++;
+               bit <<= 1;
+       }
+}
+
+void FAST_FUNC sig_block(int sig)
+{
+       sigset_t ss;
+       sigemptyset(&ss);
+       sigaddset(&ss, sig);
+       sigprocmask(SIG_BLOCK, &ss, NULL);
+}
+
+void FAST_FUNC sig_unblock(int sig)
+{
+       sigset_t ss;
+       sigemptyset(&ss);
+       sigaddset(&ss, sig);
+       sigprocmask(SIG_UNBLOCK, &ss, NULL);
+}
+
+void FAST_FUNC wait_for_any_sig(void)
+{
+       sigset_t ss;
+       sigemptyset(&ss);
+       sigsuspend(&ss);
+}
+
+/* Assuming the sig is fatal */
+void FAST_FUNC kill_myself_with_sig(int sig)
+{
+       signal(sig, SIG_DFL);
+       sig_unblock(sig);
+       raise(sig);
+       _exit(EXIT_FAILURE); /* Should not reach it */
+}
+
+void FAST_FUNC signal_SA_RESTART_empty_mask(int sig, void (*handler)(int))
+{
+       struct sigaction sa;
+       memset(&sa, 0, sizeof(sa));
+       /*sigemptyset(&sa.sa_mask);*/
+       sa.sa_flags = SA_RESTART;
+       sa.sa_handler = handler;
+       sigaction_set(sig, &sa);
+}
+
+void FAST_FUNC signal_no_SA_RESTART_empty_mask(int sig, void (*handler)(int))
+{
+       struct sigaction sa;
+       memset(&sa, 0, sizeof(sa));
+       /*sigemptyset(&sa.sa_mask);*/
+       /*sa.sa_flags = 0;*/
+       sa.sa_handler = handler;
+       sigaction_set(sig, &sa);
+}
diff --git a/libbb/simplify_path.c b/libbb/simplify_path.c
new file mode 100644 (file)
index 0000000..f80e3e8
--- /dev/null
@@ -0,0 +1,59 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * bb_simplify_path implementation for busybox
+ *
+ * Copyright (C) 2001  Manuel Novoa III  <mjn3@codepoet.org>
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+#include "libbb.h"
+
+char* FAST_FUNC bb_simplify_abs_path_inplace(char *start)
+{
+       char *s, *p;
+
+       p = s = start;
+       do {
+               if (*p == '/') {
+                       if (*s == '/') {        /* skip duplicate (or initial) slash */
+                               continue;
+                       }
+                       if (*s == '.') {
+                               if (s[1] == '/' || !s[1]) {     /* remove extra '.' */
+                                       continue;
+                               }
+                               if ((s[1] == '.') && (s[2] == '/' || !s[2])) {
+                                       ++s;
+                                       if (p > start) {
+                                               while (*--p != '/')     /* omit previous dir */
+                                                       continue;
+                                       }
+                                       continue;
+                               }
+                       }
+               }
+               *++p = *s;
+       } while (*++s);
+
+       if ((p == start) || (*p != '/')) {      /* not a trailing slash */
+               ++p;                                    /* so keep last character */
+       }
+       *p = '\0';
+       return p;
+}
+
+char* FAST_FUNC bb_simplify_path(const char *path)
+{
+       char *s, *p;
+
+       if (path[0] == '/')
+               s = xstrdup(path);
+       else {
+               p = xrealloc_getcwd_or_warn(NULL);
+               s = concat_path_file(p, path);
+               free(p);
+       }
+
+       bb_simplify_abs_path_inplace(s);
+       return s;
+}
diff --git a/libbb/skip_whitespace.c b/libbb/skip_whitespace.c
new file mode 100644 (file)
index 0000000..e85f385
--- /dev/null
@@ -0,0 +1,25 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * skip_whitespace implementation for busybox
+ *
+ * Copyright (C) 2003  Manuel Novoa III  <mjn3@codepoet.org>
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.h"
+
+char* FAST_FUNC skip_whitespace(const char *s)
+{
+       /* NB: isspace('\0') returns 0 */
+       while (isspace(*s)) ++s;
+
+       return (char *) s;
+}
+
+char* FAST_FUNC skip_non_whitespace(const char *s)
+{
+       while (*s && !isspace(*s)) ++s;
+
+       return (char *) s;
+}
diff --git a/libbb/speed_table.c b/libbb/speed_table.c
new file mode 100644 (file)
index 0000000..05fe66c
--- /dev/null
@@ -0,0 +1,116 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * compact speed_t <-> speed functions for busybox
+ *
+ * Copyright (C) 2003  Manuel Novoa III  <mjn3@codepoet.org>
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+#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 = ARRAY_SIZE(speeds) };
+
+unsigned FAST_FUNC 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 FAST_FUNC 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 <stdio.h>
+
+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/str_tolower.c b/libbb/str_tolower.c
new file mode 100644 (file)
index 0000000..f402e8e
--- /dev/null
@@ -0,0 +1,14 @@
+/* vi set: sw=4 ts=4: */
+/* Convert string str to lowercase, return str.
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+#include "libbb.h"
+
+char* FAST_FUNC str_tolower(char *str)
+{
+       char *c;
+       for (c = str; *c; ++c)
+               *c = tolower(*c);
+       return str;
+}
diff --git a/libbb/strrstr.c b/libbb/strrstr.c
new file mode 100644 (file)
index 0000000..a803dd9
--- /dev/null
@@ -0,0 +1,71 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Utility routines.
+ *
+ * Copyright (C) 2008 Bernhard Reutner-Fischer
+ *
+ * Licensed under GPLv2 or later, see file License in this tarball for details.
+ */
+
+#ifdef __DO_STRRSTR_TEST
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#else
+#include "libbb.h"
+#endif
+
+/*
+ * The strrstr() function finds the last occurrence of the substring needle
+ * in the string haystack. The terminating nul characters are not compared.
+ */
+char* FAST_FUNC strrstr(const char *haystack, const char *needle)
+{
+       char *r = NULL;
+
+       if (!needle[0])
+               return (char*)haystack + strlen(haystack);
+       while (1) {
+               char *p = strstr(haystack, needle);
+               if (!p)
+                       return r;
+               r = p;
+               haystack = p + 1;
+       }
+}
+
+#ifdef __DO_STRRSTR_TEST
+int main(int argc, char **argv)
+{
+       static const struct {
+               const char *h, *n;
+               int pos;
+       } test_array[] = {
+               /* 0123456789 */
+               { "baaabaaab",  "aaa", 5  },
+               { "baaabaaaab", "aaa", 6  },
+               { "baaabaab",   "aaa", 1  },
+               { "aaa",        "aaa", 0  },
+               { "aaa",        "a",   2  },
+               { "aaa",        "bbb", -1 },
+               { "a",          "aaa", -1 },
+               { "aaa",        "",    3  },
+               { "",           "aaa", -1 },
+               { "",           "",    0  },
+       };
+
+       int i;
+
+       i = 0;
+       while (i < sizeof(test_array) / sizeof(test_array[0])) {
+               const char *r = strrstr(test_array[i].h, test_array[i].n);
+               printf("'%s' vs. '%s': '%s' - ", test_array[i].h, test_array[i].n, r);
+               if (r == NULL)
+                       r = test_array[i].h - 1;
+               printf("%s\n", r == test_array[i].h + test_array[i].pos ? "PASSED" : "FAILED");
+               i++;
+       }
+
+       return 0;
+}
+#endif
diff --git a/libbb/time.c b/libbb/time.c
new file mode 100644 (file)
index 0000000..850ac15
--- /dev/null
@@ -0,0 +1,66 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Utility routines.
+ *
+ * Copyright (C) 2007 Denys Vlasenko
+ *
+ * Licensed under GPL version 2, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.h"
+
+#if ENABLE_MONOTONIC_SYSCALL
+
+#include <sys/syscall.h>
+/* Old glibc (< 2.3.4) does not provide this constant. We use syscall
+ * directly so this definition is safe. */
+#ifndef CLOCK_MONOTONIC
+#define CLOCK_MONOTONIC 1
+#endif
+
+/* libc has incredibly messy way of doing this,
+ * typically requiring -lrt. We just skip all this mess */
+static void get_mono(struct timespec *ts)
+{
+       if (syscall(__NR_clock_gettime, CLOCK_MONOTONIC, ts))
+               bb_error_msg_and_die("clock_gettime(MONOTONIC) failed");
+}
+unsigned long long FAST_FUNC monotonic_ns(void)
+{
+       struct timespec ts;
+       get_mono(&ts);
+       return ts.tv_sec * 1000000000ULL + ts.tv_nsec;
+}
+unsigned long long FAST_FUNC monotonic_us(void)
+{
+       struct timespec ts;
+       get_mono(&ts);
+       return ts.tv_sec * 1000000ULL + ts.tv_nsec/1000;
+}
+unsigned FAST_FUNC monotonic_sec(void)
+{
+       struct timespec ts;
+       get_mono(&ts);
+       return ts.tv_sec;
+}
+
+#else
+
+unsigned long long FAST_FUNC monotonic_ns(void)
+{
+       struct timeval tv;
+       gettimeofday(&tv, NULL);
+       return tv.tv_sec * 1000000000ULL + tv.tv_usec * 1000;
+}
+unsigned long long FAST_FUNC monotonic_us(void)
+{
+       struct timeval tv;
+       gettimeofday(&tv, NULL);
+       return tv.tv_sec * 1000000ULL + tv.tv_usec;
+}
+unsigned FAST_FUNC monotonic_sec(void)
+{
+       return time(NULL);
+}
+
+#endif
diff --git a/libbb/trim.c b/libbb/trim.c
new file mode 100644 (file)
index 0000000..ea20ff3
--- /dev/null
@@ -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 "libbb.h"
+
+void FAST_FUNC 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");
+               if (lws) {
+                       len -= lws;
+                       memmove(s, s + lws, len);
+               }
+       }
+       s[len] = '\0';
+}
diff --git a/libbb/u_signal_names.c b/libbb/u_signal_names.c
new file mode 100644 (file)
index 0000000..915eea5
--- /dev/null
@@ -0,0 +1,180 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Signal name/number conversion routines.
+ *
+ * Copyright 2006 Rob Landley <rob@landley.net>
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.h"
+
+/* Believe it or not, but some arches have more than 32 SIGs!
+ * HPPA: SIGSTKFLT == 36. */
+
+static const char signals[][7] = {
+       // SUSv3 says kill must support these, and specifies the numerical values,
+       // http://www.opengroup.org/onlinepubs/009695399/utilities/kill.html
+       // {0, "EXIT"}, {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"}
+
+       [0] = "EXIT",
+#ifdef SIGHUP
+       [SIGHUP   ] = "HUP",
+#endif
+#ifdef SIGINT
+       [SIGINT   ] = "INT",
+#endif
+#ifdef SIGQUIT
+       [SIGQUIT  ] = "QUIT",
+#endif
+#ifdef SIGILL
+       [SIGILL   ] = "ILL",
+#endif
+#ifdef SIGTRAP
+       [SIGTRAP  ] = "TRAP",
+#endif
+#ifdef SIGABRT
+       [SIGABRT  ] = "ABRT",
+#endif
+#ifdef SIGBUS
+       [SIGBUS   ] = "BUS",
+#endif
+#ifdef SIGFPE
+       [SIGFPE   ] = "FPE",
+#endif
+#ifdef SIGKILL
+       [SIGKILL  ] = "KILL",
+#endif
+#ifdef SIGUSR1
+       [SIGUSR1  ] = "USR1",
+#endif
+#ifdef SIGSEGV
+       [SIGSEGV  ] = "SEGV",
+#endif
+#ifdef SIGUSR2
+       [SIGUSR2  ] = "USR2",
+#endif
+#ifdef SIGPIPE
+       [SIGPIPE  ] = "PIPE",
+#endif
+#ifdef SIGALRM
+       [SIGALRM  ] = "ALRM",
+#endif
+#ifdef SIGTERM
+       [SIGTERM  ] = "TERM",
+#endif
+#ifdef SIGSTKFLT
+       [SIGSTKFLT] = "STKFLT",
+#endif
+#ifdef SIGCHLD
+       [SIGCHLD  ] = "CHLD",
+#endif
+#ifdef SIGCONT
+       [SIGCONT  ] = "CONT",
+#endif
+#ifdef SIGSTOP
+       [SIGSTOP  ] = "STOP",
+#endif
+#ifdef SIGTSTP
+       [SIGTSTP  ] = "TSTP",
+#endif
+#ifdef SIGTTIN
+       [SIGTTIN  ] = "TTIN",
+#endif
+#ifdef SIGTTOU
+       [SIGTTOU  ] = "TTOU",
+#endif
+#ifdef SIGURG
+       [SIGURG   ] = "URG",
+#endif
+#ifdef SIGXCPU
+       [SIGXCPU  ] = "XCPU",
+#endif
+#ifdef SIGXFSZ
+       [SIGXFSZ  ] = "XFSZ",
+#endif
+#ifdef SIGVTALRM
+       [SIGVTALRM] = "VTALRM",
+#endif
+#ifdef SIGPROF
+       [SIGPROF  ] = "PROF",
+#endif
+#ifdef SIGWINCH
+       [SIGWINCH ] = "WINCH",
+#endif
+#ifdef SIGPOLL
+       [SIGPOLL  ] = "POLL",
+#endif
+#ifdef SIGPWR
+       [SIGPWR   ] = "PWR",
+#endif
+#ifdef SIGSYS
+       [SIGSYS   ] = "SYS",
+#endif
+};
+
+// Convert signal name to number.
+
+int FAST_FUNC get_signum(const char *name)
+{
+       unsigned i;
+
+       i = bb_strtou(name, NULL, 10);
+       if (!errno)
+               return i;
+       if (strncasecmp(name, "SIG", 3) == 0)
+               name += 3;
+       for (i = 0; i < ARRAY_SIZE(signals); i++)
+               if (strcasecmp(name, signals[i]) == 0)
+                       return i;
+
+#if ENABLE_DESKTOP && (defined(SIGIOT) || defined(SIGIO))
+       /* SIGIO[T] are aliased to other names,
+        * thus cannot be stored in the signals[] array.
+        * Need special code to recognize them */
+       if ((name[0] | 0x20) == 'i' && (name[1] | 0x20) == 'o') {
+#ifdef SIGIO
+               if (!name[2])
+                       return SIGIO;
+#endif
+#ifdef SIGIOT
+               if ((name[2] | 0x20) == 't' && !name[3])
+                       return SIGIOT;
+#endif
+       }
+#endif
+
+       return -1;
+}
+
+// Convert signal number to name
+
+const char* FAST_FUNC get_signame(int number)
+{
+       if ((unsigned)number < ARRAY_SIZE(signals)) {
+               if (signals[number][0]) /* if it's not an empty str */
+                       return signals[number];
+       }
+
+       return itoa(number);
+}
+
+
+// Print the whole signal list
+
+void FAST_FUNC print_signames(void)
+{
+       unsigned signo;
+
+       for (signo = 1; signo < ARRAY_SIZE(signals); signo++) {
+               const char *name = signals[signo];
+               if (name[0])
+                       puts(name);
+       }
+}
diff --git a/libbb/udp_io.c b/libbb/udp_io.c
new file mode 100644 (file)
index 0000000..b31f284
--- /dev/null
@@ -0,0 +1,168 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Utility routines.
+ *
+ * Copyright (C) 2007 Denys Vlasenko
+ *
+ * Licensed under GPL version 2, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.h"
+
+/*
+ * This asks kernel to let us know dst addr/port of incoming packets
+ * We don't check for errors here. Not supported == won't be used
+ */
+void FAST_FUNC
+socket_want_pktinfo(int fd)
+{
+#ifdef IP_PKTINFO
+       setsockopt(fd, IPPROTO_IP, IP_PKTINFO, &const_int_1, sizeof(int));
+#endif
+#if ENABLE_FEATURE_IPV6 && defined(IPV6_PKTINFO)
+       setsockopt(fd, IPPROTO_IPV6, IPV6_PKTINFO, &const_int_1, sizeof(int));
+#endif
+}
+
+
+ssize_t FAST_FUNC
+send_to_from(int fd, void *buf, size_t len, int flags,
+               const struct sockaddr *to,
+               const struct sockaddr *from,
+               socklen_t tolen)
+{
+#ifndef IP_PKTINFO
+       return sendto(fd, buf, len, flags, to, tolen);
+#else
+       struct iovec iov[1];
+       struct msghdr msg;
+       union {
+               char cmsg[CMSG_SPACE(sizeof(struct in_pktinfo))];
+#if ENABLE_FEATURE_IPV6 && defined(IPV6_PKTINFO)
+               char cmsg6[CMSG_SPACE(sizeof(struct in6_pktinfo))];
+#endif
+       } u;
+       struct cmsghdr* cmsgptr;
+
+       if (from->sa_family != AF_INET
+#if ENABLE_FEATURE_IPV6
+        && from->sa_family != AF_INET6
+#endif
+       ) {
+               /* ANY local address */
+               return sendto(fd, buf, len, flags, to, tolen);
+       }
+
+       /* man recvmsg and man cmsg is needed to make sense of code below */
+
+       iov[0].iov_base = buf;
+       iov[0].iov_len = len;
+
+       memset(&u, 0, sizeof(u));
+
+       memset(&msg, 0, sizeof(msg));
+       msg.msg_name = (void *)(struct sockaddr *)to; /* or compiler will annoy us */
+       msg.msg_namelen = tolen;
+       msg.msg_iov = iov;
+       msg.msg_iovlen = 1;
+       msg.msg_control = &u;
+       msg.msg_controllen = sizeof(u);
+       msg.msg_flags = flags;
+
+       cmsgptr = CMSG_FIRSTHDR(&msg);
+       if (to->sa_family == AF_INET && from->sa_family == AF_INET) {
+               struct in_pktinfo *pktptr;
+               cmsgptr->cmsg_level = IPPROTO_IP;
+               cmsgptr->cmsg_type = IP_PKTINFO;
+               cmsgptr->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo));
+               pktptr = (struct in_pktinfo *)(CMSG_DATA(cmsgptr));
+               /* pktptr->ipi_ifindex = 0; -- already done by memset(cbuf...) */
+               pktptr->ipi_spec_dst = ((struct sockaddr_in*)from)->sin_addr;
+       }
+#if ENABLE_FEATURE_IPV6 && defined(IPV6_PKTINFO)
+       else if (to->sa_family == AF_INET6 && from->sa_family == AF_INET6) {
+               struct in6_pktinfo *pktptr;
+               cmsgptr->cmsg_level = IPPROTO_IPV6;
+               cmsgptr->cmsg_type = IPV6_PKTINFO;
+               cmsgptr->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo));
+               pktptr = (struct in6_pktinfo *)(CMSG_DATA(cmsgptr));
+               /* pktptr->ipi6_ifindex = 0; -- already done by memset(cbuf...) */
+               pktptr->ipi6_addr = ((struct sockaddr_in6*)from)->sin6_addr;
+       }
+#endif
+       msg.msg_controllen = cmsgptr->cmsg_len;
+
+       return sendmsg(fd, &msg, flags);
+#endif
+}
+
+/* NB: this will never set port# in 'to'!
+ * _Only_ IP/IPv6 address part of 'to' is _maybe_ modified.
+ * Typical usage is to preinit 'to' with "default" value
+ * before calling recv_from_to(). */
+ssize_t FAST_FUNC
+recv_from_to(int fd, void *buf, size_t len, int flags,
+               struct sockaddr *from, struct sockaddr *to,
+               socklen_t sa_size)
+{
+#ifndef IP_PKTINFO
+       return recvfrom(fd, buf, len, flags, from, &sa_size);
+#else
+       /* man recvmsg and man cmsg is needed to make sense of code below */
+       struct iovec iov[1];
+       union {
+               char cmsg[CMSG_SPACE(sizeof(struct in_pktinfo))];
+#if ENABLE_FEATURE_IPV6 && defined(IPV6_PKTINFO)
+               char cmsg6[CMSG_SPACE(sizeof(struct in6_pktinfo))];
+#endif
+       } u;
+       struct cmsghdr *cmsgptr;
+       struct msghdr msg;
+       ssize_t recv_length;
+
+       iov[0].iov_base = buf;
+       iov[0].iov_len = len;
+
+       memset(&msg, 0, sizeof(msg));
+       msg.msg_name = (struct sockaddr *)from;
+       msg.msg_namelen = sa_size;
+       msg.msg_iov = iov;
+       msg.msg_iovlen = 1;
+       msg.msg_control = &u;
+       msg.msg_controllen = sizeof(u);
+
+       recv_length = recvmsg(fd, &msg, flags);
+       if (recv_length < 0)
+               return recv_length;
+
+       /* Here we try to retrieve destination IP and memorize it */
+       for (cmsgptr = CMSG_FIRSTHDR(&msg);
+                       cmsgptr != NULL;
+                       cmsgptr = CMSG_NXTHDR(&msg, cmsgptr)
+       ) {
+               if (cmsgptr->cmsg_level == IPPROTO_IP
+                && cmsgptr->cmsg_type == IP_PKTINFO
+               ) {
+#define pktinfo(cmsgptr) ( (struct in_pktinfo*)(CMSG_DATA(cmsgptr)) )
+                       to->sa_family = AF_INET;
+                       ((struct sockaddr_in*)to)->sin_addr = pktinfo(cmsgptr)->ipi_addr;
+                       /* ((struct sockaddr_in*)to)->sin_port = 123; */
+#undef pktinfo
+                       break;
+               }
+#if ENABLE_FEATURE_IPV6 && defined(IPV6_PKTINFO)
+               if (cmsgptr->cmsg_level == IPPROTO_IPV6
+                && cmsgptr->cmsg_type == IPV6_PKTINFO
+               ) {
+#define pktinfo(cmsgptr) ( (struct in6_pktinfo*)(CMSG_DATA(cmsgptr)) )
+                       to->sa_family = AF_INET6;
+                       ((struct sockaddr_in6*)to)->sin6_addr = pktinfo(cmsgptr)->ipi6_addr;
+                       /* ((struct sockaddr_in6*)to)->sin6_port = 123; */
+#undef pktinfo
+                       break;
+               }
+#endif
+       }
+       return recv_length;
+#endif
+}
diff --git a/libbb/update_passwd.c b/libbb/update_passwd.c
new file mode 100644 (file)
index 0000000..35b89a5
--- /dev/null
@@ -0,0 +1,266 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * update_passwd
+ *
+ * update_passwd is a common function for passwd and chpasswd applets;
+ * it is responsible for updating password file (i.e. /etc/passwd or
+ * /etc/shadow) for a given user and password.
+ *
+ * Moved from loginutils/passwd.c by Alexander Shishkin <virtuoso@slind.org>
+ *
+ * Modified to be able to add or delete users, groups and users to/from groups
+ * by Tito Ragusa <farmatito@tiscali.it>
+ *
+ * Licensed under GPLv2, see file LICENSE in this tarball for details.
+ */
+#include "libbb.h"
+
+#if ENABLE_SELINUX
+static void check_selinux_update_passwd(const char *username)
+{
+       security_context_t context;
+       char *seuser;
+
+       if (getuid() != (uid_t)0 || is_selinux_enabled() == 0)
+               return;         /* No need to check */
+
+       if (getprevcon_raw(&context) < 0)
+               bb_perror_msg_and_die("getprevcon failed");
+       seuser = strtok(context, ":");
+       if (!seuser)
+               bb_error_msg_and_die("invalid context '%s'", context);
+       if (strcmp(seuser, username) != 0) {
+               if (checkPasswdAccess(PASSWD__PASSWD) != 0)
+                       bb_error_msg_and_die("SELinux: access denied");
+       }
+       if (ENABLE_FEATURE_CLEAN_UP)
+               freecon(context);
+}
+#else
+# define check_selinux_update_passwd(username) ((void)0)
+#endif
+
+/*
+ 1) add a user: update_passwd(FILE, USER, REMAINING_PWLINE, NULL)
+    only if CONFIG_ADDUSER=y and applet_name[0] == 'a' like in adduser
+
+ 2) add a group: update_passwd(FILE, GROUP, REMAINING_GRLINE, NULL)
+    only if CONFIG_ADDGROUP=y and applet_name[0] == 'a' like in addgroup
+
+ 3) add a user to a group: update_passwd(FILE, GROUP, NULL, MEMBER)
+    only if CONFIG_FEATURE_ADDUSER_TO_GROUP=y, applet_name[0] == 'a'
+    like in addgroup and member != NULL
+
+ 4) delete a user: update_passwd(FILE, USER, NULL, NULL)
+
+ 5) delete a group: update_passwd(FILE, GROUP, NULL, NULL)
+
+ 6) delete a user from a group: update_passwd(FILE, GROUP, NULL, MEMBER)
+    only if CONFIG_FEATURE_DEL_USER_FROM_GROUP=y and member != NULL
+
+ 7) change user's passord: update_passwd(FILE, USER, NEW_PASSWD, NULL)
+    only if CONFIG_PASSWD=y and applet_name[0] == 'p' like in passwd
+    or if CONFIG_CHPASSWD=y and applet_name[0] == 'c' like in chpasswd
+
+ This function does not validate the arguments fed to it
+ so the calling program should take care of that.
+
+ Returns number of lines changed, or -1 on error.
+*/
+int FAST_FUNC update_passwd(const char *filename,
+               const char *name,
+               const char *new_passwd,
+               const char *member)
+{
+#if !(ENABLE_FEATURE_ADDUSER_TO_GROUP || ENABLE_FEATURE_DEL_USER_FROM_GROUP)
+#define member NULL
+#endif
+       struct stat sb;
+       struct flock lock;
+       FILE *old_fp;
+       FILE *new_fp;
+       char *fnamesfx;
+       char *sfx_char;
+       unsigned user_len;
+       int old_fd;
+       int new_fd;
+       int i;
+       int changed_lines;
+       int ret = -1; /* failure */
+
+       filename = xmalloc_follow_symlinks(filename);
+       if (filename == NULL)
+               return ret;
+
+       check_selinux_update_passwd(name);
+
+       /* New passwd file, "/etc/passwd+" for now */
+       fnamesfx = xasprintf("%s+", filename);
+       sfx_char = &fnamesfx[strlen(fnamesfx)-1];
+       name = xasprintf("%s:", name);
+       user_len = strlen(name);
+
+       if (strstr(filename, "shadow"))
+               old_fp = fopen(filename, "r+");
+       else
+               old_fp = fopen_or_warn(filename, "r+");
+       if (!old_fp)
+               goto free_mem;
+       old_fd = fileno(old_fp);
+
+       selinux_preserve_fcontext(old_fd);
+
+       /* 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(fnamesfx, 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("can't create '%s'", fnamesfx);
+       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);
+       }
+       errno = 0;
+       new_fp = fdopen(new_fd, "w");
+       if (!new_fp) {
+               bb_perror_nomsg();
+               close(new_fd);
+               goto unlink_new;
+       }
+
+       /* Backup file is "/etc/passwd-" */
+       *sfx_char = '-';
+       /* Delete old backup */
+       i = (unlink(fnamesfx) && errno != ENOENT);
+       /* Create backup as a hardlink to current */
+       if (i || link(filename, fnamesfx))
+               bb_perror_msg("warning: can't create backup copy '%s'",
+                               fnamesfx);
+       *sfx_char = '+';
+
+       /* 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: can't lock '%s'", filename);
+       lock.l_type = F_UNLCK;
+
+       /* Read current password file, write updated /etc/passwd+ */
+       changed_lines = 0;
+       while (1) {
+               char *cp, *line;
+
+               line = xmalloc_fgetline(old_fp);
+               if (!line) /* EOF/error */
+                       break;
+               if (strncmp(name, line, user_len) != 0) {
+                       fprintf(new_fp, "%s\n", line);
+                       goto next;
+               }
+
+               /* We have a match with "name:"... */
+               cp = line + user_len; /* move past name: */
+
+#if ENABLE_FEATURE_ADDUSER_TO_GROUP || ENABLE_FEATURE_DEL_USER_FROM_GROUP
+               if (member) {
+                       /* It's actually /etc/group+, not /etc/passwd+ */
+                       if (ENABLE_FEATURE_ADDUSER_TO_GROUP
+                        && applet_name[0] == 'a'
+                       ) {
+                               /* Add user to group */
+                               fprintf(new_fp, "%s%s%s\n", line,
+                                       last_char_is(line, ':') ? "" : ",",
+                                       member);
+                               changed_lines++;
+                       } else if (ENABLE_FEATURE_DEL_USER_FROM_GROUP
+                       /* && applet_name[0] == 'd' */
+                       ) {
+                               /* Delete user from group */
+                               char *tmp;
+                               const char *fmt = "%s";
+
+                               /* find the start of the member list: last ':' */
+                               cp = strrchr(line, ':');
+                               /* cut it */
+                               *cp++ = '\0';
+                               /* write the cut line name:passwd:gid:
+                                * or name:!:: */
+                               fprintf(new_fp, "%s:", line);
+                               /* parse the tokens of the member list */
+                               tmp = cp;
+                               while ((cp = strsep(&tmp, ",")) != NULL) {
+                                       if (strcmp(member, cp) != 0) {
+                                               fprintf(new_fp, fmt, cp);
+                                               fmt = ",%s";
+                                       } else {
+                                               /* found member, skip it */
+                                               changed_lines++;
+                                       }
+                               }
+                               fprintf(new_fp, "\n");
+                       }
+               } else
+#endif
+               if ((ENABLE_PASSWD && applet_name[0] == 'p')
+                || (ENABLE_CHPASSWD && applet_name[0] == 'c')
+               ) {
+                       /* Change passwd */
+                       cp = strchrnul(cp, ':'); /* move past old passwd */
+                       /* name: + new_passwd + :rest of line */
+                       fprintf(new_fp, "%s%s%s\n", name, new_passwd, cp);
+                       changed_lines++;
+               } /* else delete user or group: skip the line */
+ next:
+               free(line);
+       }
+
+       if (changed_lines == 0) {
+#if ENABLE_FEATURE_DEL_USER_FROM_GROUP
+               if (member)
+                       bb_error_msg("can't find %s in %s", member, filename);
+#endif
+               if ((ENABLE_ADDUSER || ENABLE_ADDGROUP)
+                && applet_name[0] == 'a' && !member
+               ) {
+                       /* add user or group */
+                       fprintf(new_fp, "%s%s\n", name, new_passwd);
+                       changed_lines++;
+               }
+       }
+
+       fcntl(old_fd, F_SETLK, &lock);
+
+       /* We do want all of them to execute, thus | instead of || */
+       errno = 0;
+       if ((ferror(old_fp) | fflush(new_fp) | fsync(new_fd) | fclose(new_fp))
+        || rename(fnamesfx, filename)
+       ) {
+               /* At least one of those failed */
+               bb_perror_nomsg();
+               goto unlink_new;
+       }
+       /* Success: ret >= 0 */
+       ret = changed_lines;
+
+ unlink_new:
+       if (ret < 0)
+               unlink(fnamesfx);
+
+ close_old_fp:
+       fclose(old_fp);
+
+ free_mem:
+       free(fnamesfx);
+       free((char *)filename);
+       free((char *)name);
+       return ret;
+}
diff --git a/libbb/uuencode.c b/libbb/uuencode.c
new file mode 100644 (file)
index 0000000..67d98d5
--- /dev/null
@@ -0,0 +1,71 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Copyright 2006 Rob Landley <rob@landley.net>
+ *
+ * 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 + 2] ALIGN1 = {
+       '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 */,
+       '\n', '\0' /* needed for uudecode.c */
+};
+
+const char bb_uuenc_tbl_std[65] ALIGN1 = {
+       '`', '!', '"', '#', '$', '%', '&', '\'',
+       '(', ')', '*', '+', ',', '-', '.', '/',
+       '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 bytes at S of length LENGTH to uuencode or 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 FAST_FUNC bb_uuencode(char *p, const void *src, int length, const char *tbl)
+{
+       const unsigned char *s = src;
+
+       /* Transform the 3x8 bits to 4x6 bits */
+       while (length > 0) {
+               unsigned s1, s2;
+
+               /* Are s[1], s[2] valid or should be assumed 0? */
+               s1 = s2 = 0;
+               length -= 3; /* can be >=0, -1, -2 */
+               if (length >= -1) {
+                       s1 = s[1];
+                       if (length >= 0)
+                               s2 = s[2];
+               }
+               *p++ = tbl[s[0] >> 2];
+               *p++ = tbl[((s[0] & 3) << 4) + (s1 >> 4)];
+               *p++ = tbl[((s1 & 0xf) << 2) + (s2 >> 6)];
+               *p++ = tbl[s2 & 0x3f];
+               s += 3;
+       }
+       /* Zero-terminate */
+       *p = '\0';
+       /* If length is -2 or -1, pad last char or two */
+       while (length) {
+               *--p = tbl[64];
+               length++;
+       }
+}
diff --git a/libbb/vdprintf.c b/libbb/vdprintf.c
new file mode 100644 (file)
index 0000000..09fffbc
--- /dev/null
@@ -0,0 +1,21 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Utility routines.
+ *
+ * Copyright (C) 1999-2004 by Erik Andersen <andersen@codepoet.org>
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.h"
+
+#if defined(__GLIBC__) && __GLIBC__ < 2
+int FAST_FUNC vdprintf(int d, const char *format, va_list ap)
+{
+       char buf[BUF_SIZE];
+       int len;
+
+       len = vsnprintf(buf, BUF_SIZE, format, ap);
+       return write(d, buf, len);
+}
+#endif
diff --git a/libbb/verror_msg.c b/libbb/verror_msg.c
new file mode 100644 (file)
index 0000000..58846d5
--- /dev/null
@@ -0,0 +1,127 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Utility routines.
+ *
+ * Copyright (C) 1999-2004 by Erik Andersen <andersen@codepoet.org>
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.h"
+#include <syslog.h>
+
+smallint logmode = LOGMODE_STDIO;
+const char *msg_eol = "\n";
+
+void FAST_FUNC bb_verror_msg(const char *s, va_list p, const char* strerr)
+{
+       char *msg;
+       int applet_len, strerr_len, msgeol_len, used;
+
+       if (!logmode)
+               return;
+
+       if (!s) /* nomsg[_and_die] uses NULL fmt */
+               s = ""; /* some libc don't like printf(NULL) */
+
+       used = vasprintf(&msg, s, p);
+       if (used < 0)
+               return;
+
+       /* This is ugly and costs +60 bytes compared to multiple
+        * fprintf's, but is guaranteed to do a single write.
+        * This is needed for e.g. httpd logging, when multiple
+        * children can produce log messages simultaneously. */
+
+       applet_len = strlen(applet_name) + 2; /* "applet: " */
+       strerr_len = strerr ? strlen(strerr) : 0;
+       msgeol_len = strlen(msg_eol);
+       /* +3 is for ": " before strerr and for terminating NUL */
+       msg = xrealloc(msg, applet_len + used + strerr_len + msgeol_len + 3);
+       /* TODO: maybe use writev instead of memmoving? Need full_writev? */
+       memmove(msg + applet_len, msg, used);
+       used += applet_len;
+       strcpy(msg, applet_name);
+       msg[applet_len - 2] = ':';
+       msg[applet_len - 1] = ' ';
+       if (strerr) {
+               if (s[0]) { /* not perror_nomsg? */
+                       msg[used++] = ':';
+                       msg[used++] = ' ';
+               }
+               strcpy(&msg[used], strerr);
+               used += strerr_len;
+       }
+       strcpy(&msg[used], msg_eol);
+
+       if (logmode & LOGMODE_STDIO) {
+               fflush(stdout);
+               full_write(STDERR_FILENO, msg, used + msgeol_len);
+       }
+       if (logmode & LOGMODE_SYSLOG) {
+               syslog(LOG_ERR, "%s", msg + applet_len);
+       }
+       free(msg);
+}
+
+
+#ifdef VERSION_WITH_WRITEV
+
+/* Code size is approximately the same, but currently it's the only user
+ * of writev in entire bbox. __libc_writev in uclibc is ~50 bytes. */
+
+void FAST_FUNC bb_verror_msg(const char *s, va_list p, const char* strerr)
+{
+       int strerr_len, msgeol_len;
+       struct iovec iov[3];
+
+#define used   (iov[2].iov_len)
+#define msgv   (iov[2].iov_base)
+#define msgc   ((char*)(iov[2].iov_base))
+#define msgptr (&(iov[2].iov_base))
+
+       if (!logmode)
+               return;
+
+       if (!s) /* nomsg[_and_die] uses NULL fmt */
+               s = ""; /* some libc don't like printf(NULL) */
+
+       /* Prevent "derefing type-punned ptr will break aliasing rules" */
+       used = vasprintf((char**)(void*)msgptr, s, p);
+       if (used < 0)
+               return;
+
+       /* This is ugly and costs +60 bytes compared to multiple
+        * fprintf's, but is guaranteed to do a single write.
+        * This is needed for e.g. httpd logging, when multiple
+        * children can produce log messages simultaneously. */
+
+       strerr_len = strerr ? strlen(strerr) : 0;
+       msgeol_len = strlen(msg_eol);
+       /* +3 is for ": " before strerr and for terminating NUL */
+       msgv = xrealloc(msgv, used + strerr_len + msgeol_len + 3);
+       if (strerr) {
+               msgc[used++] = ':';
+               msgc[used++] = ' ';
+               strcpy(msgc + used, strerr);
+               used += strerr_len;
+       }
+       strcpy(msgc + used, msg_eol);
+       used += msgeol_len;
+
+       if (logmode & LOGMODE_STDIO) {
+               iov[0].iov_base = (char*)applet_name;
+               iov[0].iov_len = strlen(applet_name);
+               iov[1].iov_base = (char*)": ";
+               iov[1].iov_len = 2;
+               /*iov[2].iov_base = msgc;*/
+               /*iov[2].iov_len = used;*/
+               fflush(stdout);
+               writev(2, iov, 3);
+       }
+       if (logmode & LOGMODE_SYSLOG) {
+               syslog(LOG_ERR, "%s", msgc);
+       }
+       free(msgc);
+}
+#endif
diff --git a/libbb/vfork_daemon_rexec.c b/libbb/vfork_daemon_rexec.c
new file mode 100644 (file)
index 0000000..f64239a
--- /dev/null
@@ -0,0 +1,330 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Rexec program for system have fork() as vfork() with foreground option
+ *
+ * Copyright (C) Vladimir N. Oleynik <dzo@simtreas.ru>
+ * Copyright (C) 2003 Russ Dill <Russ.Dill@asu.edu>
+ *
+ * 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 <andersee@debian.org>
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+#include <paths.h>
+#include "busybox.h" /* uses applet tables */
+
+/* This does a fork/exec in one call, using vfork().  Returns PID of new child,
+ * -1 for failure.  Runs argv[0], searching path if that has no / in it. */
+pid_t FAST_FUNC spawn(char **argv)
+{
+       /* Compiler should not optimize stores here */
+       volatile int failed;
+       pid_t pid;
+
+// Ain't it a good place to fflush(NULL)?
+
+       /* Be nice to nommu machines. */
+       failed = 0;
+       pid = vfork();
+       if (pid < 0) /* error */
+               return pid;
+       if (!pid) { /* child */
+               /* This macro is ok - it doesn't do NOEXEC/NOFORK tricks */
+               BB_EXECVP(argv[0], argv);
+
+               /* We are (maybe) 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 = errno;
+               _exit(111);
+       }
+       /* parent */
+       /* Unfortunately, this is not reliable: according to standards
+        * vfork() can be equivalent to fork() and we won't see value
+        * of 'failed'.
+        * Interested party can wait on pid and learn exit code.
+        * If 111 - then it (most probably) failed to exec */
+       if (failed) {
+               errno = failed;
+               return -1;
+       }
+       return pid;
+}
+
+/* Die with an error message if we can't spawn a child process. */
+pid_t FAST_FUNC xspawn(char **argv)
+{
+       pid_t pid = spawn(argv);
+       if (pid < 0)
+               bb_simple_perror_msg_and_die(*argv);
+       return pid;
+}
+
+pid_t FAST_FUNC safe_waitpid(pid_t pid, int *wstat, int options)
+{
+       pid_t r;
+
+       do
+               r = waitpid(pid, wstat, options);
+       while ((r == -1) && (errno == EINTR));
+       return r;
+}
+
+pid_t FAST_FUNC wait_any_nohang(int *wstat)
+{
+       return safe_waitpid(-1, wstat, WNOHANG);
+}
+
+// Wait for the specified child PID to exit, returning child's error return.
+int FAST_FUNC wait4pid(pid_t pid)
+{
+       int status;
+
+       if (pid <= 0) {
+               /*errno = ECHILD; -- wrong. */
+               /* we expect errno to be already set from failed [v]fork/exec */
+               return -1;
+       }
+       if (safe_waitpid(pid, &status, 0) == -1)
+               return -1;
+       if (WIFEXITED(status))
+               return WEXITSTATUS(status);
+       if (WIFSIGNALED(status))
+               return WTERMSIG(status) + 1000;
+       return 0;
+}
+
+#if ENABLE_FEATURE_PREFER_APPLETS
+void FAST_FUNC save_nofork_data(struct nofork_save_area *save)
+{
+       memcpy(&save->die_jmp, &die_jmp, sizeof(die_jmp));
+       save->applet_name = applet_name;
+       save->xfunc_error_retval = xfunc_error_retval;
+       save->option_mask32 = option_mask32;
+       save->die_sleep = die_sleep;
+       save->saved = 1;
+}
+
+void FAST_FUNC restore_nofork_data(struct nofork_save_area *save)
+{
+       memcpy(&die_jmp, &save->die_jmp, sizeof(die_jmp));
+       applet_name = save->applet_name;
+       xfunc_error_retval = save->xfunc_error_retval;
+       option_mask32 = save->option_mask32;
+       die_sleep = save->die_sleep;
+}
+
+int FAST_FUNC run_nofork_applet_prime(struct nofork_save_area *old, int applet_no, char **argv)
+{
+       int rc, argc;
+
+       applet_name = APPLET_NAME(applet_no);
+
+       xfunc_error_retval = EXIT_FAILURE;
+
+       /* Special flag for xfunc_die(). If xfunc will "die"
+        * in NOFORK applet, xfunc_die() sees negative
+        * die_sleep and longjmp here instead. */
+       die_sleep = -1;
+
+       /* In case getopt() or getopt32() was already called:
+        * reset the libc getopt() function, which keeps internal state.
+        *
+        * BSD-derived getopt() functions require that optind be set to 1 in
+        * order to reset getopt() state.  This used to be generally accepted
+        * way of resetting getopt().  However, glibc's getopt()
+        * has additional getopt() state beyond optind, and requires that
+        * optind be set to zero to reset its state.  So the unfortunate state of
+        * affairs is that BSD-derived versions of getopt() misbehave if
+        * optind is set to 0 in order to reset getopt(), and glibc's getopt()
+        * will core dump if optind is set 1 in order to reset getopt().
+        *
+        * More modern versions of BSD require that optreset be set to 1 in
+        * order to reset getopt().  Sigh.  Standards, anyone?
+        */
+#ifdef __GLIBC__
+       optind = 0;
+#else /* BSD style */
+       optind = 1;
+       /* optreset = 1; */
+#endif
+       /* optarg = NULL; opterr = 1; optopt = 63; - do we need this too? */
+       /* (values above are what they initialized to in glibc and uclibc) */
+       /* option_mask32 = 0; - not needed, no applet depends on it being 0 */
+
+       argc = 1;
+       while (argv[argc])
+               argc++;
+
+       rc = setjmp(die_jmp);
+       if (!rc) {
+               /* Some callers (xargs)
+                * need argv untouched because they free argv[i]! */
+               char *tmp_argv[argc+1];
+               memcpy(tmp_argv, argv, (argc+1) * sizeof(tmp_argv[0]));
+               /* Finally we can call NOFORK applet's main() */
+               rc = applet_main[applet_no](argc, tmp_argv);
+
+       /* The whole reason behind nofork_save_area is that <applet>_main
+        * may exit non-locally! For example, in hush Ctrl-Z tries
+        * (modulo bugs) to dynamically create a child (backgrounded task)
+        * if it detects that Ctrl-Z was pressed when a NOFORK was running.
+        * Testcase: interactive "rm -i".
+        * Don't fool yourself into thinking "and <applet>_main() returns
+        * quickly here" and removing "useless" nofork_save_area code. */
+
+       } else { /* xfunc died in NOFORK applet */
+               /* in case they meant to return 0... */
+               if (rc == -2222)
+                       rc = 0;
+       }
+
+       /* Restoring some globals */
+       restore_nofork_data(old);
+
+       /* Other globals can be simply reset to defaults */
+#ifdef __GLIBC__
+       optind = 0;
+#else /* BSD style */
+       optind = 1;
+#endif
+
+       return rc & 0xff; /* don't confuse people with "exitcodes" >255 */
+}
+
+int FAST_FUNC run_nofork_applet(int applet_no, char **argv)
+{
+       struct nofork_save_area old;
+
+       /* Saving globals */
+       save_nofork_data(&old);
+       return run_nofork_applet_prime(&old, applet_no, argv);
+}
+#endif /* FEATURE_PREFER_APPLETS */
+
+int FAST_FUNC spawn_and_wait(char **argv)
+{
+       int rc;
+#if ENABLE_FEATURE_PREFER_APPLETS
+       int a = find_applet_by_name(argv[0]);
+
+       if (a >= 0 && (APPLET_IS_NOFORK(a)
+#if BB_MMU
+                       || APPLET_IS_NOEXEC(a) /* NOEXEC trick needs fork() */
+#endif
+       )) {
+#if BB_MMU
+               if (APPLET_IS_NOFORK(a))
+#endif
+               {
+                       return run_nofork_applet(a, argv);
+               }
+#if BB_MMU
+               /* MMU only */
+               /* a->noexec is true */
+               rc = fork();
+               if (rc) /* parent or error */
+                       return wait4pid(rc);
+               /* child */
+               xfunc_error_retval = EXIT_FAILURE;
+               run_applet_no_and_exit(a, argv);
+#endif
+       }
+#endif /* FEATURE_PREFER_APPLETS */
+       rc = spawn(argv);
+       return wait4pid(rc);
+}
+
+#if !BB_MMU
+void FAST_FUNC re_exec(char **argv)
+{
+       /* high-order bit of first char in argv[0] is a hidden
+        * "we have (already) re-execed, don't do it again" flag */
+       argv[0][0] |= 0x80;
+       execv(bb_busybox_exec_path, argv);
+       bb_perror_msg_and_die("exec %s", bb_busybox_exec_path);
+}
+
+pid_t FAST_FUNC fork_or_rexec(char **argv)
+{
+       pid_t pid;
+       /* Maybe we are already re-execed and come here again? */
+       if (re_execed)
+               return 0; /* child */
+
+       pid = vfork();
+       if (pid < 0) /* wtf? */
+               bb_perror_msg_and_die("vfork");
+       if (pid) /* parent */
+               return pid;
+       /* child - re-exec ourself */
+       re_exec(argv);
+}
+#else
+/* Dance around (void)...*/
+#undef fork_or_rexec
+pid_t FAST_FUNC fork_or_rexec(void)
+{
+       pid_t pid;
+       pid = fork();
+       if (pid < 0) /* wtf? */
+               bb_perror_msg_and_die("fork");
+       return pid;
+}
+#define fork_or_rexec(argv) fork_or_rexec()
+#endif
+
+/* Due to a #define in libbb.h on MMU systems we actually have 1 argument -
+ * char **argv "vanishes" */
+void FAST_FUNC bb_daemonize_or_rexec(int flags, char **argv)
+{
+       int fd;
+
+       if (flags & DAEMON_CHDIR_ROOT)
+               xchdir("/");
+
+       if (flags & DAEMON_DEVNULL_STDIO) {
+               close(0);
+               close(1);
+               close(2);
+       }
+
+       fd = open(bb_dev_null, O_RDWR);
+       if (fd < 0) {
+               /* NB: we can be called as bb_sanitize_stdio() from init
+                * or mdev, and there /dev/null may legitimately not (yet) exist!
+                * Do not use xopen above, but obtain _ANY_ open descriptor,
+                * even bogus one as below. */
+               fd = xopen("/", O_RDONLY); /* don't believe this can fail */
+       }
+
+       while ((unsigned)fd < 2)
+               fd = dup(fd); /* have 0,1,2 open at least to /dev/null */
+
+       if (!(flags & DAEMON_ONLY_SANITIZE)) {
+               if (fork_or_rexec(argv))
+                       exit(EXIT_SUCCESS); /* parent */
+               /* if daemonizing, make sure we detach from stdio & ctty */
+               setsid();
+               dup2(fd, 0);
+               dup2(fd, 1);
+               dup2(fd, 2);
+       }
+       while (fd > 2) {
+               close(fd--);
+               if (!(flags & DAEMON_CLOSE_EXTRA_FDS))
+                       return;
+               /* else close everything after fd#2 */
+       }
+}
+
+void FAST_FUNC bb_sanitize_stdio(void)
+{
+       bb_daemonize_or_rexec(DAEMON_ONLY_SANITIZE, NULL);
+}
diff --git a/libbb/warn_ignoring_args.c b/libbb/warn_ignoring_args.c
new file mode 100644 (file)
index 0000000..65dea32
--- /dev/null
@@ -0,0 +1,17 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * warn_ignoring_args implementation for busybox
+ *
+ * Copyright (C) 2003  Manuel Novoa III  <mjn3@codepoet.org>
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.h"
+
+void FAST_FUNC 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 (file)
index 0000000..1cb871e
--- /dev/null
@@ -0,0 +1,40 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Utility routines.
+ *
+ * Copyright (C) 1999-2004 by Erik Andersen <andersen@codepoet.org>
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.h"
+
+FILE* FAST_FUNC fopen_or_warn(const char *path, const char *mode)
+{
+       FILE *fp = fopen(path, mode);
+       if (!fp) {
+               bb_simple_perror_msg(path);
+               //errno = 0; /* why? */
+       }
+       return fp;
+}
+
+FILE* FAST_FUNC fopen_for_read(const char *path)
+{
+       return fopen(path, "r");
+}
+
+FILE* FAST_FUNC xfopen_for_read(const char *path)
+{
+       return xfopen(path, "r");
+}
+
+FILE* FAST_FUNC fopen_for_write(const char *path)
+{
+       return fopen(path, "w");
+}
+
+FILE* FAST_FUNC xfopen_for_write(const char *path)
+{
+       return xfopen(path, "w");
+}
diff --git a/libbb/wfopen_input.c b/libbb/wfopen_input.c
new file mode 100644 (file)
index 0000000..46ff7a6
--- /dev/null
@@ -0,0 +1,48 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * wfopen_input implementation for busybox
+ *
+ * Copyright (C) 2003  Manuel Novoa III  <mjn3@codepoet.org>
+ *
+ * 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.
+ */
+
+#include "libbb.h"
+
+FILE* FAST_FUNC fopen_or_warn_stdin(const char *filename)
+{
+       FILE *fp = stdin;
+
+       if (filename != bb_msg_standard_input
+        && NOT_LONE_DASH(filename)
+       ) {
+               fp = fopen_or_warn(filename, "r");
+       }
+       return fp;
+}
+
+FILE* FAST_FUNC xfopen_stdin(const char *filename)
+{
+       FILE *fp = fopen_or_warn_stdin(filename);
+       if (fp)
+               return fp;
+       xfunc_die();    /* We already output an error message. */
+}
+
+int FAST_FUNC open_or_warn_stdin(const char *filename)
+{
+       int fd = STDIN_FILENO;
+
+       if (filename != bb_msg_standard_input
+        && NOT_LONE_DASH(filename)
+       ) {
+               fd = open_or_warn(filename, O_RDONLY);
+       }
+
+       return fd;
+}
diff --git a/libbb/write.c b/libbb/write.c
new file mode 100644 (file)
index 0000000..116e4d1
--- /dev/null
@@ -0,0 +1,19 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Utility routines.
+ *
+ * Copyright (C) 2008 Bernhard Reutner-Fischer
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.h"
+
+/* Open file and write string str to it, close file.
+ * Die on any open or write error.  */
+void FAST_FUNC xopen_xwrite_close(const char* file, const char* str)
+{
+       int fd = xopen(file, O_WRONLY);
+       xwrite_str(fd, str);
+       close(fd);
+}
diff --git a/libbb/xatonum.c b/libbb/xatonum.c
new file mode 100644 (file)
index 0000000..3cdf634
--- /dev/null
@@ -0,0 +1,70 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * ascii-to-numbers implementations for busybox
+ *
+ * Copyright (C) 2003  Manuel Novoa III  <mjn3@codepoet.org>
+ *
+ * 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
+static ALWAYS_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 FAST_FUNC xatoi_u(const char *numstr)
+{
+       return xatou_range(numstr, 0, INT_MAX);
+}
+
+uint16_t FAST_FUNC 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 (file)
index 0000000..5e0bb59
--- /dev/null
@@ -0,0 +1,191 @@
+/*
+ *
+ * Licensed under GPLv2, see file LICENSE in this tarball for details.
+ */
+/*
+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 FAST_FUNC 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. Make sure we get the
+        * actual isspace function rather than a macro implementaion. */
+       if (*numstr == '-' || *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->mult) {
+                       if (strcmp(suffixes->suffix, e) == 0) {
+                               if (XSTR_UTYPE_MAX / suffixes->mult < r)
+                                       goto range; /* overflow! */
+                               r *= suffixes->mult;
+                               goto chk_range;
+                       }
+                       ++suffixes;
+               }
+       }
+
+       /* Note: trailing space is an error.
+          It would be easy enough to allow though if desired. */
+       if (*e)
+               goto inval;
+ chk_range:
+       /* 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 FAST_FUNC xstrtou(_range)(const char *numstr, int base,
+               unsigned type lower,
+               unsigned type upper)
+{
+       return xstrtou(_range_sfx)(numstr, base, lower, upper, NULL);
+}
+
+unsigned type FAST_FUNC 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 FAST_FUNC xstrtou()(const char *numstr, int base)
+{
+       return xstrtou(_range_sfx)(numstr, base, 0, XSTR_UTYPE_MAX, NULL);
+}
+
+unsigned type FAST_FUNC 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 FAST_FUNC xatou(_range)(const char *numstr,
+               unsigned type lower,
+               unsigned type upper)
+{
+       return xstrtou(_range_sfx)(numstr, 10, lower, upper, NULL);
+}
+
+unsigned type FAST_FUNC xatou(_sfx)(const char *numstr,
+               const struct suffix_mult *suffixes)
+{
+       return xstrtou(_range_sfx)(numstr, 10, 0, XSTR_UTYPE_MAX, suffixes);
+}
+
+unsigned type FAST_FUNC xatou()(const char *numstr)
+{
+       return xatou(_sfx)(numstr, NULL);
+}
+
+/* Signed ones */
+
+type FAST_FUNC 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;
+
+       /* NB: if you'll decide to disallow '+':
+        * at least renice applet needs to allow it */
+       if (p[0] == '+' || p[0] == '-') {
+               ++p;
+               if (p[0] == '-')
+                       ++u; /* = <type>_MIN (01111... + 1 == 10000...) */
+       }
+
+       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 FAST_FUNC xstrto(_range)(const char *numstr, int base, type lower, type upper)
+{
+       return xstrto(_range_sfx)(numstr, base, lower, upper, NULL);
+}
+
+type FAST_FUNC 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 FAST_FUNC xato(_range)(const char *numstr, type lower, type upper)
+{
+       return xstrto(_range_sfx)(numstr, 10, lower, upper, NULL);
+}
+
+type FAST_FUNC xato(_sfx)(const char *numstr, const struct suffix_mult *suffixes)
+{
+       return xstrto(_range_sfx)(numstr, 10, XSTR_TYPE_MIN, XSTR_TYPE_MAX, suffixes);
+}
+
+type FAST_FUNC 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 (file)
index 0000000..f5d7983
--- /dev/null
@@ -0,0 +1,423 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Utility routines.
+ *
+ * Connect to host at port using address resolution from getaddrinfo
+ *
+ * Licensed under GPLv2, see file LICENSE in this tarball for details.
+ */
+
+#include <netinet/in.h>
+#include <net/if.h>
+#include "libbb.h"
+
+void FAST_FUNC setsockopt_reuseaddr(int fd)
+{
+       setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &const_int_1, sizeof(const_int_1));
+}
+int FAST_FUNC setsockopt_broadcast(int fd)
+{
+       return setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &const_int_1, sizeof(const_int_1));
+}
+int FAST_FUNC setsockopt_bindtodevice(int fd, const char *iface)
+{
+       int r;
+       struct ifreq ifr;
+       strncpy_IFNAMSIZ(ifr.ifr_name, iface);
+       /* NB: passing (iface, strlen(iface) + 1) does not work!
+        * (maybe it works on _some_ kernels, but not on 2.6.26)
+        * Actually, ifr_name is at offset 0, and in practice
+        * just giving char[IFNAMSIZ] instead of struct ifreq works too.
+        * But just in case it's not true on some obscure arch... */
+       r = setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, &ifr, sizeof(ifr));
+       if (r)
+               bb_perror_msg("can't bind to interface %s", iface);
+       return r;
+}
+
+len_and_sockaddr* FAST_FUNC get_sock_lsa(int fd)
+{
+       len_and_sockaddr *lsa;
+       socklen_t len = 0;
+
+       /* Can be optimized to do only one getsockname() */
+       if (getsockname(fd, NULL, &len) != 0)
+               return NULL;
+       lsa = xzalloc(LSA_LEN_SIZE + len);
+       lsa->len = len;
+       getsockname(fd, &lsa->u.sa, &lsa->len);
+       return lsa;
+}
+
+void FAST_FUNC 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("%s (%s)",
+                               "cannot connect to remote host",
+                               inet_ntoa(((struct sockaddr_in *)s_addr)->sin_addr));
+               bb_perror_msg_and_die("cannot connect to remote host");
+       }
+}
+
+/* Return 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 FAST_FUNC bb_lookup_port(const char *port, const char *protocol, unsigned default_port)
+{
+       unsigned port_nr = default_port;
+       if (port) {
+               int old_errno;
+
+               /* 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;
+               port_nr = bb_strtou(port, NULL, 10);
+               if (errno || port_nr > 65535) {
+                       struct servent *tserv = getservbyname(port, protocol);
+                       port_nr = default_port;
+                       if (tserv)
+                               port_nr = ntohs(tserv->s_port);
+               }
+               errno = old_errno;
+       }
+       return (uint16_t)port_nr;
+}
+
+
+/* "Old" networking API - only IPv4 */
+
+/*
+void FAST_FUNC 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);
+}
+
+
+int FAST_FUNC 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;
+}
+*/
+
+/* "New" networking API */
+
+
+int FAST_FUNC get_nport(const struct sockaddr *sa)
+{
+#if ENABLE_FEATURE_IPV6
+       if (sa->sa_family == AF_INET6) {
+               return ((struct sockaddr_in6*)sa)->sin6_port;
+       }
+#endif
+       if (sa->sa_family == AF_INET) {
+               return ((struct sockaddr_in*)sa)->sin_port;
+       }
+       /* What? UNIX socket? IPX?? :) */
+       return -1;
+}
+
+void FAST_FUNC set_nport(len_and_sockaddr *lsa, unsigned port)
+{
+#if ENABLE_FEATURE_IPV6
+       if (lsa->u.sa.sa_family == AF_INET6) {
+               lsa->u.sin6.sin6_port = port;
+               return;
+       }
+#endif
+       if (lsa->u.sa.sa_family == AF_INET) {
+               lsa->u.sin.sin_port = port;
+               return;
+       }
+       /* What? UNIX socket? IPX?? :) */
+}
+
+/* We hijack this constant to mean something else */
+/* It doesn't hurt because we will remove this bit anyway */
+#define DIE_ON_ERROR AI_CANONNAME
+
+/* host: "1.2.3.4[:port]", "www.google.com[:port]"
+ * port: if neither of above specifies port # */
+static len_and_sockaddr* str2sockaddr(
+               const char *host, int port,
+USE_FEATURE_IPV6(sa_family_t af,)
+               int ai_flags)
+{
+       int rc;
+       len_and_sockaddr *r = NULL;
+       struct addrinfo *result = NULL;
+       struct addrinfo *used_res;
+       const char *org_host = host; /* only for error msg */
+       const char *cp;
+       struct addrinfo hint;
+
+       /* Ugly parsing of host:addr */
+       if (ENABLE_FEATURE_IPV6 && host[0] == '[') {
+               /* Even uglier parsing of [xx]:nn */
+               host++;
+               cp = strchr(host, ']');
+               if (!cp || (cp[1] != ':' && cp[1] != '\0')) {
+                       /* Malformed: must be [xx]:nn or [xx] */
+                       bb_error_msg("bad address '%s'", org_host);
+                       if (ai_flags & DIE_ON_ERROR)
+                               xfunc_die();
+                       return NULL;
+               }
+       } else {
+               cp = strrchr(host, ':');
+               if (ENABLE_FEATURE_IPV6 && cp && strchr(host, ':') != cp) {
+                       /* There is more than one ':' (e.g. "::1") */
+                       cp = NULL; /* it's not a port spec */
+               }
+       }
+       if (cp) { /* points to ":" or "]:" */
+               int sz = cp - host + 1;
+               host = safe_strncpy(alloca(sz), host, sz);
+               if (ENABLE_FEATURE_IPV6 && *cp != ':') {
+                       cp++; /* skip ']' */
+                       if (*cp == '\0') /* [xx] without port */
+                               goto skip;
+               }
+               cp++; /* skip ':' */
+               port = bb_strtou(cp, NULL, 10);
+               if (errno || (unsigned)port > 0xffff) {
+                       bb_error_msg("bad port spec '%s'", org_host);
+                       if (ai_flags & DIE_ON_ERROR)
+                               xfunc_die();
+                       return NULL;
+               }
+ skip: ;
+       }
+
+       memset(&hint, 0 , sizeof(hint));
+#if !ENABLE_FEATURE_IPV6
+       hint.ai_family = AF_INET; /* do not try to find IPv6 */
+#else
+       hint.ai_family = af;
+#endif
+       /* 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_flags & ~DIE_ON_ERROR;
+       rc = getaddrinfo(host, NULL, &hint, &result);
+       if (rc || !result) {
+               bb_error_msg("bad address '%s'", org_host);
+               if (ai_flags & DIE_ON_ERROR)
+                       xfunc_die();
+               goto ret;
+       }
+       used_res = result;
+#if ENABLE_FEATURE_PREFER_IPV4_ADDRESS
+       while (1) {
+               if (used_res->ai_family == AF_INET)
+                       break;
+               used_res = used_res->ai_next;
+               if (!used_res) {
+                       used_res = result;
+                       break;
+               }
+       }
+#endif
+       r = xmalloc(offsetof(len_and_sockaddr, u.sa) + used_res->ai_addrlen);
+       r->len = used_res->ai_addrlen;
+       memcpy(&r->u.sa, used_res->ai_addr, used_res->ai_addrlen);
+       set_nport(r, htons(port));
+ ret:
+       freeaddrinfo(result);
+       return r;
+}
+#if !ENABLE_FEATURE_IPV6
+#define str2sockaddr(host, port, af, ai_flags) str2sockaddr(host, port, ai_flags)
+#endif
+
+#if ENABLE_FEATURE_IPV6
+len_and_sockaddr* FAST_FUNC host_and_af2sockaddr(const char *host, int port, sa_family_t af)
+{
+       return str2sockaddr(host, port, af, 0);
+}
+
+len_and_sockaddr* FAST_FUNC xhost_and_af2sockaddr(const char *host, int port, sa_family_t af)
+{
+       return str2sockaddr(host, port, af, DIE_ON_ERROR);
+}
+#endif
+
+len_and_sockaddr* FAST_FUNC host2sockaddr(const char *host, int port)
+{
+       return str2sockaddr(host, port, AF_UNSPEC, 0);
+}
+
+len_and_sockaddr* FAST_FUNC xhost2sockaddr(const char *host, int port)
+{
+       return str2sockaddr(host, port, AF_UNSPEC, DIE_ON_ERROR);
+}
+
+len_and_sockaddr* FAST_FUNC xdotted2sockaddr(const char *host, int port)
+{
+       return str2sockaddr(host, port, AF_UNSPEC, AI_NUMERICHOST | DIE_ON_ERROR);
+}
+
+#undef xsocket_type
+int FAST_FUNC xsocket_type(len_and_sockaddr **lsap, USE_FEATURE_IPV6(int family,) int sock_type)
+{
+       SKIP_FEATURE_IPV6(enum { family = AF_INET };)
+       len_and_sockaddr *lsa;
+       int fd;
+       int len;
+
+#if ENABLE_FEATURE_IPV6
+       if (family == AF_UNSPEC) {
+               fd = socket(AF_INET6, sock_type, 0);
+               if (fd >= 0) {
+                       family = AF_INET6;
+                       goto done;
+               }
+               family = AF_INET;
+       }
+#endif
+       fd = xsocket(family, sock_type, 0);
+       len = sizeof(struct sockaddr_in);
+#if ENABLE_FEATURE_IPV6
+       if (family == AF_INET6) {
+ done:
+               len = sizeof(struct sockaddr_in6);
+       }
+#endif
+       lsa = xzalloc(offsetof(len_and_sockaddr, u.sa) + len);
+       lsa->len = len;
+       lsa->u.sa.sa_family = family;
+       *lsap = lsa;
+       return fd;
+}
+
+int FAST_FUNC xsocket_stream(len_and_sockaddr **lsap)
+{
+       return xsocket_type(lsap, USE_FEATURE_IPV6(AF_UNSPEC,) SOCK_STREAM);
+}
+
+static int create_and_bind_or_die(const char *bindaddr, int port, int sock_type)
+{
+       int fd;
+       len_and_sockaddr *lsa;
+
+       if (bindaddr && bindaddr[0]) {
+               lsa = xdotted2sockaddr(bindaddr, port);
+               /* user specified bind addr dictates family */
+               fd = xsocket(lsa->u.sa.sa_family, sock_type, 0);
+       } else {
+               fd = xsocket_type(&lsa, USE_FEATURE_IPV6(AF_UNSPEC,) sock_type);
+               set_nport(lsa, htons(port));
+       }
+       setsockopt_reuseaddr(fd);
+       xbind(fd, &lsa->u.sa, lsa->len);
+       free(lsa);
+       return fd;
+}
+
+int FAST_FUNC create_and_bind_stream_or_die(const char *bindaddr, int port)
+{
+       return create_and_bind_or_die(bindaddr, port, SOCK_STREAM);
+}
+
+int FAST_FUNC create_and_bind_dgram_or_die(const char *bindaddr, int port)
+{
+       return create_and_bind_or_die(bindaddr, port, SOCK_DGRAM);
+}
+
+
+int FAST_FUNC create_and_connect_stream_or_die(const char *peer, int port)
+{
+       int fd;
+       len_and_sockaddr *lsa;
+
+       lsa = xhost2sockaddr(peer, port);
+       fd = xsocket(lsa->u.sa.sa_family, SOCK_STREAM, 0);
+       setsockopt_reuseaddr(fd);
+       xconnect(fd, &lsa->u.sa, lsa->len);
+       free(lsa);
+       return fd;
+}
+
+int FAST_FUNC xconnect_stream(const len_and_sockaddr *lsa)
+{
+       int fd = xsocket(lsa->u.sa.sa_family, SOCK_STREAM, 0);
+       xconnect(fd, &lsa->u.sa, lsa->len);
+       return fd;
+}
+
+/* We hijack this constant to mean something else */
+/* It doesn't hurt because we will add this bit anyway */
+#define IGNORE_PORT NI_NUMERICSERV
+static char* FAST_FUNC sockaddr2str(const struct sockaddr *sa, int flags)
+{
+       char host[128];
+       char serv[16];
+       int rc;
+       socklen_t salen;
+
+       salen = LSA_SIZEOF_SA;
+#if ENABLE_FEATURE_IPV6
+       if (sa->sa_family == AF_INET)
+               salen = sizeof(struct sockaddr_in);
+       if (sa->sa_family == AF_INET6)
+               salen = sizeof(struct sockaddr_in6);
+#endif
+       rc = getnameinfo(sa, salen,
+                       host, sizeof(host),
+       /* can do ((flags & IGNORE_PORT) ? NULL : serv) but why bother? */
+                       serv, sizeof(serv),
+                       /* do not resolve port# into service _name_ */
+                       flags | NI_NUMERICSERV
+       );
+       if (rc)
+               return NULL;
+       if (flags & IGNORE_PORT)
+               return xstrdup(host);
+#if ENABLE_FEATURE_IPV6
+       if (sa->sa_family == AF_INET6) {
+               if (strchr(host, ':')) /* heh, it's not a resolved hostname */
+                       return xasprintf("[%s]:%s", host, serv);
+               /*return xasprintf("%s:%s", host, serv);*/
+               /* - fall through instead */
+       }
+#endif
+       /* For now we don't support anything else, so it has to be INET */
+       /*if (sa->sa_family == AF_INET)*/
+               return xasprintf("%s:%s", host, serv);
+       /*return xstrdup(host);*/
+}
+
+char* FAST_FUNC xmalloc_sockaddr2host(const struct sockaddr *sa)
+{
+       return sockaddr2str(sa, 0);
+}
+
+char* FAST_FUNC xmalloc_sockaddr2host_noport(const struct sockaddr *sa)
+{
+       return sockaddr2str(sa, IGNORE_PORT);
+}
+
+char* FAST_FUNC xmalloc_sockaddr2hostonly_noport(const struct sockaddr *sa)
+{
+       return sockaddr2str(sa, NI_NAMEREQD | IGNORE_PORT);
+}
+char* FAST_FUNC xmalloc_sockaddr2dotted(const struct sockaddr *sa)
+{
+       return sockaddr2str(sa, NI_NUMERICHOST);
+}
+
+char* FAST_FUNC xmalloc_sockaddr2dotted_noport(const struct sockaddr *sa)
+{
+       return sockaddr2str(sa, NI_NUMERICHOST | IGNORE_PORT);
+}
diff --git a/libbb/xfunc_die.c b/libbb/xfunc_die.c
new file mode 100644 (file)
index 0000000..ba9fe93
--- /dev/null
@@ -0,0 +1,40 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Utility routines.
+ *
+ * Copyright (C) 2008 by Denys Vlasenko <vda.linux@googlemail.com>
+ *
+ * Licensed under GPLv2, see file LICENSE in this tarball for details.
+ */
+
+/* Keeping it separate allows to NOT suck in stdio for VERY small applets.
+ * Try building busybox with only "true" enabled... */
+
+#include "libbb.h"
+
+int die_sleep;
+#if ENABLE_FEATURE_PREFER_APPLETS || ENABLE_HUSH
+jmp_buf die_jmp;
+#endif
+
+void FAST_FUNC xfunc_die(void)
+{
+       if (die_sleep) {
+               if ((ENABLE_FEATURE_PREFER_APPLETS || ENABLE_HUSH)
+                && die_sleep < 0
+               ) {
+                       /* Special case. We arrive here if NOFORK applet
+                        * calls xfunc, which then decides to die.
+                        * We don't die, but jump instead back to caller.
+                        * NOFORK applets still cannot carelessly call xfuncs:
+                        * p = xmalloc(10);
+                        * q = xmalloc(10); // BUG! if this dies, we leak p!
+                        */
+                       /* -2222 means "zero" (longjmp can't pass 0)
+                        * run_nofork_applet() catches -2222. */
+                       longjmp(die_jmp, xfunc_error_retval ? xfunc_error_retval : -2222);
+               }
+               sleep(die_sleep);
+       }
+       exit(xfunc_error_retval);
+}
diff --git a/libbb/xfuncs.c b/libbb/xfuncs.c
new file mode 100644 (file)
index 0000000..a86efc2
--- /dev/null
@@ -0,0 +1,315 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Utility routines.
+ *
+ * Copyright (C) 1999-2004 by Erik Andersen <andersen@codepoet.org>
+ * Copyright (C) 2006 Rob Landley
+ * Copyright (C) 2006 Denys Vlasenko
+ *
+ * Licensed under GPL version 2, see file LICENSE in this tarball for details.
+ */
+
+/* We need to have separate xfuncs.c and xfuncs_printf.c because
+ * with current linkers, even with section garbage collection,
+ * if *.o module references any of XXXprintf functions, you pull in
+ * entire printf machinery. Even if you do not use the function
+ * which uses XXXprintf.
+ *
+ * xfuncs.c contains functions (not necessarily xfuncs)
+ * which do not pull in printf, directly or indirectly.
+ * xfunc_printf.c contains those which do.
+ *
+ * TODO: move xmalloc() and xatonum() here.
+ */
+
+#include "libbb.h"
+
+/* Turn on nonblocking I/O on a fd */
+int FAST_FUNC ndelay_on(int fd)
+{
+       return fcntl(fd, F_SETFL, fcntl(fd,F_GETFL) | O_NONBLOCK);
+}
+
+int FAST_FUNC ndelay_off(int fd)
+{
+       return fcntl(fd, F_SETFL, fcntl(fd,F_GETFL) & ~O_NONBLOCK);
+}
+
+int FAST_FUNC close_on_exec_on(int fd)
+{
+       return fcntl(fd, F_SETFD, FD_CLOEXEC);
+}
+
+char* FAST_FUNC strncpy_IFNAMSIZ(char *dst, const char *src)
+{
+#ifndef IFNAMSIZ
+       enum { IFNAMSIZ = 16 };
+#endif
+       return strncpy(dst, src, IFNAMSIZ);
+}
+
+/* Convert unsigned long long value into compact 4-char
+ * representation. Examples: "1234", "1.2k", " 27M", "123T"
+ * String is not terminated (buf[4] is untouched) */
+void FAST_FUNC smart_ulltoa4(unsigned long long ul, char buf[5], const char *scale)
+{
+       const char *fmt;
+       char c;
+       unsigned v, u, idx = 0;
+
+       if (ul > 9999) { // do not scale if 9999 or less
+               ul *= 10;
+               do {
+                       ul /= 1024;
+                       idx++;
+               } while (ul >= 10000);
+       }
+       v = ul; // ullong divisions are expensive, avoid them
+
+       fmt = " 123456789";
+       u = v / 10;
+       v = v % 10;
+       if (!idx) {
+               // 9999 or less: use "1234" format
+               // u is value/10, v is last digit
+               c = buf[0] = " 123456789"[u/100];
+               if (c != ' ') fmt = "0123456789";
+               c = buf[1] = fmt[u/10%10];
+               if (c != ' ') fmt = "0123456789";
+               buf[2] = fmt[u%10];
+               buf[3] = "0123456789"[v];
+       } else {
+               // u is value, v is 1/10ths (allows for 9.2M format)
+               if (u >= 10) {
+                       // value is >= 10: use "123M', " 12M" formats
+                       c = buf[0] = " 123456789"[u/100];
+                       if (c != ' ') fmt = "0123456789";
+                       v = u % 10;
+                       u = u / 10;
+                       buf[1] = fmt[u%10];
+               } else {
+                       // value is < 10: use "9.2M" format
+                       buf[0] = "0123456789"[u];
+                       buf[1] = '.';
+               }
+               buf[2] = "0123456789"[v];
+               buf[3] = scale[idx]; /* typically scale = " kmgt..." */
+       }
+}
+
+/* Convert unsigned long long value into compact 5-char representation.
+ * String is not terminated (buf[5] is untouched) */
+void FAST_FUNC smart_ulltoa5(unsigned long long ul, char buf[6], const char *scale)
+{
+       const char *fmt;
+       char c;
+       unsigned v, u, idx = 0;
+
+       if (ul > 99999) { // do not scale if 99999 or less
+               ul *= 10;
+               do {
+                       ul /= 1024;
+                       idx++;
+               } while (ul >= 100000);
+       }
+       v = ul; // ullong divisions are expensive, avoid them
+
+       fmt = " 123456789";
+       u = v / 10;
+       v = v % 10;
+       if (!idx) {
+               // 99999 or less: use "12345" format
+               // u is value/10, v is last digit
+               c = buf[0] = " 123456789"[u/1000];
+               if (c != ' ') fmt = "0123456789";
+               c = buf[1] = fmt[u/100%10];
+               if (c != ' ') fmt = "0123456789";
+               c = buf[2] = fmt[u/10%10];
+               if (c != ' ') fmt = "0123456789";
+               buf[3] = fmt[u%10];
+               buf[4] = "0123456789"[v];
+       } else {
+               // value has been scaled into 0..9999.9 range
+               // u is value, v is 1/10ths (allows for 92.1M format)
+               if (u >= 100) {
+                       // value is >= 100: use "1234M', " 123M" formats
+                       c = buf[0] = " 123456789"[u/1000];
+                       if (c != ' ') fmt = "0123456789";
+                       c = buf[1] = fmt[u/100%10];
+                       if (c != ' ') fmt = "0123456789";
+                       v = u % 10;
+                       u = u / 10;
+                       buf[2] = fmt[u%10];
+               } else {
+                       // value is < 100: use "92.1M" format
+                       c = buf[0] = " 123456789"[u/10];
+                       if (c != ' ') fmt = "0123456789";
+                       buf[1] = fmt[u%10];
+                       buf[2] = '.';
+               }
+               buf[3] = "0123456789"[v];
+               buf[4] = scale[idx]; /* typically scale = " kmgt..." */
+       }
+}
+
+
+// Convert unsigned integer to ascii, writing into supplied buffer.
+// A truncated result contains the first few digits of the result ala strncpy.
+// Returns a pointer past last generated digit, does _not_ store NUL.
+void BUG_sizeof_unsigned_not_4(void);
+char* FAST_FUNC 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;
+                       }
+               }
+       }
+       return buf;
+}
+
+/* Convert signed integer to ascii, like utoa_to_buf() */
+char* FAST_FUNC itoa_to_buf(int n, char *buf, unsigned buflen)
+{
+       if (buflen && n < 0) {
+               n = -n;
+               *buf++ = '-';
+               buflen--;
+       }
+       return 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.
+// It so happens that sizeof(int) * 3 is enough for 32+ bits.
+// (sizeof(int) * 3 + 2 is correct for any width, even 8-bit)
+
+static char local_buf[sizeof(int) * 3];
+
+// Convert unsigned integer to ascii using a static buffer (returned).
+char* FAST_FUNC utoa(unsigned n)
+{
+       *(utoa_to_buf(n, local_buf, sizeof(local_buf))) = '\0';
+
+       return local_buf;
+}
+
+/* Convert signed integer to ascii using a static buffer (returned). */
+char* FAST_FUNC itoa(int n)
+{
+       *(itoa_to_buf(n, local_buf, sizeof(local_buf))) = '\0';
+
+       return local_buf;
+}
+
+/* Emit a string of hex representation of bytes */
+char* FAST_FUNC bin2hex(char *p, const char *cp, int count)
+{
+       while (count) {
+               unsigned char c = *cp++;
+               /* put lowercase hex digits */
+               *p++ = 0x20 | bb_hexdigits_upcase[c >> 4];
+               *p++ = 0x20 | bb_hexdigits_upcase[c & 0xf];
+               count--;
+       }
+       return p;
+}
+
+/* Return how long the file at fd is, if there's any way to determine it. */
+#ifdef UNUSED
+off_t FAST_FUNC 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;
+}
+#endif
+
+char* FAST_FUNC xmalloc_ttyname(int fd)
+{
+       char *buf = xzalloc(128);
+       int r = ttyname_r(fd, buf, 127);
+       if (r) {
+               free(buf);
+               buf = NULL;
+       }
+       return buf;
+}
+
+/* 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 FAST_FUNC get_terminal_width_height(int fd, unsigned *width, unsigned *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;
+}
+
+int FAST_FUNC tcsetattr_stdin_TCSANOW(const struct termios *tp)
+{
+       return tcsetattr(STDIN_FILENO, TCSANOW, tp);
+}
diff --git a/libbb/xfuncs_printf.c b/libbb/xfuncs_printf.c
new file mode 100644 (file)
index 0000000..6d0fa6e
--- /dev/null
@@ -0,0 +1,548 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Utility routines.
+ *
+ * Copyright (C) 1999-2004 by Erik Andersen <andersen@codepoet.org>
+ * Copyright (C) 2006 Rob Landley
+ * Copyright (C) 2006 Denys Vlasenko
+ *
+ * Licensed under GPL version 2, see file LICENSE in this tarball for details.
+ */
+
+/* We need to have separate xfuncs.c and xfuncs_printf.c because
+ * with current linkers, even with section garbage collection,
+ * if *.o module references any of XXXprintf functions, you pull in
+ * entire printf machinery. Even if you do not use the function
+ * which uses XXXprintf.
+ *
+ * xfuncs.c contains functions (not necessarily xfuncs)
+ * which do not pull in printf, directly or indirectly.
+ * xfunc_printf.c contains those which do.
+ */
+
+#include "libbb.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.
+ */
+// Warn if we can't allocate size bytes of memory.
+void* FAST_FUNC malloc_or_warn(size_t size)
+{
+       void *ptr = malloc(size);
+       if (ptr == NULL && size != 0)
+               bb_error_msg(bb_msg_memory_exhausted);
+       return ptr;
+}
+
+// Die if we can't allocate size bytes of memory.
+void* FAST_FUNC 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* FAST_FUNC 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* FAST_FUNC 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* FAST_FUNC 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* FAST_FUNC 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 -= 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* FAST_FUNC xfopen(const char *path, const char *mode)
+{
+       FILE *fp = fopen(path, mode);
+       if (fp == NULL)
+               bb_perror_msg_and_die("can't open '%s'", path);
+       return fp;
+}
+
+// Die if we can't open a file and return a fd.
+int FAST_FUNC xopen3(const char *pathname, int flags, int mode)
+{
+       int ret;
+
+       ret = open(pathname, flags, mode);
+       if (ret < 0) {
+               bb_perror_msg_and_die("can't open '%s'", pathname);
+       }
+       return ret;
+}
+
+// Die if we can't open an existing file and return a fd.
+int FAST_FUNC xopen(const char *pathname, int flags)
+{
+       return xopen3(pathname, flags, 0666);
+}
+
+// Warn if we can't open a file and return a fd.
+int FAST_FUNC open3_or_warn(const char *pathname, int flags, int mode)
+{
+       int ret;
+
+       ret = open(pathname, flags, mode);
+       if (ret < 0) {
+               bb_perror_msg("can't open '%s'", pathname);
+       }
+       return ret;
+}
+
+// Warn if we can't open a file and return a fd.
+int FAST_FUNC open_or_warn(const char *pathname, int flags)
+{
+       return open3_or_warn(pathname, flags, 0666);
+}
+
+void FAST_FUNC xunlink(const char *pathname)
+{
+       if (unlink(pathname))
+               bb_perror_msg_and_die("can't remove file '%s'", pathname);
+}
+
+void FAST_FUNC xrename(const char *oldpath, const char *newpath)
+{
+       if (rename(oldpath, newpath))
+               bb_perror_msg_and_die("can't move '%s' to '%s'", oldpath, newpath);
+}
+
+int FAST_FUNC rename_or_warn(const char *oldpath, const char *newpath)
+{
+       int n = rename(oldpath, newpath);
+       if (n)
+               bb_perror_msg("can't move '%s' to '%s'", oldpath, newpath);
+       return n;
+}
+
+void FAST_FUNC xpipe(int filedes[2])
+{
+       if (pipe(filedes))
+               bb_perror_msg_and_die("can't create pipe");
+}
+
+void FAST_FUNC xdup2(int from, int to)
+{
+       if (dup2(from, to) != to)
+               bb_perror_msg_and_die("can't duplicate file descriptor");
+}
+
+// "Renumber" opened fd
+void FAST_FUNC xmove_fd(int from, int to)
+{
+       if (from == to)
+               return;
+       xdup2(from, to);
+       close(from);
+}
+
+// Die with an error message if we can't write the entire buffer.
+void FAST_FUNC xwrite(int fd, const void *buf, size_t count)
+{
+       if (count) {
+               ssize_t size = full_write(fd, buf, count);
+               if ((size_t)size != count)
+                       bb_error_msg_and_die("short write");
+       }
+}
+void FAST_FUNC xwrite_str(int fd, const char *str)
+{
+       xwrite(fd, str, strlen(str));
+}
+
+// Die with an error message if we can't lseek to the right spot.
+off_t FAST_FUNC 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 FAST_FUNC die_if_ferror(FILE *fp, const char *fn)
+{
+       if (ferror(fp)) {
+               /* ferror doesn't set useful errno */
+               bb_error_msg_and_die("%s: I/O error", fn);
+       }
+}
+
+// Die with an error message if stdout has ferror set.
+void FAST_FUNC 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 FAST_FUNC xfflush_stdout(void)
+{
+       if (fflush(stdout)) {
+               bb_perror_msg_and_die(bb_msg_standard_output);
+       }
+}
+
+
+int FAST_FUNC bb_putchar(int ch)
+{
+       /* time.c needs putc(ch, stdout), not putchar(ch).
+        * it does "stdout = stderr;", but then glibc's putchar()
+        * doesn't work as expected. bad glibc, bad */
+       return putc(ch, stdout);
+}
+
+/* Die with an error message if we can't copy an entire FILE* to stdout,
+ * then close that file. */
+void FAST_FUNC xprint_and_close_file(FILE *file)
+{
+       fflush(stdout);
+       // copyfd outputs error messages for us.
+       if (bb_copyfd_eof(fileno(file), 1) == -1)
+               xfunc_die();
+
+       fclose(file);
+}
+
+// Die with an error message if we can't malloc() enough space and do an
+// sprintf() into that space.
+char* FAST_FUNC 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;
+}
+
+#if 0 /* If we will ever meet a libc which hasn't [f]dprintf... */
+int FAST_FUNC fdprintf(int fd, 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) + 1;
+       va_end(p);
+       string_ptr = malloc(r);
+       if (string_ptr) {
+               va_start(p, format);
+               r = vsnprintf(string_ptr, r, format, p);
+               va_end(p);
+       }
+#endif
+
+       if (r >= 0) {
+               full_write(fd, string_ptr, r);
+               free(string_ptr);
+       }
+       return r;
+}
+#endif
+
+void FAST_FUNC xsetenv(const char *key, const char *value)
+{
+       if (setenv(key, value, 1))
+               bb_error_msg_and_die(bb_msg_memory_exhausted);
+}
+
+/* Handles "VAR=VAL" strings, even those which are part of environ
+ * _right now_
+ */
+void FAST_FUNC bb_unsetenv(const char *var)
+{
+       char *tp = strchr(var, '=');
+
+       if (!tp) {
+               unsetenv(var);
+               return;
+       }
+
+       /* In case var was putenv'ed, we can't replace '='
+        * with NUL and unsetenv(var) - it won't work,
+        * env is modified by the replacement, unsetenv
+        * sees "VAR" instead of "VAR=VAL" and does not remove it!
+        * horror :( */
+       tp = xstrndup(var, tp - var);
+       unsetenv(tp);
+       free(tp);
+}
+
+
+// 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 FAST_FUNC xsetgid(gid_t gid)
+{
+       if (setgid(gid)) bb_perror_msg_and_die("setgid");
+}
+
+// Die with an error message if we can't set uid.  (See xsetgid() for why.)
+void FAST_FUNC xsetuid(uid_t uid)
+{
+       if (setuid(uid)) bb_perror_msg_and_die("setuid");
+}
+
+// Die if we can't chdir to a new path.
+void FAST_FUNC xchdir(const char *path)
+{
+       if (chdir(path))
+               bb_perror_msg_and_die("chdir(%s)", path);
+}
+
+void FAST_FUNC xchroot(const char *path)
+{
+       if (chroot(path))
+               bb_perror_msg_and_die("can't change root directory to %s", path);
+}
+
+// Print a warning message if opendir() fails, but don't die.
+DIR* FAST_FUNC warn_opendir(const char *path)
+{
+       DIR *dp;
+
+       dp = opendir(path);
+       if (!dp)
+               bb_perror_msg("can't open '%s'", path);
+       return dp;
+}
+
+// Die with an error message if opendir() fails.
+DIR* FAST_FUNC xopendir(const char *path)
+{
+       DIR *dp;
+
+       dp = opendir(path);
+       if (!dp)
+               bb_perror_msg_and_die("can't open '%s'", path);
+       return dp;
+}
+
+// Die with an error message if we can't open a new socket.
+int FAST_FUNC xsocket(int domain, int type, int protocol)
+{
+       int r = socket(domain, type, protocol);
+
+       if (r < 0) {
+               /* Hijack vaguely related config option */
+#if ENABLE_VERBOSE_RESOLUTION_ERRORS
+               const char *s = "INET";
+               if (domain == AF_PACKET) s = "PACKET";
+               if (domain == AF_NETLINK) s = "NETLINK";
+USE_FEATURE_IPV6(if (domain == AF_INET6) s = "INET6";)
+               bb_perror_msg_and_die("socket(AF_%s)", s);
+#else
+               bb_perror_msg_and_die("socket");
+#endif
+       }
+
+       return r;
+}
+
+// Die with an error message if we can't bind a socket to an address.
+void FAST_FUNC 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 FAST_FUNC xlisten(int s, int backlog)
+{
+       if (listen(s, backlog)) bb_perror_msg_and_die("listen");
+}
+
+/* Die with an error message if sendto failed.
+ * Return bytes sent otherwise  */
+ssize_t FAST_FUNC xsendto(int s, const void *buf, size_t len, const struct sockaddr *to,
+                               socklen_t tolen)
+{
+       ssize_t ret = sendto(s, buf, len, 0, to, tolen);
+       if (ret < 0) {
+               if (ENABLE_FEATURE_CLEAN_UP)
+                       close(s);
+               bb_perror_msg_and_die("sendto");
+       }
+       return ret;
+}
+
+// xstat() - a stat() which dies on failure with meaningful error message
+void FAST_FUNC xstat(const char *name, struct stat *stat_buf)
+{
+       if (stat(name, stat_buf))
+               bb_perror_msg_and_die("can't stat '%s'", name);
+}
+
+// selinux_or_die() - die if SELinux is disabled.
+void FAST_FUNC selinux_or_die(void)
+{
+#if ENABLE_SELINUX
+       int rc = is_selinux_enabled();
+       if (rc == 0) {
+               bb_error_msg_and_die("SELinux is disabled");
+       } else if (rc < 0) {
+               bb_error_msg_and_die("is_selinux_enabled() failed");
+       }
+#else
+       bb_error_msg_and_die("SELinux support is disabled");
+#endif
+}
+
+int FAST_FUNC ioctl_or_perror_and_die(int fd, unsigned request, void *argp, const char *fmt,...)
+{
+       int ret;
+       va_list p;
+
+       ret = ioctl(fd, request, argp);
+       if (ret < 0) {
+               va_start(p, fmt);
+               bb_verror_msg(fmt, p, strerror(errno));
+               /* xfunc_die can actually longjmp, so be nice */
+               va_end(p);
+               xfunc_die();
+       }
+       return ret;
+}
+
+int FAST_FUNC ioctl_or_perror(int fd, unsigned request, void *argp, const char *fmt,...)
+{
+       va_list p;
+       int ret = ioctl(fd, request, argp);
+
+       if (ret < 0) {
+               va_start(p, fmt);
+               bb_verror_msg(fmt, p, strerror(errno));
+               va_end(p);
+       }
+       return ret;
+}
+
+#if ENABLE_IOCTL_HEX2STR_ERROR
+int FAST_FUNC bb_ioctl_or_warn(int fd, unsigned request, void *argp, const char *ioctl_name)
+{
+       int ret;
+
+       ret = ioctl(fd, request, argp);
+       if (ret < 0)
+               bb_simple_perror_msg(ioctl_name);
+       return ret;
+}
+int FAST_FUNC bb_xioctl(int fd, unsigned request, void *argp, const char *ioctl_name)
+{
+       int ret;
+
+       ret = ioctl(fd, request, argp);
+       if (ret < 0)
+               bb_simple_perror_msg_and_die(ioctl_name);
+       return ret;
+}
+#else
+int FAST_FUNC bb_ioctl_or_warn(int fd, unsigned request, void *argp)
+{
+       int ret;
+
+       ret = ioctl(fd, request, argp);
+       if (ret < 0)
+               bb_perror_msg("ioctl %#x failed", request);
+       return ret;
+}
+int FAST_FUNC bb_xioctl(int fd, unsigned request, void *argp)
+{
+       int ret;
+
+       ret = ioctl(fd, request, argp);
+       if (ret < 0)
+               bb_perror_msg_and_die("ioctl %#x failed", request);
+       return ret;
+}
+#endif
diff --git a/libbb/xgetcwd.c b/libbb/xgetcwd.c
new file mode 100644 (file)
index 0000000..10febe3
--- /dev/null
@@ -0,0 +1,43 @@
+/* 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 <djm@gnu.ai.mit.edu>.
+ *
+ * Special function for busybox written by Vladimir Oleynik <dzo@simtreas.ru>
+ *
+ * Licensed under GPLv2, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.h"
+
+/* 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* FAST_FUNC
+xrealloc_getcwd_or_warn(char *cwd)
+{
+#define PATH_INCR 64
+
+       char *ret;
+       unsigned path_max;
+
+       path_max = 128; /* 128 + 64 should be enough for 99% of cases */
+
+       while (1) {
+               path_max += PATH_INCR;
+               cwd = xrealloc(cwd, path_max);
+               ret = getcwd(cwd, path_max);
+               if (ret == NULL) {
+                       if (errno == ERANGE)
+                               continue;
+                       free(cwd);
+                       bb_perror_msg("getcwd");
+                       return NULL;
+               }
+               cwd = xrealloc(cwd, strlen(cwd) + 1);
+               return cwd;
+       }
+}
diff --git a/libbb/xgethostbyname.c b/libbb/xgethostbyname.c
new file mode 100644 (file)
index 0000000..f1839f7
--- /dev/null
@@ -0,0 +1,19 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Mini xgethostbyname implementation.
+ *
+ * Copyright (C) 2001 Matt Kraai <kraai@alumni.carnegiemellon.edu>.
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+//#include <netdb.h>
+#include "libbb.h"
+
+struct hostent* FAST_FUNC xgethostbyname(const char *name)
+{
+       struct hostent *retval = gethostbyname(name);
+       if (!retval)
+               bb_herror_msg_and_die("%s", name);
+       return retval;
+}
diff --git a/libbb/xreadlink.c b/libbb/xreadlink.c
new file mode 100644 (file)
index 0000000..8d232f1
--- /dev/null
@@ -0,0 +1,117 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * xreadlink.c - safe implementation of readlink.
+ * Returns a NULL on failure...
+ *
+ * Licensed under GPLv2, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.h"
+
+/*
+ * NOTE: This function returns a malloced char* that you will have to free
+ * yourself.
+ */
+char* FAST_FUNC xmalloc_readlink(const char *path)
+{
+       enum { GROWBY = 80 }; /* how large we will grow strings by */
+
+       char *buf = NULL;
+       int bufsize = 0, readsize = 0;
+
+       do {
+               bufsize += GROWBY;
+               buf = xrealloc(buf, bufsize);
+               readsize = readlink(path, buf, bufsize);
+               if (readsize == -1) {
+                       free(buf);
+                       return NULL;
+               }
+       } while (bufsize < readsize + 1);
+
+       buf[readsize] = '\0';
+
+       return buf;
+}
+
+/*
+ * This routine is not the same as realpath(), which
+ * canonicalizes the given path completely. This routine only
+ * follows trailing symlinks until a real file is reached and
+ * returns its name. If the path ends in a dangling link or if
+ * the target doesn't exist, the path is returned in any case.
+ * Intermediate symlinks in the path are not expanded -- only
+ * those at the tail.
+ * A malloced char* is returned, which must be freed by the caller.
+ */
+char* FAST_FUNC xmalloc_follow_symlinks(const char *path)
+{
+       char *buf;
+       char *lpc;
+       char *linkpath;
+       int bufsize;
+       int looping = MAXSYMLINKS + 1;
+
+       buf = xstrdup(path);
+       goto jump_in;
+
+       while (1) {
+               linkpath = xmalloc_readlink(buf);
+               if (!linkpath) {
+                       /* not a symlink, or doesn't exist */
+                       if (errno == EINVAL || errno == ENOENT)
+                               return buf;
+                       goto free_buf_ret_null;
+               }
+
+               if (!--looping) {
+                       free(linkpath);
+ free_buf_ret_null:
+                       free(buf);
+                       return NULL;
+               }
+
+               if (*linkpath != '/') {
+                       bufsize += strlen(linkpath);
+                       buf = xrealloc(buf, bufsize);
+                       lpc = bb_get_last_path_component_strip(buf);
+                       strcpy(lpc, linkpath);
+                       free(linkpath);
+               } else {
+                       free(buf);
+                       buf = linkpath;
+ jump_in:
+                       bufsize = strlen(buf) + 1;
+               }
+       }
+}
+
+char* FAST_FUNC xmalloc_readlink_or_warn(const char *path)
+{
+       char *buf = xmalloc_readlink(path);
+       if (!buf) {
+               /* EINVAL => "file: Invalid argument" => puzzled user */
+               const char *errmsg = "not a symlink";
+               int err = errno;
+               if (err != EINVAL)
+                       errmsg = strerror(err);
+               bb_error_msg("%s: cannot read link: %s", path, errmsg);
+       }
+       return buf;
+}
+
+/* UNUSED */
+#if 0
+char* FAST_FUNC xmalloc_realpath(const char *path)
+{
+#if defined(__GLIBC__) && !defined(__UCLIBC__)
+       /* glibc provides a non-standard extension */
+       return realpath(path, NULL);
+#else
+       char buf[PATH_MAX+1];
+
+       /* on error returns NULL (xstrdup(NULL) ==NULL) */
+       return xstrdup(realpath(path, buf));
+#endif
+}
+#endif
diff --git a/libbb/xrealloc_vector.c b/libbb/xrealloc_vector.c
new file mode 100644 (file)
index 0000000..bbd5ab8
--- /dev/null
@@ -0,0 +1,45 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Utility routines.
+ *
+ * Copyright (C) 2008 Denys Vlasenko
+ *
+ * Licensed under GPLv2, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.h"
+
+/* Resize (grow) malloced vector.
+ *
+ *  #define magic packed two parameters into one:
+ *  sizeof = sizeof_and_shift >> 8
+ *  shift  = (sizeof_and_shift) & 0xff
+ *
+ * Lets say shift = 4. 1 << 4 == 0x10.
+ * If idx == 0, 0x10, 0x20 etc, vector[] is resized to next higher
+ * idx step, plus one: if idx == 0x20, vector[] is resized to 0x31,
+ * thus last usable element is vector[0x30].
+ *
+ * In other words: after xrealloc_vector(v, 4, idx) it's ok to use
+ * at least v[idx] and v[idx+1], for all idx values.
+ *
+ * New elements are zeroed out, but only if realloc was done
+ * (not on every call). You can depend on v[idx] and v[idx+1] being
+ * zeroed out if you use it like this:
+ *  v = xrealloc_vector(v, 4, idx);
+ *  v[idx].some_fields = ...; - the rest stays 0/NULL
+ *  idx++;
+ * If you do not advance idx like above, you should be more careful.
+ * Next call to xrealloc_vector(v, 4, idx) may or may not zero out v[idx].
+ */
+void* FAST_FUNC xrealloc_vector_helper(void *vector, unsigned sizeof_and_shift, int idx)
+{
+       int mask = 1 << (uint8_t)sizeof_and_shift;
+
+       if (!(idx & (mask - 1))) {
+               sizeof_and_shift >>= 8; /* sizeof(vector[0]) */
+               vector = xrealloc(vector, sizeof_and_shift * (idx + mask + 1));
+               memset((char*)vector + (sizeof_and_shift * idx), 0, sizeof_and_shift * (mask + 1));
+       }
+       return vector;
+}
diff --git a/libbb/xregcomp.c b/libbb/xregcomp.c
new file mode 100644 (file)
index 0000000..61efb5b
--- /dev/null
@@ -0,0 +1,32 @@
+/* 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"
+#include "xregex.h"
+
+char* FAST_FUNC regcomp_or_errmsg(regex_t *preg, const char *regex, int cflags)
+{
+       int ret = regcomp(preg, regex, cflags);
+       if (ret) {
+               int errmsgsz = regerror(ret, preg, NULL, 0);
+               char *errmsg = xmalloc(errmsgsz);
+               regerror(ret, preg, errmsg, errmsgsz);
+               return errmsg;
+       }
+       return NULL;
+}
+
+void FAST_FUNC xregcomp(regex_t *preg, const char *regex, int cflags)
+{
+       char *errmsg = regcomp_or_errmsg(preg, regex, cflags);
+       if (errmsg) {
+               bb_error_msg_and_die("bad regex '%s': %s", regex, errmsg);
+       }
+}
diff --git a/libpwdgrp/Kbuild b/libpwdgrp/Kbuild
new file mode 100644 (file)
index 0000000..f9f1ddb
--- /dev/null
@@ -0,0 +1,9 @@
+# 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 := uidgid_get.o
+
+lib-$(CONFIG_USE_BB_PWD_GRP) += pwd_grp.o
diff --git a/libpwdgrp/pwd_grp.c b/libpwdgrp/pwd_grp.c
new file mode 100644 (file)
index 0000000..56bfcbe
--- /dev/null
@@ -0,0 +1,1077 @@
+/* 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 <features.h>
+#include <assert.h>
+
+#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. */
+
+static int bb__pgsreader(int (*parserfunc)(void *d, char *line), void *data,
+               char *__restrict line_buff, size_t buflen, FILE *f);
+
+static int bb__parsepwent(void *pw, char *line);
+static int bb__parsegrent(void *gr, char *line);
+#if ENABLE_USE_BB_SHADOW
+static int bb__parsespent(void *sp, char *line);
+#endif
+
+/**********************************************************************/
+/* We avoid having big global data. */
+
+struct statics {
+       /* Smaller things first */
+       struct passwd getpwuid_resultbuf;
+       struct group getgrgid_resultbuf;
+       struct passwd getpwnam_resultbuf;
+       struct group getgrnam_resultbuf;
+
+       char getpwuid_buffer[PWD_BUFFER_SIZE];
+       char getgrgid_buffer[GRP_BUFFER_SIZE];
+       char getpwnam_buffer[PWD_BUFFER_SIZE];
+       char getgrnam_buffer[GRP_BUFFER_SIZE];
+#if 0
+       struct passwd fgetpwent_resultbuf;
+       struct group fgetgrent_resultbuf;
+       struct spwd fgetspent_resultbuf;
+       char fgetpwent_buffer[PWD_BUFFER_SIZE];
+       char fgetgrent_buffer[GRP_BUFFER_SIZE];
+       char fgetspent_buffer[PWD_BUFFER_SIZE];
+#endif
+#if 0 //ENABLE_USE_BB_SHADOW
+       struct spwd getspuid_resultbuf;
+       struct spwd getspnam_resultbuf;
+       char getspuid_buffer[PWD_BUFFER_SIZE];
+       char getspnam_buffer[PWD_BUFFER_SIZE];
+#endif
+// Not converted - too small to bother
+//pthread_mutex_t mylock = PTHREAD_MUTEX_INITIALIZER;
+//FILE *pwf /*= NULL*/;
+//FILE *grf /*= NULL*/;
+//FILE *spf /*= NULL*/;
+#if 0
+       struct passwd getpwent_pwd;
+       struct group getgrent_gr;
+       char getpwent_line_buff[PWD_BUFFER_SIZE];
+       char getgrent_line_buff[GRP_BUFFER_SIZE];
+#endif
+#if 0 //ENABLE_USE_BB_SHADOW
+       struct spwd getspent_spwd;
+       struct spwd sgetspent_spwd;
+       char getspent_line_buff[PWD_BUFFER_SIZE];
+       char sgetspent_line_buff[PWD_BUFFER_SIZE];
+#endif
+};
+
+static struct statics *ptr_to_statics;
+
+static struct statics *get_S(void)
+{
+       if (!ptr_to_statics)
+               ptr_to_statics = xzalloc(sizeof(*ptr_to_statics));
+       return ptr_to_statics;
+}
+
+/* Always use in this order, get_S() must be called first */
+#define RESULTBUF(name) &((S = get_S())->name##_resultbuf)
+#define BUFFER(name)    (S->name##_buffer)
+
+/**********************************************************************/
+/* For the various fget??ent_r funcs, return
+ *
+ *  0: success
+ *  ENOENT: end-of-file encountered
+ *  ERANGE: buflen too small
+ *  other error values possible. See bb__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;
+
+       rv = bb__pgsreader(bb__parsepwent, resultbuf, buffer, buflen, stream);
+       if (!rv) {
+               *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;
+
+       rv = bb__pgsreader(bb__parsegrent, resultbuf, buffer, buflen, stream);
+       if (!rv) {
+               *result = resultbuf;
+       }
+
+       return rv;
+}
+
+#if ENABLE_USE_BB_SHADOW
+int fgetspent_r(FILE *__restrict stream, struct spwd *__restrict resultbuf,
+                               char *__restrict buffer, size_t buflen,
+                               struct spwd **__restrict result)
+{
+       int rv;
+
+       *result = NULL;
+
+       rv = bb__pgsreader(bb__parsespent, resultbuf, buffer, buflen, stream);
+       if (!rv) {
+               *result = resultbuf;
+       }
+
+       return rv;
+}
+#endif
+
+/**********************************************************************/
+/* For the various fget??ent funcs, return NULL on failure and a
+ * pointer to the appropriate struct (statically allocated) on success.
+ * TODO: audit & stop using these in bbox, they pull in static buffers */
+/**********************************************************************/
+
+#if 0
+struct passwd *fgetpwent(FILE *stream)
+{
+       struct statics *S;
+       struct passwd *resultbuf = RESULTBUF(fgetpwent);
+       char *buffer = BUFFER(fgetpwent);
+       struct passwd *result;
+
+       fgetpwent_r(stream, resultbuf, buffer, sizeof(BUFFER(fgetpwent)), &result);
+       return result;
+}
+
+struct group *fgetgrent(FILE *stream)
+{
+       struct statics *S;
+       struct group *resultbuf = RESULTBUF(fgetgrent);
+       char *buffer = BUFFER(fgetgrent);
+       struct group *result;
+
+       fgetgrent_r(stream, resultbuf, buffer, sizeof(BUFFER(fgetgrent)), &result);
+       return result;
+}
+#endif
+
+#if ENABLE_USE_BB_SHADOW
+#if 0
+struct spwd *fgetspent(FILE *stream)
+{
+       struct statics *S;
+       struct spwd *resultbuf = RESULTBUF(fgetspent);
+       char *buffer = BUFFER(fgetspent);
+       struct spwd *result;
+
+       fgetspent_r(stream, resultbuf, buffer, sizeof(BUFFER(fgetspent)), &result);
+       return result;
+}
+#endif
+
+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);
+       }
+
+       rv = bb__parsespent(result_buf, buffer);
+       if (!rv) {
+               *result = result_buf;
+       }
+
+ DONE:
+       return rv;
+}
+#endif
+
+/**********************************************************************/
+
+#define GETXXKEY_R_FUNC         getpwnam_r
+#define GETXXKEY_R_PARSER       bb__parsepwent
+#define GETXXKEY_R_ENTTYPE      struct passwd
+#define GETXXKEY_R_TEST(ENT)    (!strcmp((ENT)->pw_name, key))
+#define GETXXKEY_R_KEYTYPE      const char *__restrict
+#define GETXXKEY_R_PATHNAME     _PATH_PASSWD
+#include "pwd_grp_internal.c"
+
+#define GETXXKEY_R_FUNC         getgrnam_r
+#define GETXXKEY_R_PARSER       bb__parsegrent
+#define GETXXKEY_R_ENTTYPE      struct group
+#define GETXXKEY_R_TEST(ENT)    (!strcmp((ENT)->gr_name, key))
+#define GETXXKEY_R_KEYTYPE      const char *__restrict
+#define GETXXKEY_R_PATHNAME     _PATH_GROUP
+#include "pwd_grp_internal.c"
+
+#if ENABLE_USE_BB_SHADOW
+#define GETXXKEY_R_FUNC         getspnam_r
+#define GETXXKEY_R_PARSER       bb__parsespent
+#define GETXXKEY_R_ENTTYPE      struct spwd
+#define GETXXKEY_R_TEST(ENT)    (!strcmp((ENT)->sp_namp, key))
+#define GETXXKEY_R_KEYTYPE      const char *__restrict
+#define GETXXKEY_R_PATHNAME     _PATH_SHADOW
+#include "pwd_grp_internal.c"
+#endif
+
+#define GETXXKEY_R_FUNC         getpwuid_r
+#define GETXXKEY_R_PARSER       bb__parsepwent
+#define GETXXKEY_R_ENTTYPE      struct passwd
+#define GETXXKEY_R_TEST(ENT)    ((ENT)->pw_uid == key)
+#define GETXXKEY_R_KEYTYPE      uid_t
+#define GETXXKEY_R_PATHNAME     _PATH_PASSWD
+#include "pwd_grp_internal.c"
+
+#define GETXXKEY_R_FUNC         getgrgid_r
+#define GETXXKEY_R_PARSER       bb__parsegrent
+#define GETXXKEY_R_ENTTYPE      struct group
+#define GETXXKEY_R_TEST(ENT)    ((ENT)->gr_gid == key)
+#define GETXXKEY_R_KEYTYPE      gid_t
+#define GETXXKEY_R_PATHNAME     _PATH_GROUP
+#include "pwd_grp_internal.c"
+
+/**********************************************************************/
+/* TODO: audit & stop using these in bbox, they pull in static buffers */
+
+/* This one has many users */
+struct passwd *getpwuid(uid_t uid)
+{
+       struct statics *S;
+       struct passwd *resultbuf = RESULTBUF(getpwuid);
+       char *buffer = BUFFER(getpwuid);
+       struct passwd *result;
+
+       getpwuid_r(uid, resultbuf, buffer, sizeof(BUFFER(getpwuid)), &result);
+       return result;
+}
+
+/* This one has many users */
+struct group *getgrgid(gid_t gid)
+{
+       struct statics *S;
+       struct group *resultbuf = RESULTBUF(getgrgid);
+       char *buffer = BUFFER(getgrgid);
+       struct group *result;
+
+       getgrgid_r(gid, resultbuf, buffer, sizeof(BUFFER(getgrgid)), &result);
+       return result;
+}
+
+#if 0 //ENABLE_USE_BB_SHADOW
+/* 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;
+       rv = getpwuid_r(uid, &password, pwd_buff, sizeof(pwd_buff), &pp);
+       if (!rv) {
+               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)
+{
+       struct statics *S;
+       struct spwd *resultbuf = RESULTBUF(getspuid);
+       char *buffer = BUFFER(getspuid);
+       struct spwd *result;
+
+       getspuid_r(uid, resultbuf, buffer, sizeof(BUFFER(getspuid)), &result);
+       return result;
+}
+#endif
+
+/* This one has many users */
+struct passwd *getpwnam(const char *name)
+{
+       struct statics *S;
+       struct passwd *resultbuf = RESULTBUF(getpwnam);
+       char *buffer = BUFFER(getpwnam);
+       struct passwd *result;
+
+       getpwnam_r(name, resultbuf, buffer, sizeof(BUFFER(getpwnam)), &result);
+       return result;
+}
+
+/* This one has many users */
+struct group *getgrnam(const char *name)
+{
+       struct statics *S;
+       struct group *resultbuf = RESULTBUF(getgrnam);
+       char *buffer = BUFFER(getgrnam);
+       struct group *result;
+
+       getgrnam_r(name, resultbuf, buffer, sizeof(BUFFER(getgrnam)), &result);
+       return result;
+}
+
+#if 0 //ENABLE_USE_BB_SHADOW
+struct spwd *getspnam(const char *name)
+{
+       struct statics *S;
+       struct spwd *resultbuf = RESULTBUF(getspnam);
+       char *buffer = BUFFER(getspnam);
+       struct spwd *result;
+
+       getspnam_r(name, resultbuf, buffer, sizeof(BUFFER(getspnam)), &result);
+       return result;
+}
+#endif
+
+#ifdef THIS_ONE_IS_UNUSED
+/* This one doesn't use static buffers */
+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;
+}
+#endif
+
+/**********************************************************************/
+
+/* FIXME: we don't have such CONFIG_xx - ?! */
+
+#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) {
+               pwf = fopen_for_read(_PATH_PASSWD);
+               if (!pwf) {
+                       rv = errno;
+                       goto ERR;
+               }
+       }
+
+       rv = bb__pgsreader(bb__parsepwent, resultbuf, buffer, buflen, pwf);
+       if (!rv) {
+               *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) {
+               grf = fopen_for_read(_PATH_GROUP);
+               if (!grf) {
+                       rv = errno;
+                       goto ERR;
+               }
+       }
+
+       rv = bb__pgsreader(bb__parsegrent, resultbuf, buffer, buflen, grf);
+       if (!rv) {
+               *result = resultbuf;
+       }
+
+ ERR:
+       UNLOCK;
+       return rv;
+}
+
+#if ENABLE_USE_BB_SHADOW
+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) {
+               spf = fopen_for_read(_PATH_SHADOW);
+               if (!spf) {
+                       rv = errno;
+                       goto ERR;
+               }
+       }
+
+       rv = bb__pgsreader(bb__parsespent, resultbuf, buffer, buflen, spf);
+       if (!rv) {
+               *result = resultbuf;
+       }
+
+ ERR:
+       UNLOCK;
+       return rv;
+}
+#endif
+
+#if 0
+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;
+}
+#endif
+
+#if 0 //ENABLE_USE_BB_SHADOW
+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;
+}
+#endif
+
+static gid_t *getgrouplist_internal(int *ngroups_ptr, const char *user, gid_t gid)
+{
+       FILE *grfile;
+       gid_t *group_list;
+       int ngroups;
+       struct group group;
+       char buff[PWD_BUFFER_SIZE];
+
+       /* We alloc space for 8 gids at a time. */
+       group_list = xmalloc(8 * sizeof(group_list[0]));
+       group_list[0] = gid;
+       ngroups = 1;
+
+       grfile = fopen_for_read(_PATH_GROUP);
+       if (grfile) {
+               while (!bb__pgsreader(bb__parsegrent, &group, buff, sizeof(buff), grfile)) {
+                       char **m;
+                       assert(group.gr_mem); /* Must have at least a NULL terminator. */
+                       if (group.gr_gid == gid)
+                               continue;
+                       for (m = group.gr_mem; *m; m++) {
+                               if (strcmp(*m, user) != 0)
+                                       continue;
+                               group_list = xrealloc_vector(group_list, 3, ngroups);
+                               group_list[ngroups++] = group.gr_gid;
+                               break;
+                       }
+               }
+               fclose(grfile);
+       }
+       *ngroups_ptr = ngroups;
+       return group_list;
+}
+
+int initgroups(const char *user, gid_t gid)
+{
+       int ngroups;
+       gid_t *group_list = getgrouplist_internal(&ngroups, user, gid);
+
+       ngroups = setgroups(ngroups, group_list);
+       free(group_list);
+       return ngroups;
+}
+
+int getgrouplist(const char *user, gid_t gid, gid_t *groups, int *ngroups)
+{
+       int ngroups_old = *ngroups;
+       gid_t *group_list = getgrouplist_internal(ngroups, user, gid);
+
+       if (*ngroups <= ngroups_old) {
+               ngroups_old = *ngroups;
+               memcpy(groups, group_list, ngroups_old * sizeof(groups[0]));
+       } else {
+               ngroups_old = -1;
+       }
+       free(group_list);
+       return ngroups_old;
+}
+
+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[] ALIGN1 = ",%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;
+}
+
+#if ENABLE_USE_BB_SHADOW
+static const unsigned char _sp_off[] ALIGN1 = {
+       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[] ALIGN1 = "%ld:";
+
+       const char *f;
+       long 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;
+               x = *(const long *)(((const char *) p) + _sp_off[i]);
+               if (x == -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;
+}
+#endif
+
+/**********************************************************************/
+/* Internal uClibc functions.                                         */
+/**********************************************************************/
+
+static const unsigned char pw_off[] ALIGN1 = {
+       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 */
+};
+
+static int bb__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. */
+                       line = strchr(line, ':');
+                       if (!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[] ALIGN1 = {
+       offsetof(struct group, gr_name),        /* 0 */
+       offsetof(struct group, gr_passwd),      /* 1 */
+       offsetof(struct group, gr_gid)          /* 2 - not a char ptr */
+};
+
+static int bb__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;
+                       line = strchr(line, ':');
+                       if (!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;
+}
+
+/**********************************************************************/
+
+#if ENABLE_USE_BB_SHADOW
+static const unsigned char sp_off[] ALIGN1 = {
+       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 */
+};
+
+static int bb__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;
+                       line = strchr(line, ':');
+                       if (!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;
+}
+#endif
+
+/**********************************************************************/
+
+/* 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).
+ */
+
+static int bb__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 == bb__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 (file)
index 0000000..ffdc85e
--- /dev/null
@@ -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(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_for_read(GETXXKEY_R_PATHNAME);
+       if (!stream)
+               return errno;
+       while (1) {
+               rv = bb__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 GETXXKEY_R_KEYTYPE
+#undef GETXXKEY_R_PATHNAME
diff --git a/libpwdgrp/uidgid_get.c b/libpwdgrp/uidgid_get.c
new file mode 100644 (file)
index 0000000..92290bf
--- /dev/null
@@ -0,0 +1,134 @@
+/*
+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.
+*/
+
+#include "libbb.h"
+
+/* Always sets uid and gid */
+int FAST_FUNC get_uidgid(struct bb_uidgid_t *u, const char *ug, int numeric_ok)
+{
+       struct passwd *pwd;
+       struct group *gr;
+       char *user, *group;
+       unsigned n;
+
+       user = (char*)ug;
+       group = strchr(ug, ':');
+       if (group) {
+               int sz = (++group) - ug;
+               user = alloca(sz);
+               /* copies sz-1 bytes, stores terminating '\0' */
+               safe_strncpy(user, ug, sz);
+       }
+       if (numeric_ok) {
+               n = bb_strtou(user, NULL, 10);
+               if (!errno) {
+                       u->uid = n;
+                       pwd = getpwuid(n);
+                       /* If we have e.g. "500" string without user */
+                       /* with uid 500 in /etc/passwd, we set gid == uid */
+                       u->gid = pwd ? pwd->pw_gid : n;
+                       goto skip;
+               }
+       }
+       /* Either it is not numeric, or caller disallows numeric username */
+       pwd = getpwnam(user);
+       if (!pwd)
+               return 0;
+       u->uid = pwd->pw_uid;
+       u->gid = pwd->pw_gid;
+
+ skip:
+       if (group) {
+               if (numeric_ok) {
+                       n = bb_strtou(group, NULL, 10);
+                       if (!errno) {
+                               u->gid = n;
+                               return 1;
+                       }
+               }
+               gr = getgrnam(group);
+               if (!gr) return 0;
+               u->gid = gr->gr_gid;
+       }
+       return 1;
+}
+void FAST_FUNC xget_uidgid(struct bb_uidgid_t *u, const char *ug)
+{
+       if (!get_uidgid(u, ug, 1))
+               bb_error_msg_and_die("unknown user/group %s", ug);
+}
+
+/* chown-like:
+ * "user" sets uid only,
+ * ":group" sets gid only
+ * "user:" sets uid and gid (to user's primary group id)
+ * "user:group" sets uid and gid
+ * ('unset' uid or gid retains the value it has on entry)
+ */
+void FAST_FUNC parse_chown_usergroup_or_die(struct bb_uidgid_t *u, char *user_group)
+{
+       char *group;
+
+       /* Check if there is a group name */
+       group = strchr(user_group, '.'); /* deprecated? */
+       if (!group)
+               group = strchr(user_group, ':');
+       else
+               *group = ':'; /* replace '.' with ':' */
+
+       /* Parse "user[:[group]]" */
+       if (!group) { /* "user" */
+               u->uid = get_ug_id(user_group, xuname2uid);
+       } else if (group == user_group) { /* ":group" */
+               u->gid = get_ug_id(group + 1, xgroup2gid);
+       } else {
+               if (!group[1]) /* "user:" */
+                       *group = '\0';
+               xget_uidgid(u, user_group);
+       }
+}
+
+#if 0
+#include <stdio.h>
+int main()
+{
+       unsigned u;
+       struct bb_uidgid_t ug;
+       u = get_uidgid(&ug, "apache", 0);
+       printf("%u = %u:%u\n", u, ug.uid, ug.gid);
+       ug.uid = ug.gid = 1111;
+       u = get_uidgid(&ug, "apache", 0);
+       printf("%u = %u:%u\n", u, ug.uid, ug.gid);
+       ug.uid = ug.gid = 1111;
+       u = get_uidgid(&ug, "apache:users", 0);
+       printf("%u = %u:%u\n", u, ug.uid, ug.gid);
+       ug.uid = ug.gid = 1111;
+       u = get_uidgid(&ug, "apache:users", 0);
+       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 (file)
index 0000000..ddd0c80
--- /dev/null
@@ -0,0 +1,296 @@
+#
+# 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_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 need to use glibc's nsswitch.conf mechanism
+         (e.g. if user/group database is NOT stored in /etc/passwd etc),
+         you must NOT use this option.
+
+         If you enable this option, it will add about 1.5k.
+
+config USE_BB_SHADOW
+       bool "Use internal 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_CRYPT
+       bool "Use internal crypt functions"
+       default y
+       help
+         Busybox has internal DES and MD5 crypt functions.
+         They produce results which are identical to corresponding
+         standard C library functions.
+
+         If you leave this disabled, busybox will use the system's
+         crypt functions. Most C libraries use large (~70k)
+         static buffers there, and also combine them with more general
+         DES encryption/decryption.
+
+         For busybox, having large static buffers is undesirable,
+         especially on NOMMU machines. Busybox also doesn't need
+         DES encryption/decryption and can do with smaller code.
+
+         If you enable this option, it will add about 4.8k of code
+         if you are building dynamically linked executable.
+         In static build, it makes code _smaller_ by about 1.2k,
+         and likely many kilobytes less of bss.
+
+config USE_BB_CRYPT_SHA
+       bool "Enable SHA256/512 crypt functions"
+       default n
+       depends on USE_BB_CRYPT
+       help
+         Enable this if you have passwords starting with "$5$" or "$6$"
+         in your /etc/passwd or /etc/shadow files. These passwords
+         are hashed using SHA256 and SHA512 algorithms. Support for them
+         was added to glibc in 2008.
+         With this option off, login will fail password check for any
+         user which has password encrypted with these algorithms.
+
+config ADDGROUP
+       bool "addgroup"
+       default n
+       help
+         Utility for creating a new group account.
+
+config FEATURE_ADDUSER_TO_GROUP
+       bool "Support for adding users to groups"
+       default n
+       depends on ADDGROUP
+       help
+         If  called  with two non-option arguments,
+         addgroup will add an existing user to an
+         existing group.
+
+config DELGROUP
+       bool "delgroup"
+       default n
+       help
+         Utility for deleting a group account.
+
+config FEATURE_DEL_USER_FROM_GROUP
+       bool "Support for removing users from groups"
+       default n
+       depends on DELGROUP
+       help
+         If called with two non-option arguments, deluser
+         or delgroup will remove an user from a specified group.
+
+config FEATURE_CHECK_NAMES
+       bool "Enable sanity check on user/group names in adduser and addgroup"
+       default n
+       depends on ADDUSER || ADDGROUP
+       help
+         Enable sanity check on user and group names in adduser and addgroup.
+         To avoid problems, the user or group name should consist only of
+         letters, digits, underscores, periods, at signs and dashes,
+         and not start with a dash (as defined by IEEE Std 1003.1-2001).
+         For compatibility with Samba machine accounts "$" is also supported
+         at the end of the user or group name.
+
+config ADDUSER
+       bool "adduser"
+       default n
+       help
+         Utility for creating a new user account.
+
+config FEATURE_ADDUSER_LONG_OPTIONS
+       bool "Enable long options"
+       default n
+       depends on ADDUSER && GETOPT_LONG
+       help
+         Support long options for the adduser applet.
+
+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 PAM
+       bool "Support for PAM (Pluggable Authentication Modules)"
+       default n
+       depends on LOGIN
+       help
+         Use PAM in login(1) instead of direct access to password database.
+
+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_NOLOGIN
+       bool "Support for /etc/nologin"
+       default y
+       depends on LOGIN
+       help
+         The file /etc/nologin is used by (some versions of) login(1).
+         If it exists, non-root logins are prohibited.
+
+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 FEATURE_PASSWD_WEAK_CHECK
+       bool "Check new passwords for weakness"
+       default y
+       depends on PASSWD
+       help
+         With this option passwd will refuse new passwords which are "weak".
+
+config CRYPTPW
+       bool "cryptpw"
+       default n
+       help
+         Encrypts the given password with the crypt(3) libc function
+         using the given salt. Debian has this utility under mkpasswd
+         name. Busybox provides mkpasswd as an alias for cryptpw.
+
+config CHPASSWD
+       bool "chpasswd"
+       default n
+       help
+         Reads a file of user name and password pairs from standard input
+         and uses this information to update a group of existing users.
+
+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 FEATURE_SU_SYSLOG
+       bool "Enable su to write to syslog"
+       default y
+       depends on SU
+
+config FEATURE_SU_CHECKS_SHELLS
+       bool "Enable su to check user's shell to be listed in /etc/shells"
+       depends on SU
+       default y
+
+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 (file)
index 0000000..3d0d777
--- /dev/null
@@ -0,0 +1,19 @@
+# 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_ADDGROUP) += addgroup.o
+lib-$(CONFIG_ADDUSER)  += adduser.o
+lib-$(CONFIG_CRYPTPW)  += cryptpw.o
+lib-$(CONFIG_CHPASSWD) += chpasswd.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 (file)
index 0000000..5a0cf3f
--- /dev/null
@@ -0,0 +1,133 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * addgroup - add groups to /etc/group and /etc/gshadow
+ *
+ * Copyright (C) 1999 by Lineo, inc. and John Beppu
+ * Copyright (C) 1999,2000,2001 by John Beppu <beppu@codepoet.org>
+ * Copyright (C) 2007 by Tito Ragusa <farmatito@tiscali.it>
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ *
+ */
+#include "libbb.h"
+
+static void xgroup_study(struct group *g)
+{
+       /* Make sure gr_name is unused */
+       if (getgrnam(g->gr_name)) {
+               goto error;
+       }
+
+       /* Check if the desired gid is free
+        * or find the first free one */
+       while (1) {
+               if (!getgrgid(g->gr_gid)) {
+                       return; /* found free group: return */
+               }
+               if (option_mask32) {
+                       /* -g N, cannot pick gid other than N: error */
+                       g->gr_name = itoa(g->gr_gid);
+                       goto error;
+               }
+               g->gr_gid++;
+               if (g->gr_gid <= 0) {
+                       /* overflowed: error */
+                       bb_error_msg_and_die("no gids left");
+               }
+       }
+
+ error:
+       /* exit */
+       bb_error_msg_and_die("group %s already exists", g->gr_name);
+}
+
+/* append a new user to the passwd file */
+static void new_group(char *group, gid_t gid)
+{
+       struct group gr;
+       char *p;
+
+       /* make sure gid and group haven't already been allocated */
+       gr.gr_gid = gid;
+       gr.gr_name = group;
+       xgroup_study(&gr);
+
+       /* add entry to group */
+       p = xasprintf("x:%u:", gr.gr_gid);
+       if (update_passwd(bb_path_group_file, group, p, NULL) < 0)
+               exit(EXIT_FAILURE);
+       if (ENABLE_FEATURE_CLEAN_UP)
+               free(p);
+#if ENABLE_FEATURE_SHADOWPASSWDS
+       /* Ignore errors: if file is missing we suppose admin doesn't want it */
+       update_passwd(bb_path_gshadow_file, group, "!::", NULL);
+#endif
+}
+
+/*
+ * addgroup will take a login_name as its first parameter.
+ *
+ * gid can be customized via command-line parameters.
+ * If called with two non-option arguments, addgroup
+ * will add an existing user to an existing group.
+ */
+int addgroup_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int addgroup_main(int argc UNUSED_PARAM, char **argv)
+{
+       char *group;
+       gid_t gid = 0;
+
+       /* need to be root */
+       if (geteuid()) {
+               bb_error_msg_and_die(bb_msg_perm_denied_are_you_root);
+       }
+
+       /* Syntax:
+        *  addgroup group
+        *  addgroup -g num group
+        *  addgroup user group
+        * Check for min, max and missing args */
+       opt_complementary = "-1:?2";
+       if (getopt32(argv, "g:", &group)) {
+               gid = xatoul_range(group, 0, ((unsigned long)(gid_t)ULONG_MAX) >> 1);
+       }
+       /* move past the commandline options */
+       argv += optind;
+       //argc -= optind;
+
+#if ENABLE_FEATURE_ADDUSER_TO_GROUP
+       if (argv[1]) {
+               struct group *gr;
+
+               if (option_mask32) {
+                       /* -g was there, but "addgroup -g num user group"
+                        * is a no-no */
+                       bb_show_usage();
+               }
+
+               /* check if group and user exist */
+               xuname2uid(argv[0]); /* unknown user: exit */
+               gr = xgetgrnam(argv[1]); /* unknown group: exit */
+               /* check if user is already in this group */
+               for (; *(gr->gr_mem) != NULL; (gr->gr_mem)++) {
+                       if (!strcmp(argv[0], *(gr->gr_mem))) {
+                               /* user is already in group: do nothing */
+                               return EXIT_SUCCESS;
+                       }
+               }
+               if (update_passwd(bb_path_group_file, argv[1], NULL, argv[0]) < 0) {
+                       return EXIT_FAILURE;
+               }
+# if ENABLE_FEATURE_SHADOWPASSWDS
+               update_passwd(bb_path_gshadow_file, argv[1], NULL, argv[0]);
+# endif
+       } else
+#endif /* ENABLE_FEATURE_ADDUSER_TO_GROUP */
+       {
+               die_if_bad_username(argv[0]);
+               new_group(argv[0], gid);
+
+       }
+       /* Reached only on success */
+       return EXIT_SUCCESS;
+}
diff --git a/loginutils/adduser.c b/loginutils/adduser.c
new file mode 100644 (file)
index 0000000..8a5d902
--- /dev/null
@@ -0,0 +1,164 @@
+/* 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 <beppu@codepoet.org>
+ *
+ * Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
+ */
+#include "libbb.h"
+
+#define OPT_DONT_SET_PASS  (1 << 4)
+#define OPT_SYSTEM_ACCOUNT (1 << 5)
+#define OPT_DONT_MAKE_HOME (1 << 6)
+
+/* remix */
+/* recoded such that the uid may be passed in *p */
+static void passwd_study(struct passwd *p)
+{
+       int max;
+
+       if (getpwnam(p->pw_name))
+               bb_error_msg_and_die("login '%s' is in use", p->pw_name);
+
+       if (option_mask32 & OPT_SYSTEM_ACCOUNT) {
+               p->pw_uid = 0;
+               max = 999;
+       } else {
+               p->pw_uid = 1000;
+               max = 64999;
+       }
+
+       /* check for a free uid (and maybe gid) */
+       while (getpwuid(p->pw_uid) || (p->pw_gid == (gid_t)-1 && getgrgid(p->pw_uid))) {
+               p->pw_uid++;
+               if (p->pw_uid > max)
+                       bb_error_msg_and_die("no free uids left");
+       }
+
+       if (p->pw_gid == (gid_t)-1) {
+               p->pw_gid = p->pw_uid; /* new gid = uid */
+               if (getgrnam(p->pw_name))
+                       bb_error_msg_and_die("group name '%s' is in use", p->pw_name);
+       }
+}
+
+static void addgroup_wrapper(struct passwd *p)
+{
+       char *cmd;
+
+       cmd = xasprintf("addgroup -g %u '%s'", (unsigned)p->pw_gid, p->pw_name);
+       system(cmd);
+       free(cmd);
+}
+
+static void passwd_wrapper(const char *login) NORETURN;
+
+static void passwd_wrapper(const char *login)
+{
+       static const char prog[] ALIGN1 = "passwd";
+
+       BB_EXECLP(prog, prog, login, NULL);
+       bb_error_msg_and_die("cannot execute %s, you must set password manually", prog);
+}
+
+#if ENABLE_FEATURE_ADDUSER_LONG_OPTIONS
+static const char adduser_longopts[] ALIGN1 =
+               "home\0"                Required_argument "h"
+               "gecos\0"               Required_argument "g"
+               "shell\0"               Required_argument "s"
+               "ingroup\0"             Required_argument "G"
+               "disabled-password\0"   No_argument       "D"
+               "empty-password\0"      No_argument       "D"
+               "system\0"              No_argument       "S"
+               "no-create-home\0"      No_argument       "H"
+               ;
+#endif
+
+/*
+ * 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) MAIN_EXTERNALLY_VISIBLE;
+int adduser_main(int argc UNUSED_PARAM, char **argv)
+{
+       struct passwd pw;
+       const char *usegroup = NULL;
+       char *p;
+
+#if ENABLE_FEATURE_ADDUSER_LONG_OPTIONS
+       applet_long_options = adduser_longopts;
+#endif
+
+       /* got root? */
+       if (geteuid()) {
+               bb_error_msg_and_die(bb_msg_perm_denied_are_you_root);
+       }
+
+       pw.pw_gecos = (char *)"Linux User,,,";
+       pw.pw_shell = (char *)DEFAULT_SHELL;
+       pw.pw_dir = NULL;
+
+       /* exactly one non-option arg */
+       opt_complementary = "=1";
+       getopt32(argv, "h:g:s:G:DSH", &pw.pw_dir, &pw.pw_gecos, &pw.pw_shell, &usegroup);
+       argv += optind;
+
+       /* fill in the passwd struct */
+       pw.pw_name = argv[0];
+       die_if_bad_username(pw.pw_name);
+       if (!pw.pw_dir) {
+               /* create string for $HOME if not specified already */
+               pw.pw_dir = xasprintf("/home/%s", argv[0]);
+       }
+       pw.pw_passwd = (char *)"x";
+       pw.pw_gid = usegroup ? xgroup2gid(usegroup) : -1; /* exits on failure */
+
+       /* make sure everything is kosher and setup uid && maybe gid */
+       passwd_study(&pw);
+
+       p = xasprintf("x:%u:%u:%s:%s:%s", pw.pw_uid, pw.pw_gid, pw.pw_gecos, pw.pw_dir, pw.pw_shell);
+       if (update_passwd(bb_path_passwd_file, pw.pw_name, p, NULL) < 0) {
+               return EXIT_FAILURE;
+       }
+       if (ENABLE_FEATURE_CLEAN_UP)
+               free(p);
+
+#if ENABLE_FEATURE_SHADOWPASSWDS
+       p = xasprintf("!:%u:0:99999:7:::", (unsigned)(time(NULL) / 86400)); /* sp->sp_lstchg */
+       /* ignore errors: if file is missing we suppose admin doesn't want it */
+       update_passwd(bb_path_shadow_file, pw.pw_name, p, NULL);
+       if (ENABLE_FEATURE_CLEAN_UP)
+               free(p);
+#endif
+
+       /* add to group */
+       /* addgroup should be responsible for dealing w/ gshadow */
+       /* if using a pre-existing group, don't create one */
+       if (!usegroup)
+               addgroup_wrapper(&pw);
+
+       /* clear the umask for this process so it doesn't
+        * screw up the permissions on the mkdir and chown. */
+       umask(0);
+       if (!(option_mask32 & OPT_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(pw.pw_dir, 0755)
+                || chown(pw.pw_dir, pw.pw_uid, pw.pw_gid)
+                || chmod(pw.pw_dir, 02755) /* set setgid bit on homedir */
+               ) {
+                       bb_simple_perror_msg(pw.pw_dir);
+               }
+       }
+
+       if (!(option_mask32 & OPT_DONT_SET_PASS)) {
+               /* interactively set passwd */
+               passwd_wrapper(pw.pw_name);
+       }
+
+       return 0;
+}
diff --git a/loginutils/chpasswd.c b/loginutils/chpasswd.c
new file mode 100644 (file)
index 0000000..4bffbe8
--- /dev/null
@@ -0,0 +1,71 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * chpasswd.c
+ *
+ * Written for SLIND (from passwd.c) by Alexander Shishkin <virtuoso@slind.org>
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+#include "libbb.h"
+
+#if ENABLE_GETOPT_LONG
+static const char chpasswd_longopts[] ALIGN1 =
+       "encrypted\0" No_argument "e"
+       "md5\0"       No_argument "m"
+       ;
+#endif
+
+#define OPT_ENC                1
+#define OPT_MD5                2
+
+int chpasswd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int chpasswd_main(int argc UNUSED_PARAM, char **argv)
+{
+       char *name, *pass;
+       char salt[sizeof("$N$XXXXXXXX")];
+       int opt, rc;
+       int rnd = rnd; /* we *want* it to be non-initialized! */
+
+       if (getuid())
+               bb_error_msg_and_die(bb_msg_perm_denied_are_you_root);
+
+       opt_complementary = "m--e:e--m";
+       USE_GETOPT_LONG(applet_long_options = chpasswd_longopts;)
+       opt = getopt32(argv, "em");
+
+       while ((name = xmalloc_fgetline(stdin)) != NULL) {
+               pass = strchr(name, ':');
+               if (!pass)
+                       bb_error_msg_and_die("missing new password");
+               *pass++ = '\0';
+
+               xuname2uid(name); /* dies if there is no such user */
+
+               if (!(opt & OPT_ENC)) {
+                       rnd = crypt_make_salt(salt, 1, rnd);
+                       if (opt & OPT_MD5) {
+                               strcpy(salt, "$1$");
+                               rnd = crypt_make_salt(salt + 3, 4, rnd);
+                       }
+                       pass = pw_encrypt(pass, salt, 0);
+               }
+
+               /* This is rather complex: if user is not found in /etc/shadow,
+                * we try to find & change his passwd in /etc/passwd */
+#if ENABLE_FEATURE_SHADOWPASSWDS
+               rc = update_passwd(bb_path_shadow_file, name, pass, NULL);
+               if (rc == 0) /* no lines updated, no errors detected */
+#endif
+                       rc = update_passwd(bb_path_passwd_file, name, pass, NULL);
+               /* LOGMODE_BOTH logs to syslog also */
+               logmode = LOGMODE_BOTH;
+               if (rc < 0)
+                       bb_error_msg_and_die("an error occurred updating password for %s", name);
+               if (rc)
+                       bb_info_msg("Password for '%s' changed", name);
+               logmode = LOGMODE_STDIO;
+               free(name);
+               if (!(opt & OPT_ENC))
+                       free(pass);
+       }
+       return EXIT_SUCCESS;
+}
diff --git a/loginutils/cryptpw.c b/loginutils/cryptpw.c
new file mode 100644 (file)
index 0000000..47212e1
--- /dev/null
@@ -0,0 +1,117 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * cryptpw.c - output a crypt(3)ed password to stdout.
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ *
+ * Cooked from passwd.c by Thomas Lundquist <thomasez@zelow.no>
+ * mkpasswd compatible options added by Bernhard Reutner-Fischer
+ *
+ * Licensed under GPLv2, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.h"
+
+/* Debian has 'mkpasswd' utility, manpage says:
+
+NAME
+    mkpasswd - Overfeatured front end to crypt(3)
+SYNOPSIS
+    mkpasswd PASSWORD SALT
+...
+OPTIONS
+-S, --salt=STRING
+    Use the STRING as salt. It must not  contain  prefixes  such  as
+    $1$.
+-R, --rounds=NUMBER
+    Use NUMBER rounds. This argument is ignored if the method
+    choosen does not support variable rounds. For the OpenBSD Blowfish
+    method this is the logarithm of the number of rounds.
+-m, --method=TYPE
+    Compute the password using the TYPE method. If TYPE is 'help'
+    then the available methods are printed.
+-P, --password-fd=NUM
+    Read the password from file descriptor NUM instead of using getpass(3).
+    If the file descriptor is not connected to a tty then
+    no other message than the hashed password is printed on stdout.
+-s, --stdin
+    Like --password-fd=0.
+ENVIRONMENT
+    $MKPASSWD_OPTIONS
+    A list of options which will be evaluated before the ones
+    specified on the command line.
+BUGS
+    This programs suffers of a bad case of featuritis.
+    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Very true...
+
+cryptpw was in bbox before this gem, so we retain it, and alias mkpasswd
+to cryptpw. -a option (alias for -m) came from cryptpw.
+*/
+
+int cryptpw_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int cryptpw_main(int argc UNUSED_PARAM, char **argv)
+{
+       /* $N$ + sha_salt_16_bytes + NUL */
+       char salt[3 + 16 + 1];
+       char *salt_ptr;
+       const char *opt_m, *opt_S;
+       int len;
+       int fd;
+
+#if ENABLE_GETOPT_LONG
+       static const char mkpasswd_longopts[] ALIGN1 =
+               "stdin\0"       No_argument       "s"
+               "password-fd\0" Required_argument "P"
+               "salt\0"        Required_argument "S"
+               "method\0"      Required_argument "m"
+       ;
+       applet_long_options = mkpasswd_longopts;
+#endif
+       fd = STDIN_FILENO;
+       opt_m = "d";
+       opt_S = NULL;
+       /* at most two non-option arguments; -P NUM */
+       opt_complementary = "?2:P+";
+       getopt32(argv, "sP:S:m:a:", &fd, &opt_S, &opt_m, &opt_m);
+       argv += optind;
+
+       /* have no idea how to handle -s... */
+
+       if (argv[0] && !opt_S)
+               opt_S = argv[1];
+
+       len = 2/2;
+       salt_ptr = salt;
+       if (opt_m[0] != 'd') { /* not des */
+               len = 8/2; /* so far assuming md5 */
+               *salt_ptr++ = '$';
+               *salt_ptr++ = '1';
+               *salt_ptr++ = '$';
+#if !ENABLE_USE_BB_CRYPT || ENABLE_USE_BB_CRYPT_SHA
+               if (opt_m[0] == 's') { /* sha */
+                       salt[1] = '5' + (strcmp(opt_m, "sha512") == 0);
+                       len = 16/2;
+               }
+#endif
+       }
+       if (opt_S)
+               safe_strncpy(salt_ptr, opt_S, sizeof(salt) - 3);
+       else
+               crypt_make_salt(salt_ptr, len, 0);
+
+       xmove_fd(fd, STDIN_FILENO);
+
+       puts(pw_encrypt(
+               argv[0] ? argv[0] : (
+                       /* Only mkpasswd, and only from tty, prompts.
+                        * Otherwise it is a plain read. */
+                       (isatty(STDIN_FILENO) && applet_name[0] == 'm')
+                       ? bb_ask_stdin("Password: ")
+                       : xmalloc_fgetline(stdin)
+               ),
+               salt, 1));
+
+       return EXIT_SUCCESS;
+}
diff --git a/loginutils/deluser.c b/loginutils/deluser.c
new file mode 100644 (file)
index 0000000..293e324
--- /dev/null
@@ -0,0 +1,56 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * deluser/delgroup implementation for busybox
+ *
+ * Copyright (C) 1999 by Lineo, inc. and John Beppu
+ * Copyright (C) 1999,2000,2001 by John Beppu <beppu@codepoet.org>
+ * Copyright (C) 2007 by Tito Ragusa <farmatito@tiscali.it>
+ *
+ * Licensed under GPL version 2, see file LICENSE in this tarball for details.
+ *
+ */
+#include "libbb.h"
+
+static int del_line_matching(char **args, const char *filename)
+{
+       if (ENABLE_FEATURE_DEL_USER_FROM_GROUP && args[2]) {
+               return update_passwd(filename, args[2], NULL, args[1]);
+       }
+       return update_passwd(filename, args[1], NULL, NULL);
+}
+
+int deluser_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int deluser_main(int argc, char **argv)
+{
+       if (argc != 2
+        && (!ENABLE_FEATURE_DEL_USER_FROM_GROUP
+           || (applet_name[3] != 'g' || argc != 3))
+       ) {
+               bb_show_usage();
+       }
+
+       if (geteuid())
+               bb_error_msg_and_die(bb_msg_perm_denied_are_you_root);
+
+       if ((ENABLE_FEATURE_DEL_USER_FROM_GROUP && argc != 3)
+        || ENABLE_DELUSER
+        || (ENABLE_DELGROUP && ENABLE_DESKTOP)
+       ) {
+               if (ENABLE_DELUSER
+                && (!ENABLE_DELGROUP || applet_name[3] == 'u')
+               ) {
+                       if (del_line_matching(argv, bb_path_passwd_file) < 0)
+                               return EXIT_FAILURE;
+                       if (ENABLE_FEATURE_SHADOWPASSWDS) {
+                               del_line_matching(argv, bb_path_shadow_file);
+                       }
+               } else if (ENABLE_DESKTOP && ENABLE_DELGROUP && getpwnam(argv[1]))
+                       bb_error_msg_and_die("can't remove primary group of user %s", argv[1]);
+       }
+       if (del_line_matching(argv, bb_path_group_file) < 0)
+               return EXIT_FAILURE;
+       if (ENABLE_FEATURE_SHADOWPASSWDS) {
+               del_line_matching(argv, bb_path_gshadow_file);
+       }
+       return EXIT_SUCCESS;
+}
diff --git a/loginutils/getty.c b/loginutils/getty.c
new file mode 100644 (file)
index 0000000..24a182f
--- /dev/null
@@ -0,0 +1,787 @@
+/* vi: set sw=4 ts=4: */
+/* agetty.c - another getty program for Linux. By W. Z. Venema 1989
+ * Ported to Linux by Peter Orbaek <poe@daimi.aau.dk>
+ * 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 <ear@usfirst.org> - 12/28/95
+ *
+ * 1999-02-22 Arkadiusz Mickiewicz <misiek@misiek.eu.org>
+ * - added Native Language Support
+ *
+ * 1999-05-05 Thorsten Kranzkowski <dl8bcu@gmx.net>
+ * - enable hardware flow control before displaying /etc/issue
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.h"
+#include <syslog.h>
+
+#if ENABLE_FEATURE_UTMP
+#include <utmp.h> /* updwtmp() */
+#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 */
+#include <sys/utsname.h>
+#else /* if !sysV style, wtmp/utmp code is off */
+#undef ENABLE_FEATURE_UTMP
+#undef ENABLE_FEATURE_WTMP
+#define ENABLE_FEATURE_UTMP 0
+#define ENABLE_FEATURE_WTMP 0
+#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
+#undef ANCIENT_BS_KILL_CHARS
+
+#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 MAX_SPEED       10              /* max. nr. of baud rates */
+
+/* Storage for command-line options. */
+struct options {
+       int flags;                      /* toggle switches, see below */
+       unsigned timeout;               /* time-out period */
+       const char *login;              /* login program */
+       const char *tty;                /* name of tty */
+       const char *initstring;         /* modem init string */
+       const char *issue;              /* alternative issue file */
+       int numspeed;                   /* number of baud rates to try */
+       int speeds[MAX_SPEED];          /* baud rates to be tried */
+};
+
+/* 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 */
+       /* (parity & 1): saw odd parity char with 7th bit set */
+       /* (parity & 2): saw even parity char with 7th bit set */
+       /* parity == 0: probably 7-bit, space parity? */
+       /* parity == 1: probably 7-bit, odd parity? */
+       /* parity == 2: probably 7-bit, even parity? */
+       /* parity == 3: definitely 8 bit, no parity! */
+       /* Hmm... with any value of "parity" 8 bit, no parity is possible */
+#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
+};
+
+static const char opt_string[] ALIGN1 = "I:LH:f:hil:mt:wn";
+#define F_INITSTRING    (1 << 0)        /* -I initstring is set */
+#define F_LOCAL         (1 << 1)        /* -L force local */
+#define F_FAKEHOST      (1 << 2)        /* -H fake hostname */
+#define F_CUSTISSUE     (1 << 3)        /* -f give alternative issue file */
+#define F_RTSCTS        (1 << 4)        /* -h enable RTS/CTS flow control */
+#define F_ISSUE         (1 << 5)        /* -i display /etc/issue */
+#define F_LOGIN         (1 << 6)        /* -l non-default login program */
+#define F_PARSE         (1 << 7)        /* -m process modem status messages */
+#define F_TIMEOUT       (1 << 8)        /* -t time out */
+#define F_WAITCRLF      (1 << 9)        /* -w wait for CR or LF */
+#define F_NOPROMPT      (1 << 10)       /* -n don't ask for login name */
+
+
+#define line_buf bb_common_bufsiz1
+
+/* The following is used for understandable diagnostics. */
+#ifdef DEBUGGING
+static FILE *dbf;
+#define DEBUGTERM "/dev/ttyp0"
+#define debug(...) do { fprintf(dbf, __VA_ARGS__); fflush(dbf); } while (0)
+#else
+#define debug(...) ((void)0)
+#endif
+
+
+/* bcode - convert speed string to speed code; return <= 0 on failure */
+static int bcode(const char *s)
+{
+       int value = bb_strtou(s, NULL, 10); /* yes, int is intended! */
+       if (value < 0) /* bad terminating char, overflow, etc */
+               return value;
+       return tty_value_to_baud(value);
+}
+
+/* parse_speeds - parse alternate baud rates */
+static void parse_speeds(struct options *op, char *arg)
+{
+       char *cp;
+
+       /* NB: at least one iteration is always done */
+       debug("entered parse_speeds\n");
+       while ((cp = strsep(&arg, ",")) != NULL) {
+               op->speeds[op->numspeed] = bcode(cp);
+               if (op->speeds[op->numspeed] < 0)
+                       bb_error_msg_and_die("bad speed: %s", cp);
+               /* note: arg "0" turns into speed B0 */
+               op->numspeed++;
+               if (op->numspeed > MAX_SPEED)
+                       bb_error_msg_and_die("too many alternate speeds");
+       }
+       debug("exiting parse_speeds\n");
+}
+
+/* parse_args - parse command-line arguments */
+static void parse_args(char **argv, struct options *op, char **fakehost_p)
+{
+       char *ts;
+
+       opt_complementary = "-2:t+"; /* at least 2 args; -t N */
+       op->flags = getopt32(argv, opt_string,
+               &(op->initstring), fakehost_p, &(op->issue),
+               &(op->login), &op->timeout);
+       argv += optind;
+       if (op->flags & F_INITSTRING) {
+               const char *p = op->initstring;
+               char *q;
+
+               op->initstring = q = xstrdup(p);
+               /* 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;           /* invert flag "show /etc/issue" */
+       debug("after getopt\n");
+
+       /* we loosen up a bit and accept both "baudrate tty" and "tty baudrate" */
+       op->tty = argv[0];      /* tty name */
+       ts = argv[1];           /* baud rate(s) */
+       if (isdigit(argv[0][0])) {
+               /* a number first, assume it's a speed (BSD style) */
+               op->tty = ts;   /* tty name is in argv[1] */
+               ts = argv[0];   /* baud rate(s) */
+       }
+       parse_speeds(op, ts);
+
+// TODO: if applet_name is set to "getty: TTY", bb_error_msg's get simpler!
+// grep for "%s:"
+
+       if (argv[2])
+               xsetenv("TERM", argv[2]);
+
+       debug("exiting parse_args\n");
+}
+
+/* open_tty - set up tty as standard { input, output, error } */
+static void open_tty(const char *tty)
+{
+       /* Set up new standard input, unless we are given an already opened port. */
+       if (NOT_LONE_DASH(tty)) {
+//             struct stat st;
+//             int cur_dir_fd;
+//             int fd;
+
+               /* Sanity checks... */
+//             cur_dir_fd = xopen(".", O_DIRECTORY | O_NONBLOCK);
+//             xchdir("/dev");
+//             xstat(tty, &st);
+//             if ((st.st_mode & S_IFMT) != S_IFCHR)
+//                     bb_error_msg_and_die("%s: not a character device", tty);
+
+               if (tty[0] != '/')
+                       tty = xasprintf("/dev/%s", tty); /* will leak it */
+
+               /* Open the tty as standard input. */
+               debug("open(2)\n");
+               close(0);
+               /*fd =*/ xopen(tty, O_RDWR | O_NONBLOCK); /* uses fd 0 */
+
+//             /* Restore current directory */
+//             fchdir(cur_dir_fd);
+
+               /* Open the tty as standard input, continued */
+//             xmove_fd(fd, 0);
+//             /* fd is >= cur_dir_fd, and cur_dir_fd gets closed too here: */
+//             while (fd > 2)
+//                     close(fd--);
+
+               /* Set proper protections and ownership. */
+               fchown(0, 0, 0);        /* 0:0 */
+               fchmod(0, 0620);        /* crw--w---- */
+       } else {
+               /*
+                * Standard input should already be connected to an open port. Make
+                * sure it is open for read/write.
+                */
+               if ((fcntl(0, F_GETFL) & O_RDWR) != O_RDWR)
+                       bb_error_msg_and_die("stdin is not open for read/write");
+       }
+}
+
+/* termios_init - initialize termios settings */
+static void termios_init(struct termios *tp, int speed, struct options *op)
+{
+       speed_t ispeed, ospeed;
+       /*
+        * 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); /* tcflush(0, TCIOFLUSH)? - same */
+#endif
+       ispeed = ospeed = speed;
+       if (speed == B0) {
+               /* Speed was specified as "0" on command line.
+                * Just leave it unchanged */
+               ispeed = cfgetispeed(tp);
+               ospeed = cfgetospeed(tp);
+       }
+       tp->c_cflag = CS8 | HUPCL | CREAD;
+       if (op->flags & F_LOCAL)
+               tp->c_cflag |= CLOCAL;
+       cfsetispeed(tp, ispeed);
+       cfsetospeed(tp, ospeed);
+
+       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
+
+       tcsetattr_stdin_TCSANOW(tp);
+
+       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:
+        *
+        * <junk><number><junk>
+        *
+        * 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 */
+       tcsetattr_stdin_TCSANOW(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 = safe_read(STDIN_FILENO, buf, size_buf - 1);
+       if (nread > 0) {
+               buf[nread] = '\0';
+               for (bp = buf; bp < buf + nread; bp++) {
+                       if (isdigit(*bp)) {
+                               speed = bcode(bp);
+                               if (speed > 0) {
+                                       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;
+       tcsetattr_stdin_TCSANOW(tp);
+}
+
+/* do_prompt - show login prompt, optionally preceded by /etc/issue contents */
+static void do_prompt(struct options *op)
+{
+#ifdef ISSUE
+       print_login_issue(op->issue, op->tty);
+#endif
+       print_login_prompt();
+}
+
+#ifdef HANDLE_ALLCAPS
+/* all_is_upcase - string contains upper case without lower case */
+/* returns 1 if true, 0 if false */
+static int all_is_upcase(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 BREAK, logname on success */
+static char *get_logname(char *logname, unsigned size_logname,
+               struct options *op, struct chardata *cp)
+{
+       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 */
+               "\010\040\010",                 /* 8 bit no parity */
+       };
+
+       /* NB: *cp is pre-initialized with init_chardata */
+
+       /* Flush pending input (esp. after parsing or switching the baud rate). */
+       sleep(1);
+       ioctl(0, TCFLSH, TCIFLUSH); /* tcflush(0, TCIOFLUSH)? - same */
+
+       /* 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);
+
+               /* 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(STDIN_FILENO, &c, 1) < 1) {
+                               if (errno == EINTR || errno == EIO)
+                                       exit(EXIT_SUCCESS);
+                               bb_perror_msg_and_die("%s: read", op->tty);
+                       }
+
+                       /* BREAK. If we have speeds to try,
+                        * return NULL (will switch speeds and return here) */
+                       if (c == '\0' && op->numspeed > 1)
+                               return NULL;
+
+                       /* Do parity bit handling. */
+                       if (!(op->flags & F_LOCAL) && (c & 0x80)) {       /* "parity" bit on? */
+                               bits = 1;
+                               mask = 1;
+                               while (mask & 0x7f) {
+                                       if (mask & c)
+                                               bits++; /* count "1" bits */
+                                       mask <<= 1;
+                               }
+                               /* ... |= 2 - even, 1 - odd */
+                               cp->parity |= 2 - (bits & 1);
+                       }
+
+                       /* Do erase, kill and end-of-line processing. */
+                       ascval = c & 0x7f;
+                       switch (ascval) {
+                       case CR:
+                       case NL:
+                               *bp = '\0';             /* terminate logname */
+                               cp->eol = ascval;       /* set end-of-line char */
+                               break;
+                       case BS:
+                       case DEL:
+#ifdef ANCIENT_BS_KILL_CHARS
+                       case '#':
+#endif
+                               cp->erase = ascval;     /* set erase character */
+                               if (bp > logname) {
+                                       full_write(STDOUT_FILENO, erase[cp->parity], 3);
+                                       bp--;
+                               }
+                               break;
+                       case CTL('U'):
+#ifdef ANCIENT_BS_KILL_CHARS
+                       case '@':
+#endif
+                               cp->kill = ascval;      /* set kill character */
+                               while (bp > logname) {
+                                       full_write(STDOUT_FILENO, erase[cp->parity], 3);
+                                       bp--;
+                               }
+                               break;
+                       case CTL('D'):
+                               exit(EXIT_SUCCESS);
+                       default:
+                               if (!isprint(ascval)) {
+                                       /* ignore garbage characters */
+                               } else if ((int)(bp - logname) >= size_logname - 1) {
+                                       bb_error_msg_and_die("%s: input overrun", op->tty);
+                               } else {
+                                       full_write(STDOUT_FILENO, &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 = all_is_upcase(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 */
+// I bet most people go here - they use only 7-bit chars in usernames....
+               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;
+// FIXME: wtf? case 3: we saw both even and odd 8-bit bytes -
+// it's probably some umlauts etc, but definitely NOT 7-bit!!!
+// Entire parity detection madness here just begs for deletion...
+               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 */
+       /* It's tcsetattr_stdin_TCSANOW() + error check */
+       ioctl_or_perror_and_die(0, TCSETS, tp, "%s: TCSETS", op->tty);
+}
+
+#if ENABLE_FEATURE_UTMP
+static void touch(const char *filename)
+{
+       if (access(filename, R_OK | W_OK) == -1)
+               close(open(filename, O_WRONLY | O_CREAT, 0664));
+}
+
+/* update_utmp - update our utmp entry */
+static void update_utmp(const char *line, char *fakehost)
+{
+       struct utmp ut;
+       struct utmp *utp;
+       int mypid = getpid();
+
+       /* In case we won't find an entry below... */
+       memset(&ut, 0, sizeof(ut));
+       safe_strncpy(ut.ut_id, line + 3, sizeof(ut.ut_id));
+
+       /*
+        * 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.
+        */
+       touch(_PATH_UTMP);
+
+       utmpname(_PATH_UTMP);
+       setutent();
+       while ((utp = getutent()) != NULL) {
+               if (utp->ut_type == INIT_PROCESS && utp->ut_pid == mypid) {
+                       memcpy(&ut, utp, sizeof(ut));
+                       break;
+               }
+       }
+
+       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));
+       ut.ut_tv.tv_sec = time(NULL);
+       ut.ut_type = LOGIN_PROCESS;
+       ut.ut_pid = mypid;
+
+       pututline(&ut);
+       endutent();
+
+#if ENABLE_FEATURE_WTMP
+       touch(bb_path_wtmp_file);
+       updwtmp(bb_path_wtmp_file, &ut);
+#endif
+}
+#endif /* CONFIG_FEATURE_UTMP */
+
+int getty_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int getty_main(int argc UNUSED_PARAM, char **argv)
+{
+       int n;
+       char *fakehost = NULL;          /* Fake hostname for ut_host */
+       char *logname;                  /* 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;
+
+       chardata = init_chardata;
+
+       memset(&options, 0, sizeof(options));
+       options.login = _PATH_LOGIN;    /* default login program */
+       options.tty = "tty1";           /* default tty line */
+       options.initstring = "";        /* modem init string */
+#ifdef ISSUE
+       options.issue = ISSUE;          /* default issue file */
+#endif
+
+       /* Parse command-line arguments. */
+       parse_args(argv, &options, &fakehost);
+
+       logmode = LOGMODE_NONE;
+
+       /* Create new session, lose controlling tty, if any */
+       /* docs/ctty.htm says:
+        * "This is allowed only when the current process
+        *  is not a process group leader" - is this a problem? */
+       setsid();
+       /* close stdio, and stray descriptors, just in case */
+       n = xopen(bb_dev_null, O_RDWR);
+       /* dup2(n, 0); - no, we need to handle "getty - 9600" too */
+       xdup2(n, 1);
+       xdup2(n, 2);
+       while (n > 2)
+               close(n--);
+
+       /* Logging. We want special flavor of error_msg_and_die */
+       die_sleep = 10;
+       msg_eol = "\r\n";
+       /* most likely will internally use fd #3 in CLOEXEC mode: */
+       openlog(applet_name, LOG_PID, LOG_AUTH);
+       logmode = LOGMODE_BOTH;
+
+#ifdef DEBUGGING
+       dbf = xfopen_for_write(DEBUGTERM);
+       for (n = 1; argv[n]; n++) {
+               debug(argv[n]);
+               debug("\n");
+       }
+#endif
+
+       /* Open the tty as standard input, if it is not "-" */
+       /* If it's not "-" and not taken yet, it will become our ctty */
+       debug("calling open_tty\n");
+       open_tty(options.tty);
+       ndelay_off(0);
+       debug("duping\n");
+       xdup2(0, 1);
+       xdup2(0, 2);
+
+       /*
+        * 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.
+        */
+       /* tcgetattr() + error check */
+       ioctl_or_perror_and_die(0, TCGETS, &termios, "%s: TCGETS", options.tty);
+
+#ifdef __linux__
+// FIXME: do we need this? Otherwise "-" case seems to be broken...
+       // /* Forcibly make fd 0 our controlling tty, even if another session
+       //  * has it as a ctty. (Another session loses ctty). */
+       // ioctl(0, TIOCSCTTY, (void*)1);
+       /* Make ourself a foreground process group within our session */
+       tcsetpgrp(0, getpid());
+#endif
+
+#if ENABLE_FEATURE_UTMP
+       /* Update the utmp file. This tty is ours now! */
+       update_utmp(options.tty, fakehost);
+#endif
+
+       /* Initialize the termios settings (raw mode, eight-bit, blocking i/o). */
+       debug("calling termios_init\n");
+       termios_init(&termios, options.speeds[0], &options);
+
+       /* Write the modem init string and DON'T flush the buffers */
+       if (options.flags & F_INITSTRING) {
+               debug("writing init string\n");
+               /* todo: use xwrite_str? */
+               full_write(STDOUT_FILENO, options.initstring, strlen(options.initstring));
+       }
+
+       /* Optionally detect the baud rate from the modem status message */
+       debug("before autobaud\n");
+       if (options.flags & F_PARSE)
+               auto_baud(line_buf, sizeof(line_buf), &termios);
+
+       /* Set the optional timer */
+       alarm(options.timeout); /* if 0, alarm is not set */
+
+       /* Optionally wait for CR or LF before writing /etc/issue */
+       if (options.flags & F_WAITCRLF) {
+               char ch;
+
+               debug("waiting for cr-lf\n");
+               while (safe_read(STDIN_FILENO, &ch, 1) == 1) {
+                       debug("read %x\n", (unsigned char)ch);
+                       ch &= 0x7f;                     /* strip "parity bit" */
+                       if (ch == '\n' || ch == '\r')
+                               break;
+               }
+       }
+
+       logname = NULL;
+       if (!(options.flags & F_NOPROMPT)) {
+               /* NB:termios_init already set line speed
+                * to options.speeds[0] */
+               int baud_index = 0;
+
+               while (1) {
+                       /* Read the login name. */
+                       debug("reading login name\n");
+                       logname = get_logname(line_buf, sizeof(line_buf),
+                                       &options, &chardata);
+                       if (logname)
+                               break;
+                       /* we are here only if options.numspeed > 1 */
+                       baud_index = (baud_index + 1) % options.numspeed;
+                       cfsetispeed(&termios, options.speeds[baud_index]);
+                       cfsetospeed(&termios, options.speeds[baud_index]);
+                       tcsetattr_stdin_TCSANOW(&termios);
+               }
+       }
+
+       /* Disable timer. */
+       alarm(0);
+
+       /* Finalize the termios settings. */
+       termios_final(&options, &termios, &chardata);
+
+       /* Now the newline character should be properly written. */
+       full_write(STDOUT_FILENO, "\n", 1);
+
+       /* Let the login program take care of password validation. */
+       /* We use PATH because we trust that root doesn't set "bad" PATH,
+        * and getty is not suid-root applet. */
+       /* With -n, logname == NULL, and login will ask for username instead */
+       BB_EXECLP(options.login, options.login, "--", logname, NULL);
+       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 (file)
index 0000000..d57d529
--- /dev/null
@@ -0,0 +1,512 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.h"
+#include <syslog.h>
+#include <utmp.h>
+#include <sys/resource.h>
+
+#if ENABLE_SELINUX
+#include <selinux/selinux.h>  /* for is_selinux_enabled()  */
+#include <selinux/get_context_list.h> /* for get_default_context() */
+#include <selinux/flask.h> /* for security class definitions  */
+#endif
+
+#if ENABLE_PAM
+/* PAM may include <locale.h>. We may need to undefine bbox's stub define: */
+#undef setlocale
+/* For some obscure reason, PAM is not in pam/xxx, but in security/xxx.
+ * Apparently they like to confuse people. */
+#include <security/pam_appl.h>
+#include <security/pam_misc.h>
+static const struct pam_conv conv = {
+       misc_conv,
+       NULL
+};
+#endif
+
+enum {
+       TIMEOUT = 60,
+       EMPTY_USERNAME_COUNT = 10,
+       USERNAME_SIZE = 32,
+       TTYNAME_SIZE = 32,
+};
+
+static char* short_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 void read_or_build_utent(struct utmp *utptr, int run_by_root)
+{
+       struct utmp *ut;
+       pid_t pid = getpid();
+
+       setutent();
+
+       /* First, try to find a valid utmp entry for this process.  */
+       /* If there is one, just use it.  */
+       while ((ut = getutent()) != NULL)
+               if (ut->ut_pid == pid && ut->ut_line[0] && ut->ut_id[0]
+                && (ut->ut_type == LOGIN_PROCESS || ut->ut_type == USER_PROCESS)
+               ) {
+                       *utptr = *ut; /* struct copy */
+                       if (run_by_root) /* why only for root? */
+                               memset(utptr->ut_host, 0, sizeof(utptr->ut_host));
+                       return;
+               }
+
+// Why? Do we require non-root to exec login from another
+// former login process (e.g. login shell)? Some login's have
+// login shells as children, so it won't work...
+//     if (!run_by_root)
+//             bb_error_msg_and_die("no utmp entry found");
+
+       /* Otherwise create a new one.  */
+       memset(utptr, 0, sizeof(*utptr));
+       utptr->ut_type = LOGIN_PROCESS;
+       utptr->ut_pid = pid;
+       strncpy(utptr->ut_line, short_tty, sizeof(utptr->ut_line));
+       /* This one is only 4 chars wide. Try to fit something
+        * remotely meaningful by skipping "tty"... */
+       strncpy(utptr->ut_id, short_tty + 3, sizeof(utptr->ut_id));
+       strncpy(utptr->ut_user, "LOGIN", sizeof(utptr->ut_user));
+       utptr->ut_tv.tv_sec = time(NULL);
+}
+
+/*
+ * 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(struct utmp *utptr, const char *username)
+{
+       utptr->ut_type = USER_PROCESS;
+       strncpy(utptr->ut_user, username, sizeof(utptr->ut_user));
+       utptr->ut_tv.tv_sec = time(NULL);
+       /* other fields already filled in by read_or_build_utent above */
+       setutent();
+       pututline(utptr);
+       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, utptr);
+#endif
+}
+#else /* !ENABLE_FEATURE_UTMP */
+#define read_or_build_utent(utptr, run_by_root) ((void)0)
+#define write_utent(utptr, username) ((void)0)
+#endif /* !ENABLE_FEATURE_UTMP */
+
+#if ENABLE_FEATURE_NOLOGIN
+static void die_if_nologin(void)
+{
+       FILE *fp;
+       int c;
+       int empty = 1;
+
+       fp = fopen_for_read("/etc/nologin");
+       if (!fp) /* assuming it does not exist */
+               return;
+
+       while ((c = getc(fp)) != EOF) {
+               if (c == '\n')
+                       bb_putchar('\r');
+               bb_putchar(c);
+               empty = 0;
+       }
+       if (empty)
+               puts("\r\nSystem closed for routine maintenance\r");
+
+       fclose(fp);
+       fflush(NULL);
+       /* Users say that they do need this prior to exit: */
+       tcdrain(STDOUT_FILENO);
+       exit(EXIT_FAILURE);
+}
+#else
+static ALWAYS_INLINE void die_if_nologin(void) {}
+#endif
+
+#if ENABLE_FEATURE_SECURETTY && !ENABLE_PAM
+static int check_securetty(void)
+{
+       char *buf = (char*)"/etc/securetty"; /* any non-NULL is ok */
+       parser_t *parser = config_open2("/etc/securetty", fopen_for_read);
+       while (config_read(parser, &buf, 1, 1, "# \t", PARSE_NORMAL)) {
+               if (strcmp(buf, short_tty) == 0)
+                       break;
+               buf = NULL;
+       }
+       config_close(parser);
+       /* buf != NULL here if config file was not found, empty
+        * or line was found which equals short_tty */
+       return buf != NULL;
+}
+#else
+static ALWAYS_INLINE int check_securetty(void) { return 1; }
+#endif
+
+#if ENABLE_SELINUX
+static void initselinux(char *username, char *full_tty,
+                                               security_context_t *user_sid)
+{
+       security_context_t old_tty_sid, new_tty_sid;
+
+       if (!is_selinux_enabled())
+               return;
+
+       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
+
+#if ENABLE_LOGIN_SCRIPTS
+static void run_login_script(struct passwd *pw, char *full_tty)
+{
+       char *t_argv[2];
+
+       t_argv[0] = getenv("LOGIN_PRE_SUID_SCRIPT");
+       if (t_argv[0]) {
+               t_argv[1] = NULL;
+               xsetenv("LOGIN_TTY", full_tty);
+               xsetenv("LOGIN_USER", pw->pw_name);
+               xsetenv("LOGIN_UID", utoa(pw->pw_uid));
+               xsetenv("LOGIN_GID", utoa(pw->pw_gid));
+               xsetenv("LOGIN_SHELL", pw->pw_shell);
+               spawn_and_wait(t_argv); /* NOMMU-friendly */
+               unsetenv("LOGIN_TTY");
+               unsetenv("LOGIN_USER");
+               unsetenv("LOGIN_UID");
+               unsetenv("LOGIN_GID");
+               unsetenv("LOGIN_SHELL");
+       }
+}
+#else
+void run_login_script(struct passwd *pw, char *full_tty);
+#endif
+
+static void get_username_or_die(char *buf, int size_buf)
+{
+       int c, cntdown;
+
+       cntdown = EMPTY_USERNAME_COUNT;
+ prompt:
+       print_login_prompt();
+       /* skip whitespace */
+       do {
+               c = getchar();
+               if (c == EOF) exit(EXIT_FAILURE);
+               if (c == '\n') {
+                       if (!--cntdown) exit(EXIT_FAILURE);
+                       goto prompt;
+               }
+       } while (isspace(c));
+
+       *buf++ = c;
+       if (!fgets(buf, size_buf-2, stdin))
+               exit(EXIT_FAILURE);
+       if (!strchr(buf, '\n'))
+               exit(EXIT_FAILURE);
+       while (isgraph(*buf)) buf++;
+       *buf = '\0';
+}
+
+static void motd(void)
+{
+       int fd;
+
+       fd = open(bb_path_motd_file, O_RDONLY);
+       if (fd >= 0) {
+               fflush(stdout);
+               bb_copyfd_eof(fd, STDOUT_FILENO);
+               close(fd);
+       }
+}
+
+static void alarm_handler(int sig UNUSED_PARAM)
+{
+       /* 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 */
+       ndelay_on(1);
+       printf("\r\nLogin timed out after %d seconds\r\n", TIMEOUT);
+       fflush(stdout);
+       /* unix API is brain damaged regarding O_NONBLOCK,
+        * we should undo it, or else we can affect other processes */
+       ndelay_off(1);
+       _exit(EXIT_SUCCESS);
+}
+
+int login_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int login_main(int argc UNUSED_PARAM, char **argv)
+{
+       enum {
+               LOGIN_OPT_f = (1<<0),
+               LOGIN_OPT_h = (1<<1),
+               LOGIN_OPT_p = (1<<2),
+       };
+       char *fromhost;
+       char username[USERNAME_SIZE];
+       const char *tmp;
+       int run_by_root;
+       unsigned opt;
+       int count = 0;
+       struct passwd *pw;
+       char *opt_host = opt_host; /* for compiler */
+       char *opt_user = opt_user; /* for compiler */
+       char *full_tty;
+       USE_SELINUX(security_context_t user_sid = NULL;)
+       USE_FEATURE_UTMP(struct utmp utent;)
+#if ENABLE_PAM
+       int pamret;
+       pam_handle_t *pamh;
+       const char *pamuser;
+       const char *failed_msg;
+       struct passwd pwdstruct;
+       char pwdbuf[256];
+#endif
+
+       username[0] = '\0';
+       signal(SIGALRM, alarm_handler);
+       alarm(TIMEOUT);
+
+       /* More of suid paranoia if called by non-root: */
+       /* Clear dangerous stuff, set PATH */
+       run_by_root = !sanitize_env_if_suid();
+
+       /* Mandatory paranoia for suid applet:
+        * ensure that fd# 0,1,2 are opened (at least to /dev/null)
+        * and any extra open fd's are closed.
+        * (The name of the function is misleading. Not daemonizing here.) */
+       bb_daemonize_or_rexec(DAEMON_ONLY_SANITIZE | DAEMON_CLOSE_EXTRA_FDS, NULL);
+
+       opt = getopt32(argv, "f:h:p", &opt_user, &opt_host);
+       if (opt & LOGIN_OPT_f) {
+               if (!run_by_root)
+                       bb_error_msg_and_die("-f is for root only");
+               safe_strncpy(username, opt_user, sizeof(username));
+       }
+       argv += optind;
+       if (argv[0]) /* user from command line (getty) */
+               safe_strncpy(username, argv[0], sizeof(username));
+
+       /* Let's find out and memorize our tty */
+       if (!isatty(STDIN_FILENO) || !isatty(STDOUT_FILENO) || !isatty(STDERR_FILENO))
+               return EXIT_FAILURE;            /* Must be a terminal */
+       full_tty = xmalloc_ttyname(STDIN_FILENO);
+       if (!full_tty)
+               full_tty = xstrdup("UNKNOWN");
+       short_tty = full_tty;
+       if (strncmp(full_tty, "/dev/", 5) == 0)
+               short_tty += 5;
+
+       read_or_build_utent(&utent, run_by_root);
+
+       if (opt & LOGIN_OPT_h) {
+               USE_FEATURE_UTMP(safe_strncpy(utent.ut_host, opt_host, sizeof(utent.ut_host));)
+               fromhost = xasprintf(" on '%s' from '%s'", short_tty, opt_host);
+       } else {
+               fromhost = xasprintf(" on '%s'", short_tty);
+       }
+
+       /* Was breaking "login <username>" from shell command line: */
+       /*bb_setpgrp();*/
+
+       openlog(applet_name, LOG_PID | LOG_CONS, LOG_AUTH);
+
+       while (1) {
+               /* flush away any type-ahead (as getty does) */
+               ioctl(0, TCFLSH, TCIFLUSH);
+
+               if (!username[0])
+                       get_username_or_die(username, sizeof(username));
+
+#if ENABLE_PAM
+               pamret = pam_start("login", username, &conv, &pamh);
+               if (pamret != PAM_SUCCESS) {
+                       failed_msg = "start";
+                       goto pam_auth_failed;
+               }
+               /* set TTY (so things like securetty work) */
+               pamret = pam_set_item(pamh, PAM_TTY, short_tty);
+               if (pamret != PAM_SUCCESS) {
+                       failed_msg = "set_item(TTY)";
+                       goto pam_auth_failed;
+               }
+               pamret = pam_authenticate(pamh, 0);
+               if (pamret != PAM_SUCCESS) {
+                       failed_msg = "authenticate";
+                       goto pam_auth_failed;
+                       /* TODO: or just "goto auth_failed"
+                        * since user seems to enter wrong password
+                        * (in this case pamret == 7)
+                        */
+               }
+               /* check that the account is healthy */
+               pamret = pam_acct_mgmt(pamh, 0);
+               if (pamret != PAM_SUCCESS) {
+                       failed_msg = "acct_mgmt";
+                       goto pam_auth_failed;
+               }
+               /* read user back */
+               pamuser = NULL;
+               /* gcc: "dereferencing type-punned pointer breaks aliasing rules..."
+                * thus we cast to (void*) */
+               if (pam_get_item(pamh, PAM_USER, (void*)&pamuser) != PAM_SUCCESS) {
+                       failed_msg = "get_item(USER)";
+                       goto pam_auth_failed;
+               }
+               if (!pamuser || !pamuser[0])
+                       goto auth_failed;
+               safe_strncpy(username, pamuser, sizeof(username));
+               /* Don't use "pw = getpwnam(username);",
+                * PAM is said to be capable of destroying static storage
+                * used by getpwnam(). We are using safe(r) function */
+               pw = NULL;
+               getpwnam_r(username, &pwdstruct, pwdbuf, sizeof(pwdbuf), &pw);
+               if (!pw)
+                       goto auth_failed;
+               pamret = pam_open_session(pamh, 0);
+               if (pamret != PAM_SUCCESS) {
+                       failed_msg = "open_session";
+                       goto pam_auth_failed;
+               }
+               pamret = pam_setcred(pamh, PAM_ESTABLISH_CRED);
+               if (pamret != PAM_SUCCESS) {
+                       failed_msg = "setcred";
+                       goto pam_auth_failed;
+               }
+               break; /* success, continue login process */
+
+ pam_auth_failed:
+               bb_error_msg("pam_%s call failed: %s (%d)", failed_msg,
+                                       pam_strerror(pamh, pamret), pamret);
+               safe_strncpy(username, "UNKNOWN", sizeof(username));
+#else /* not PAM */
+               pw = getpwnam(username);
+               if (!pw) {
+                       strcpy(username, "UNKNOWN");
+                       goto fake_it;
+               }
+
+               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;
+ fake_it:
+               /* authorization takes place here */
+               if (correct_password(pw))
+                       break;
+#endif /* ENABLE_PAM */
+ auth_failed:
+               opt &= ~LOGIN_OPT_f;
+               bb_do_delay(FAIL_DELAY);
+               /* TODO: doesn't sound like correct English phrase to me */
+               puts("Login incorrect");
+               if (++count == 3) {
+                       syslog(LOG_WARNING, "invalid password for '%s'%s",
+                                               username, fromhost);
+                       return EXIT_FAILURE;
+               }
+               username[0] = '\0';
+       } /* while (1) */
+
+       alarm(0);
+       /* We can ignore /etc/nologin if we are logging in as root,
+        * it doesn't matter whether we are run by root or not */
+       if (pw->pw_uid != 0)
+               die_if_nologin();
+
+       write_utent(&utent, username);
+
+       USE_SELINUX(initselinux(username, full_tty, &user_sid));
+
+       /* 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);
+
+       /* We trust environment only if we run by root */
+       if (ENABLE_LOGIN_SCRIPTS && run_by_root)
+               run_login_script(pw, full_tty);
+
+       change_identity(pw);
+       tmp = pw->pw_shell;
+       if (!tmp || !*tmp)
+               tmp = DEFAULT_SHELL;
+       /* setup_environment params: shell, clear_env, change_env, pw */
+       setup_environment(tmp, !(opt & LOGIN_OPT_p), 1, pw);
+
+       motd();
+
+       if (pw->pw_uid == 0)
+               syslog(LOG_INFO, "root login%s", fromhost);
+
+       /* well, a simple setexeccon() here would do the job as well,
+        * but let's play the game for now */
+       USE_SELINUX(set_current_security_context(user_sid);)
+
+       // util-linux login also does:
+       // /* start new session */
+       // setsid();
+       // /* TIOCSCTTY: steal tty from other process group */
+       // if (ioctl(0, TIOCSCTTY, 1)) error_msg...
+       // BBox login used to do this (see above):
+       // bb_setpgrp();
+       // If this stuff is really needed, add it and explain why!
+
+       /* Set signals to defaults */
+       /* Non-ignored signals revert to SIG_DFL on exec anyway */
+       /*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 we leave SIGINT etc enabled or disabled? */
+       signal(SIGINT, SIG_DFL);
+
+       /* Exec login shell with no additional parameters */
+       run_shell(tmp, 1, NULL, NULL);
+
+       /* return EXIT_FAILURE; - not reached */
+}
diff --git a/loginutils/passwd.c b/loginutils/passwd.c
new file mode 100644 (file)
index 0000000..7b93713
--- /dev/null
@@ -0,0 +1,204 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+#include "libbb.h"
+#include <syslog.h>
+
+static void nuke_str(char *str)
+{
+       if (str) memset(str, 0, strlen(str));
+}
+
+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*)"";
+       char *newp = NULL;
+       char *cp = NULL;
+       char *ret = NULL; /* failure so far */
+
+       if (myuid && pw->pw_passwd[0]) {
+               char *encrypted;
+
+               orig = bb_ask_stdin("Old password:"); /* returns ptr to static */
+               if (!orig)
+                       goto err_ret;
+               encrypted = pw_encrypt(orig, pw->pw_passwd, 1); /* returns malloced str */
+               if (strcmp(encrypted, 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;
+               }
+               if (ENABLE_FEATURE_CLEAN_UP) free(encrypted);
+       }
+       orig = xstrdup(orig); /* or else bb_ask_stdin() will destroy it */
+       newp = bb_ask_stdin("New password:"); /* returns ptr to static */
+       if (!newp)
+               goto err_ret;
+       newp = xstrdup(newp); /* we are going to bb_ask_stdin() again, so save it */
+       if (ENABLE_FEATURE_PASSWD_WEAK_CHECK
+        && obscure(orig, newp, pw) && myuid)
+               goto err_ret; /* non-root is not allowed to have weak passwd */
+
+       cp = bb_ask_stdin("Retype password:");
+       if (!cp)
+               goto err_ret;
+       if (strcmp(cp, newp)) {
+               puts("Passwords don't match");
+               goto err_ret;
+       }
+
+       crypt_make_salt(salt, 1, 0); /* des */
+       if (algo) { /* MD5 */
+               strcpy(salt, "$1$");
+               crypt_make_salt(salt + 3, 4, 0);
+       }
+       /* pw_encrypt returns malloced str */
+       ret = pw_encrypt(newp, salt, 1);
+       /* 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(cp);
+       return ret;
+}
+
+int passwd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int passwd_main(int argc UNUSED_PARAM, 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;
+       int rc;
+       const char *opt_a = "";
+       const char *filename;
+       char *myname;
+       char *name;
+       char *newp;
+       struct passwd *pw;
+       uid_t myuid;
+       struct rlimit rlimit_fsize;
+       char c;
+#if ENABLE_FEATURE_SHADOWPASSWDS
+       /* Using _r function to avoid pulling in static buffers */
+       struct spwd spw;
+       char buffer[256];
+#endif
+
+       logmode = LOGMODE_BOTH;
+       openlog(applet_name, 0, LOG_AUTH);
+       opt = getopt32(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();
+       /* -l, -u, -d require root priv and username argument */
+       if ((opt & OPT_lud) && (myuid || !argv[0]))
+               bb_show_usage();
+
+       /* Will complain and die if username not found */
+       myname = xstrdup(xuid2uname(myuid));
+       name = argv[0] ? argv[0] : myname;
+
+       pw = xgetpwnam(name);
+       if (myuid && pw->pw_uid != myuid) {
+               /* LOGMODE_BOTH */
+               bb_error_msg_and_die("%s can't change password for %s", myname, name);
+       }
+
+#if ENABLE_FEATURE_SHADOWPASSWDS
+       {
+               /* getspnam_r may return 0 yet set result to NULL.
+                * At least glibc 2.4 does this. Be extra paranoid here. */
+               struct spwd *result = NULL;
+               if (getspnam_r(pw->pw_name, &spw, buffer, sizeof(buffer), &result)
+                || !result || strcmp(result->sp_namp, pw->pw_name) != 0) {
+                       /* LOGMODE_BOTH */
+                       bb_error_msg("no record of %s in %s, using %s",
+                                       name, bb_path_shadow_file,
+                                       bb_path_passwd_file);
+               } else {
+                       pw->pw_passwd = result->sp_pwdp;
+               }
+       }
+#endif
+
+       /* 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 '!' */
+               /* pw->pw_passwd points to static storage,
+                * strdup'ing to avoid nasty surprizes */
+               newp = xstrdup(&pw->pw_passwd[1]);
+       } else if (opt & OPT_delete) {
+               //newp = xstrdup("");
+               newp = (char*)"";
+       }
+
+       rlimit_fsize.rlim_cur = rlimit_fsize.rlim_max = 512L * 30000;
+       setrlimit(RLIMIT_FSIZE, &rlimit_fsize);
+       bb_signals(0
+               + (1 << SIGHUP)
+               + (1 << SIGINT)
+               + (1 << SIGQUIT)
+               , SIG_IGN);
+       umask(077);
+       xsetuid(0);
+
+#if ENABLE_FEATURE_SHADOWPASSWDS
+       filename = bb_path_shadow_file;
+       rc = update_passwd(bb_path_shadow_file, name, newp, NULL);
+       if (rc == 0) /* no lines updated, no errors detected */
+#endif
+       {
+               filename = bb_path_passwd_file;
+               rc = update_passwd(bb_path_passwd_file, name, newp, NULL);
+       }
+       /* LOGMODE_BOTH */
+       if (rc < 0)
+               bb_error_msg_and_die("cannot update password file %s",
+                               filename);
+       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 (file)
index 0000000..de8c18d
--- /dev/null
@@ -0,0 +1,100 @@
+/* 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 "libbb.h"
+#include <syslog.h>
+
+#define SU_OPT_mp (3)
+#define SU_OPT_l (4)
+
+int su_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int su_main(int argc UNUSED_PARAM, char **argv)
+{
+       unsigned flags;
+       char *opt_shell = NULL;
+       char *opt_command = NULL;
+       const char *opt_username = "root";
+       struct passwd *pw;
+       uid_t cur_uid = getuid();
+       const char *tty;
+       char *old_user;
+
+       flags = getopt32(argv, "mplc:s:", &opt_command, &opt_shell);
+       //argc -= optind;
+       argv += optind;
+
+       if (argv[0] && LONE_DASH(argv[0])) {
+               flags |= SU_OPT_l;
+               argv++;
+       }
+
+       /* get user if specified */
+       if (argv[0]) {
+               opt_username = argv[0];
+               argv++;
+       }
+
+       if (ENABLE_FEATURE_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 = xmalloc_ttyname(2) ? : "none";
+               openlog(applet_name, 0, LOG_AUTH);
+       }
+
+       pw = xgetpwnam(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_FEATURE_SU_SYSLOG)
+                       syslog(LOG_NOTICE, "%c %s %s:%s",
+                               '+', tty, old_user, opt_username);
+       } else {
+               if (ENABLE_FEATURE_SU_SYSLOG)
+                       syslog(LOG_NOTICE, "%c %s %s:%s",
+                               '-', tty, old_user, opt_username);
+               bb_error_msg_and_die("incorrect password");
+       }
+
+       if (ENABLE_FEATURE_CLEAN_UP && ENABLE_FEATURE_SU_SYSLOG) {
+               closelog();
+               free(old_user);
+       }
+
+       if (!opt_shell && (flags & SU_OPT_mp))
+               opt_shell = getenv("SHELL");
+
+#if ENABLE_FEATURE_SU_CHECKS_SHELLS
+       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 = NULL;
+       }
+#endif
+       if (!opt_shell)
+               opt_shell = pw->pw_shell;
+
+       change_identity(pw);
+       /* setup_environment params: shell, clear_env, change_env, 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**)argv);
+
+       /* return EXIT_FAILURE; - not reached */
+}
diff --git a/loginutils/sulogin.c b/loginutils/sulogin.c
new file mode 100644 (file)
index 0000000..4ffefe9
--- /dev/null
@@ -0,0 +1,117 @@
+/* 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 "libbb.h"
+#include <syslog.h>
+
+//static void catchalarm(int UNUSED_PARAM junk)
+//{
+//     exit(EXIT_FAILURE);
+//}
+
+
+int sulogin_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int sulogin_main(int argc UNUSED_PARAM, char **argv)
+{
+       char *cp;
+       int timeout = 0;
+       struct passwd *pwd;
+       const char *shell;
+#if ENABLE_FEATURE_SHADOWPASSWDS
+       /* Using _r function to avoid pulling in static buffers */
+       char buffer[256];
+       struct spwd spw;
+#endif
+
+       logmode = LOGMODE_BOTH;
+       openlog(applet_name, 0, LOG_AUTH);
+
+       opt_complementary = "t+"; /* -t N */
+       getopt32(argv, "t:", &timeout);
+       argv += optind;
+
+       if (argv[0]) {
+               close(0);
+               close(1);
+               dup(xopen(argv[0], O_RDWR));
+               close(2);
+               dup(0);
+       }
+
+       /* Malicious use like "sulogin /dev/sda"? */
+       if (!isatty(0) || !isatty(1) || !isatty(2)) {
+               logmode = LOGMODE_SYSLOG;
+               bb_error_msg_and_die("not a tty");
+       }
+
+       /* Clear dangerous stuff, set PATH */
+       sanitize_env_if_suid();
+
+// bb_ask() already handles this
+//     signal(SIGALRM, catchalarm);
+
+       pwd = getpwuid(0);
+       if (!pwd) {
+               goto auth_error;
+       }
+
+#if ENABLE_FEATURE_SHADOWPASSWDS
+       {
+               /* getspnam_r may return 0 yet set result to NULL.
+                * At least glibc 2.4 does this. Be extra paranoid here. */
+               struct spwd *result = NULL;
+               int r = getspnam_r(pwd->pw_name, &spw, buffer, sizeof(buffer), &result);
+               if (r || !result) {
+                       goto auth_error;
+               }
+               pwd->pw_passwd = result->sp_pwdp;
+       }
+#endif
+
+       while (1) {
+               char *encrypted;
+               int r;
+
+               /* cp points to a static buffer that is zeroed every time */
+               cp = bb_ask(STDIN_FILENO, 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;
+               }
+               encrypted = pw_encrypt(cp, pwd->pw_passwd, 1);
+               r = strcmp(encrypted, pwd->pw_passwd);
+               free(encrypted);
+               if (r == 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;
+       }
+       /* Exec login shell with no additional parameters. Never returns. */
+       run_shell(shell, 1, NULL, NULL);
+
+ 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 (file)
index 0000000..85f489c
--- /dev/null
@@ -0,0 +1,101 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * vlock implementation for busybox
+ *
+ * Copyright (C) 2000 by spoon <spoon@ix.netcom.com>
+ * Written by spoon <spon@ix.netcom.com>
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+/* Shoutz to Michael K. Johnson <johnsonm@redhat.com>, 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 <sys/vt.h>
+#include "libbb.h"
+
+static void release_vt(int signo UNUSED_PARAM)
+{
+       /* If -a, param is 0, which means:
+        * "no, kernel, we don't allow console switch away from us!" */
+       ioctl(STDIN_FILENO, VT_RELDISP, (unsigned long) !option_mask32);
+}
+
+static void acquire_vt(int signo UNUSED_PARAM)
+{
+       /* ACK to kernel that switch to console is successful */
+       ioctl(STDIN_FILENO, VT_RELDISP, VT_ACKACQ);
+}
+
+int vlock_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int vlock_main(int argc UNUSED_PARAM, char **argv)
+{
+       struct vt_mode vtm;
+       struct termios term;
+       struct termios oterm;
+       struct vt_mode ovtm;
+       struct passwd *pw;
+
+       pw = xgetpwuid(getuid());
+       opt_complementary = "=0"; /* no params! */
+       getopt32(argv, "a");
+
+       /* Ignore some signals so that we don't get killed by them */
+       bb_signals(0
+               + (1 << SIGTSTP)
+               + (1 << SIGTTIN)
+               + (1 << SIGTTOU)
+               + (1 << SIGHUP )
+               + (1 << SIGCHLD) /* paranoia :) */
+               + (1 << SIGQUIT)
+               + (1 << SIGINT )
+               , SIG_IGN);
+
+       /* We will use SIGUSRx for console switch control: */
+       /* 1: set handlers */
+       signal_SA_RESTART_empty_mask(SIGUSR1, release_vt);
+       signal_SA_RESTART_empty_mask(SIGUSR2, acquire_vt);
+       /* 2: unmask them */
+       sig_unblock(SIGUSR1);
+       sig_unblock(SIGUSR2);
+
+       /* Revert stdin/out to our controlling tty
+        * (or die if we have none) */
+       xmove_fd(xopen(CURRENT_TTY, O_RDWR), STDIN_FILENO);
+       xdup2(STDIN_FILENO, STDOUT_FILENO);
+
+       xioctl(STDIN_FILENO, VT_GETMODE, &vtm);
+       ovtm = vtm;
+       /* "console switches are controlled by us, not kernel!" */
+       vtm.mode = VT_PROCESS;
+       vtm.relsig = SIGUSR1;
+       vtm.acqsig = SIGUSR2;
+       ioctl(STDIN_FILENO, 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_TCSANOW(&term);
+
+       do {
+               printf("Virtual console%s locked by %s.\n",
+                               option_mask32 /*o_lock_all*/ ? "s" : "",
+                               pw->pw_name);
+               if (correct_password(pw)) {
+                       break;
+               }
+               bb_do_delay(FAIL_DELAY);
+               puts("Password incorrect");
+       } while (1);
+
+       ioctl(STDIN_FILENO, VT_SETMODE, &ovtm);
+       tcsetattr_stdin_TCSANOW(&oterm);
+       fflush_stdout_and_exit(EXIT_SUCCESS);
+}
diff --git a/mailutils/Config.in b/mailutils/Config.in
new file mode 100644 (file)
index 0000000..519d562
--- /dev/null
@@ -0,0 +1,53 @@
+menu "Mail Utilities"
+
+config MAKEMIME
+       bool "makemime"
+       default n
+       help
+         Create MIME-formatted messages.
+
+config FEATURE_MIME_CHARSET
+       string "Default charset"
+       default "us-ascii"
+       depends on MAKEMIME || REFORMIME || SENDMAIL
+       help
+         Default charset of the message.
+
+config POPMAILDIR
+       bool "popmaildir"
+       default n
+       help
+         Simple yet powerful POP3 mail popper. Delivers content
+         of remote mailboxes to local Maildir.
+
+config FEATURE_POPMAILDIR_DELIVERY
+       bool "Allow message filters and custom delivery program"
+       default n
+       depends on POPMAILDIR
+       help
+         Allow to use a custom program to filter the content
+         of the message before actual delivery (-F "prog [args...]").
+         Allow to use a custom program for message actual delivery
+         (-M "prog [args...]").
+
+config REFORMIME
+       bool "reformime"
+       default n
+       help
+         Parse MIME-formatted messages.
+
+config FEATURE_REFORMIME_COMPAT
+       bool "Accept and ignore options other than -x and -X"
+       default y
+       depends on REFORMIME
+       help
+         Accept (for compatibility only) and ignore options
+         other than -x and -X.
+
+config SENDMAIL
+       bool "sendmail"
+       default n
+       help
+         Barebones sendmail.
+
+endmenu
diff --git a/mailutils/Kbuild b/mailutils/Kbuild
new file mode 100644 (file)
index 0000000..871e879
--- /dev/null
@@ -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_MAKEMIME)     += mime.o mail.o
+lib-$(CONFIG_POPMAILDIR)   += popmaildir.o mail.o
+lib-$(CONFIG_REFORMIME)    += mime.o mail.o
+lib-$(CONFIG_SENDMAIL)     += sendmail.o mail.o
diff --git a/mailutils/mail.c b/mailutils/mail.c
new file mode 100644 (file)
index 0000000..68883ff
--- /dev/null
@@ -0,0 +1,248 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * helper routines
+ *
+ * Copyright (C) 2008 by Vladimir Dronnikov <dronnikov@gmail.com>
+ *
+ * Licensed under GPLv2, see file LICENSE in this tarball for details.
+ */
+#include "libbb.h"
+#include "mail.h"
+
+static void kill_helper(void)
+{
+       if (G.helper_pid > 0) {
+               kill(G.helper_pid, SIGTERM);
+               G.helper_pid = 0;
+       }
+}
+
+// generic signal handler
+static void signal_handler(int signo)
+{
+#define err signo
+       if (SIGALRM == signo) {
+               kill_helper();
+               bb_error_msg_and_die("timed out");
+       }
+
+       // SIGCHLD. reap zombies
+       if (safe_waitpid(G.helper_pid, &err, WNOHANG) > 0) {
+               if (WIFSIGNALED(err))
+                       bb_error_msg_and_die("helper killed by signal %u", WTERMSIG(err));
+               if (WIFEXITED(err)) {
+                       G.helper_pid = 0;
+                       if (WEXITSTATUS(err))
+                               bb_error_msg_and_die("helper exited (%u)", WEXITSTATUS(err));
+               }
+       }
+#undef err
+}
+
+void FAST_FUNC launch_helper(const char **argv)
+{
+       // setup vanilla unidirectional pipes interchange
+       int i;
+       int pipes[4];
+
+       xpipe(pipes);
+       xpipe(pipes + 2);
+
+       // NB: handler must be installed before vfork
+       bb_signals(0
+               + (1 << SIGCHLD)
+               + (1 << SIGALRM)
+               , signal_handler);
+
+       G.helper_pid = vfork();
+       if (G.helper_pid < 0)
+               bb_perror_msg_and_die("vfork");
+
+       i = (!G.helper_pid) * 2; // for parent:0, for child:2
+       close(pipes[i + 1]); // 1 or 3 - closing one write end
+       close(pipes[2 - i]); // 2 or 0 - closing one read end
+       xmove_fd(pipes[i], STDIN_FILENO); // 0 or 2 - using other read end
+       xmove_fd(pipes[3 - i], STDOUT_FILENO); // 3 or 1 - other write end
+
+       if (!G.helper_pid) {
+               // child: try to execute connection helper
+               // NB: SIGCHLD & SIGALRM revert to SIG_DFL on exec
+               BB_EXECVP(*argv, (char **)argv);
+               _exit(127);
+       }
+
+       // parent
+       // check whether child is alive
+       //redundant:signal_handler(SIGCHLD);
+       // child seems OK -> parent goes on
+       atexit(kill_helper);
+}
+
+const FAST_FUNC char *command(const char *fmt, const char *param)
+{
+       const char *msg = fmt;
+       if (timeout)
+               alarm(timeout);
+       if (msg) {
+               msg = xasprintf(fmt, param);
+               printf("%s\r\n", msg);
+       }
+       fflush(stdout);
+       return msg;
+}
+
+// NB: parse_url can modify url[] (despite const), but only if '@' is there
+/*
+static char FAST_FUNC *parse_url(char *url, char **user, char **pass)
+{
+       // parse [user[:pass]@]host
+       // return host
+       char *s = strchr(url, '@');
+       *user = *pass = NULL;
+       if (s) {
+               *s++ = '\0';
+               *user = url;
+               url = s;
+               s = strchr(*user, ':');
+               if (s) {
+                       *s++ = '\0';
+                       *pass = s;
+               }
+       }
+       return url;
+}
+*/
+
+void FAST_FUNC encode_base64(char *fname, const char *text, const char *eol)
+{
+       enum {
+               SRC_BUF_SIZE = 45,  /* This *MUST* be a multiple of 3 */
+               DST_BUF_SIZE = 4 * ((SRC_BUF_SIZE + 2) / 3),
+       };
+
+#define src_buf text
+       FILE *fp = fp;
+       ssize_t len = len;
+       char dst_buf[DST_BUF_SIZE + 1];
+
+       if (fname) {
+               fp = (NOT_LONE_DASH(fname)) ? xfopen_for_read(fname) : (FILE *)text;
+               src_buf = bb_common_bufsiz1;
+       // N.B. strlen(NULL) segfaults!
+       } else if (text) {
+               // though we do not call uuencode(NULL, NULL) explicitly
+               // still we do not want to break things suddenly
+               len = strlen(text);
+       } else
+               return;
+
+       while (1) {
+               size_t size;
+               if (fname) {
+                       size = fread((char *)src_buf, 1, SRC_BUF_SIZE, fp);
+                       if ((ssize_t)size < 0)
+                               bb_perror_msg_and_die(bb_msg_read_error);
+               } else {
+                       size = len;
+                       if (len > SRC_BUF_SIZE)
+                               size = SRC_BUF_SIZE;
+               }
+               if (!size)
+                       break;
+               // encode the buffer we just read in
+               bb_uuencode(dst_buf, src_buf, size, bb_uuenc_tbl_base64);
+               if (fname) {
+                       printf("%s\n", eol);
+               } else {
+                       src_buf += size;
+                       len -= size;
+               }
+               fwrite(dst_buf, 1, 4 * ((size + 2) / 3), stdout);
+       }
+       if (fname && NOT_LONE_DASH(fname))
+               fclose(fp);
+#undef src_buf
+}
+
+void FAST_FUNC decode_base64(FILE *src_stream, FILE *dst_stream)
+{
+       int term_count = 1;
+
+       while (1) {
+               char translated[4];
+               int count = 0;
+
+               while (count < 4) {
+                       char *table_ptr;
+                       int ch;
+
+                       /* Get next _valid_ character.
+                        * global vector bb_uuenc_tbl_base64[] contains this string:
+                        * "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=\n"
+                        */
+                       do {
+                               ch = fgetc(src_stream);
+                               if (ch == EOF) {
+                                       bb_error_msg_and_die(bb_msg_read_error);
+                               }
+                               // - means end of MIME section
+                               if ('-' == ch) {
+                                       // push it back
+                                       ungetc(ch, src_stream);
+                                       return;
+                               }
+                               table_ptr = strchr(bb_uuenc_tbl_base64, ch);
+                       } while (table_ptr == NULL);
+
+                       /* Convert encoded character to decimal */
+                       ch = table_ptr - bb_uuenc_tbl_base64;
+
+                       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;
+                               }
+                               term_count = 1;
+                               continue;
+                       } else {
+                               translated[count] = ch;
+                               count++;
+                               term_count = 0;
+                       }
+               }
+
+               /* Merge 6 bit chars to 8 bit */
+               if (count > 1) {
+                       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);
+               }
+       }
+}
+
+
+/*
+ * get username and password from a file descriptor
+ */
+void FAST_FUNC get_cred_or_die(int fd)
+{
+       if (isatty(fd)) {
+               G.user = xstrdup(bb_ask(fd, /* timeout: */ 0, "User: "));
+               G.pass = xstrdup(bb_ask(fd, /* timeout: */ 0, "Password: "));
+       } else {
+               G.user = xmalloc_reads(fd, /* pfx: */ NULL, /* maxsize: */ NULL);
+               G.pass = xmalloc_reads(fd, /* pfx: */ NULL, /* maxsize: */ NULL);
+       }
+       if (!G.user || !*G.user || !G.pass)
+               bb_error_msg_and_die("no username or password");
+}
diff --git a/mailutils/mail.h b/mailutils/mail.h
new file mode 100644 (file)
index 0000000..bb747c4
--- /dev/null
@@ -0,0 +1,35 @@
+
+struct globals {
+       pid_t helper_pid;
+       unsigned timeout;
+       unsigned opts;
+       char *user;
+       char *pass;
+       FILE *fp0; // initial stdin
+       char *opt_charset;
+       char *content_type;
+};
+
+#define G (*ptr_to_globals)
+#define timeout         (G.timeout  )
+#define opts            (G.opts     )
+//#define user            (G.user     )
+//#define pass            (G.pass     )
+//#define fp0             (G.fp0      )
+//#define opt_charset     (G.opt_charset)
+//#define content_type    (G.content_type)
+#define INIT_G() do { \
+       SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
+       G.opt_charset = (char *)CONFIG_FEATURE_MIME_CHARSET; \
+       G.content_type = (char *)"text/plain"; \
+} while (0)
+
+//char FAST_FUNC *parse_url(char *url, char **user, char **pass);
+
+void FAST_FUNC launch_helper(const char **argv);
+void FAST_FUNC get_cred_or_die(int fd);
+
+const FAST_FUNC char *command(const char *fmt, const char *param);
+
+void FAST_FUNC encode_base64(char *fname, const char *text, const char *eol);
+void FAST_FUNC decode_base64(FILE *src_stream, FILE *dst_stream);
diff --git a/mailutils/mime.c b/mailutils/mime.c
new file mode 100644 (file)
index 0000000..bda727b
--- /dev/null
@@ -0,0 +1,354 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * makemime: create MIME-encoded message
+ * reformime: parse MIME-encoded message
+ *
+ * Copyright (C) 2008 by Vladimir Dronnikov <dronnikov@gmail.com>
+ *
+ * Licensed under GPLv2, see file LICENSE in this tarball for details.
+ */
+#include "libbb.h"
+#include "mail.h"
+
+/*
+  makemime -c type [-o file] [-e encoding] [-C charset] [-N name] \
+                   [-a "Header: Contents"] file
+           -m [ type ] [-o file] [-e encoding] [-a "Header: Contents"] file
+           -j [-o file] file1 file2
+           @file
+
+   file:  filename    - read or write from filename
+          -           - read or write from stdin or stdout
+          &n          - read or write from file descriptor n
+          \( opts \)  - read from child process, that generates [ opts ]
+
+Options:
+
+  -c type         - create a new MIME section from "file" with this
+                    Content-Type: (default is application/octet-stream).
+  -C charset      - MIME charset of a new text/plain section.
+  -N name         - MIME content name of the new mime section.
+  -m [ type ]     - create a multipart mime section from "file" of this
+                    Content-Type: (default is multipart/mixed).
+  -e encoding     - use the given encoding (7bit, 8bit, quoted-printable,
+                    or base64), instead of guessing.  Omit "-e" and use
+                    -c auto to set Content-Type: to text/plain or
+                    application/octet-stream based on picked encoding.
+  -j file1 file2  - join mime section file2 to multipart section file1.
+  -o file         - write ther result to file, instead of stdout (not
+                    allowed in child processes).
+  -a header       - prepend an additional header to the output.
+
+  @file - read all of the above options from file, one option or
+          value on each line.
+*/
+
+int makemime_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int makemime_main(int argc UNUSED_PARAM, char **argv)
+{
+       llist_t *opt_headers = NULL, *l;
+       const char *opt_output;
+#define boundary opt_output
+
+       enum {
+               OPT_c = 1 << 0,         // Content-Type:
+               OPT_e = 1 << 1,         // Content-Transfer-Encoding. Ignored. Assumed base64
+               OPT_o = 1 << 2,         // output to
+               OPT_C = 1 << 3,         // charset
+               OPT_N = 1 << 4,         // COMPAT
+               OPT_a = 1 << 5,         // additional headers
+               OPT_m = 1 << 6,         // COMPAT
+               OPT_j = 1 << 7,         // COMPAT
+       };
+
+       INIT_G();
+
+       // parse options
+       opt_complementary = "a::";
+       opts = getopt32(argv,
+               "c:e:o:C:N:a:m:j:",
+               &G.content_type, NULL, &opt_output, &G.opt_charset, NULL, &opt_headers, NULL, NULL
+       );
+       //argc -= optind;
+       argv += optind;
+
+       // respect -o output
+       if (opts & OPT_o)
+               freopen(opt_output, "w", stdout);
+
+       // no files given on command line? -> use stdin
+       if (!*argv)
+               *--argv = (char *)"-";
+
+       // put additional headers
+       for (l = opt_headers; l; l = l->link)
+               puts(l->data);
+
+       // make a random string -- it will delimit message parts
+       srand(monotonic_us());
+       boundary = xasprintf("%d-%d-%d", rand(), rand(), rand());
+
+       // put multipart header
+       printf(
+               "Mime-Version: 1.0\n"
+               "Content-Type: multipart/mixed; boundary=\"%s\"\n"
+               , boundary
+       );
+
+       // put attachments
+       while (*argv) {
+               printf(
+                       "\n--%s\n"
+                       "Content-Type: %s; charset=%s\n"
+                       "Content-Disposition: inline; filename=\"%s\"\n"
+                       "Content-Transfer-Encoding: base64\n"
+                       , boundary
+                       , G.content_type
+                       , G.opt_charset
+                       , bb_get_last_path_component_strip(*argv)
+               );
+               encode_base64(*argv++, (const char *)stdin, "");
+       }
+
+       // put multipart footer
+       printf("\n--%s--\n" "\n", boundary);
+
+       return EXIT_SUCCESS;
+#undef boundary
+}
+
+static const char *find_token(const char *const string_array[], const char *key, const char *defvalue)
+{
+       const char *r = NULL;
+       for (int i = 0; string_array[i] != 0; i++) {
+               if (strcasecmp(string_array[i], key) == 0) {
+                       r = (char *)string_array[i+1];
+                       break;
+               }
+       }
+       return (r) ? r : defvalue;
+}
+
+static const char *xfind_token(const char *const string_array[], const char *key)
+{
+       const char *r = find_token(string_array, key, NULL);
+       if (r)
+               return r;
+       bb_error_msg_and_die("header: %s", key);
+}
+
+enum {
+       OPT_x = 1 << 0,
+       OPT_X = 1 << 1,
+#if ENABLE_FEATURE_REFORMIME_COMPAT
+       OPT_d = 1 << 2,
+       OPT_e = 1 << 3,
+       OPT_i = 1 << 4,
+       OPT_s = 1 << 5,
+       OPT_r = 1 << 6,
+       OPT_c = 1 << 7,
+       OPT_m = 1 << 8,
+       OPT_h = 1 << 9,
+       OPT_o = 1 << 10,
+       OPT_O = 1 << 11,
+#endif
+};
+
+static int parse(const char *boundary, char **argv)
+{
+       char *line, *s, *p;
+       const char *type;
+       int boundary_len = strlen(boundary);
+       const char *delims = " ;\"\t\r\n";
+       const char *uniq;
+       int ntokens;
+       const char *tokens[32]; // 32 is enough
+
+       // prepare unique string pattern
+       uniq = xasprintf("%%llu.%u.%s", (unsigned)getpid(), safe_gethostname());
+
+//bb_info_msg("PARSE[%s]", terminator);
+
+       while ((line = xmalloc_fgets_str(stdin, "\r\n\r\n")) != NULL) {
+
+               // seek to start of MIME section
+               // N.B. to avoid false positives let us seek to the _last_ occurance
+               p = NULL;
+               s = line;
+               while ((s=strcasestr(s, "Content-Type:")) != NULL)
+                       p = s++;
+               if (!p)
+                       goto next;
+//bb_info_msg("L[%s]", p);
+
+               // split to tokens
+               // TODO: strip of comments which are of form: (comment-text)
+               ntokens = 0;
+               tokens[ntokens] = NULL;
+               for (s = strtok(p, delims); s; s = strtok(NULL, delims)) {
+                       tokens[ntokens] = s;
+                       if (ntokens < ARRAY_SIZE(tokens) - 1)
+                               ntokens++;
+//bb_info_msg("L[%d][%s]", ntokens, s);
+               }
+               tokens[ntokens] = NULL;
+//bb_info_msg("N[%d]", ntokens);
+
+               // analyse tokens
+               type = find_token(tokens, "Content-Type:", "text/plain");
+//bb_info_msg("T[%s]", type);
+               if (0 == strncasecmp(type, "multipart/", 10)) {
+                       if (0 == strcasecmp(type+10, "mixed")) {
+                               parse(xfind_token(tokens, "boundary="), argv);
+                       } else
+                               bb_error_msg_and_die("no support of content type '%s'", type);
+               } else {
+                       pid_t pid = pid;
+                       int rc;
+                       FILE *fp;
+                       // fetch charset
+                       const char *charset = find_token(tokens, "charset=", CONFIG_FEATURE_MIME_CHARSET);
+                       // fetch encoding
+                       const char *encoding = find_token(tokens, "Content-Transfer-Encoding:", "7bit");
+                       // compose target filename
+                       char *filename = (char *)find_token(tokens, "filename=", NULL);
+                       if (!filename)
+                               filename = xasprintf(uniq, monotonic_us());
+                       else
+                               filename = bb_get_last_path_component_strip(xstrdup(filename));
+
+                       // start external helper, if any
+                       if (opts & OPT_X) {
+                               int fd[2];
+                               xpipe(fd);
+                               pid = vfork();
+                               if (0 == pid) {
+                                       // child reads from fd[0]
+                                       xdup2(fd[0], STDIN_FILENO);
+                                       close(fd[0]); close(fd[1]);
+                                       xsetenv("CONTENT_TYPE", type);
+                                       xsetenv("CHARSET", charset);
+                                       xsetenv("ENCODING", encoding);
+                                       xsetenv("FILENAME", filename);
+                                       BB_EXECVP(*argv, argv);
+                                       _exit(EXIT_FAILURE);
+                               }
+                               // parent dumps to fd[1]
+                               close(fd[0]);
+                               fp = fdopen(fd[1], "w");
+                               signal(SIGPIPE, SIG_IGN); // ignore EPIPE
+                       // or create a file for dump
+                       } else {
+                               char *fname = xasprintf("%s%s", *argv, filename);
+                               fp = xfopen_for_write(fname);
+                               free(fname);
+                       }
+
+                       // housekeeping
+                       free(filename);
+
+                       // dump to fp
+                       if (0 == strcasecmp(encoding, "base64")) {
+                               decode_base64(stdin, fp);
+                       } else if (0 != strcasecmp(encoding, "7bit")
+                               && 0 != strcasecmp(encoding, "8bit")) {
+                               // quoted-printable, binary, user-defined are unsupported so far
+                               bb_error_msg_and_die("no support of encoding '%s'", encoding);
+                       } else {
+                               // N.B. we have written redundant \n. so truncate the file
+                               // The following weird 2-tacts reading technique is due to
+                               // we have to not write extra \n at the end of the file
+                               // In case of -x option we could truncate the resulting file as
+                               // fseek(fp, -1, SEEK_END);
+                               // if (ftruncate(fileno(fp), ftell(fp)))
+                               //      bb_perror_msg("ftruncate");
+                               // But in case of -X we have to be much more careful. There is
+                               // no means to truncate what we already have sent to the helper.
+                               p = xmalloc_fgets_str(stdin, "\r\n");
+                               while (p) {
+                                       if ((s = xmalloc_fgets_str(stdin, "\r\n")) == NULL)
+                                               break;
+                                       if ('-' == s[0] && '-' == s[1]
+                                               && 0 == strncmp(s+2, boundary, boundary_len))
+                                               break;
+                                       fputs(p, fp);
+                                       p = s;
+                               }
+
+/*
+                               while ((s = xmalloc_fgetline_str(stdin, "\r\n")) != NULL) {
+                                       if ('-' == s[0] && '-' == s[1]
+                                               && 0 == strncmp(s+2, boundary, boundary_len))
+                                               break;
+                                       fprintf(fp, "%s\n", s);
+                               }
+                               // N.B. we have written redundant \n. so truncate the file
+                               fseek(fp, -1, SEEK_END);
+                               if (ftruncate(fileno(fp), ftell(fp)))
+                                       bb_perror_msg("ftruncate");
+*/
+                       }
+                       fclose(fp);
+
+                       // finalize helper
+                       if (opts & OPT_X) {
+                               signal(SIGPIPE, SIG_DFL);
+                               // exit if helper exited >0
+                               rc = wait4pid(pid);
+                               if (rc)
+                                       return rc+20;
+                       }
+
+                       // check multipart finalized
+                       if (s && '-' == s[2+boundary_len] && '-' == s[2+boundary_len+1]) {
+                               free(line);
+                               break;
+                       }
+               }
+ next:
+               free(line);
+       }
+
+//bb_info_msg("ENDPARSE[%s]", boundary);
+
+       return EXIT_SUCCESS;
+}
+
+/*
+Usage: reformime [options]
+    -d - parse a delivery status notification.
+    -e - extract contents of MIME section.
+    -x - extract MIME section to a file.
+    -X - pipe MIME section to a program.
+    -i - show MIME info.
+    -s n.n.n.n - specify MIME section.
+    -r - rewrite message, filling in missing MIME headers.
+    -r7 - also convert 8bit/raw encoding to quoted-printable, if possible.
+    -r8 - also convert quoted-printable encoding to 8bit, if possible.
+    -c charset - default charset for rewriting, -o, and -O.
+    -m [file] [file]... - create a MIME message digest.
+    -h "header" - decode RFC 2047-encoded header.
+    -o "header" - encode unstructured header using RFC 2047.
+    -O "header" - encode address list header using RFC 2047.
+*/
+
+int reformime_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int reformime_main(int argc UNUSED_PARAM, char **argv)
+{
+       const char *opt_prefix = "";
+
+       INIT_G();
+
+       // parse options
+       // N.B. only -x and -X are supported so far
+       opt_complementary = "x--X:X--x" USE_FEATURE_REFORMIME_COMPAT(":m::");
+       opts = getopt32(argv,
+               "x:X" USE_FEATURE_REFORMIME_COMPAT("deis:r:c:m:h:o:O:"),
+               &opt_prefix
+               USE_FEATURE_REFORMIME_COMPAT(, NULL, NULL, &G.opt_charset, NULL, NULL, NULL, NULL)
+       );
+       //argc -= optind;
+       argv += optind;
+
+       return parse("", (opts & OPT_X) ? argv : (char **)&opt_prefix);
+}
diff --git a/mailutils/popmaildir.c b/mailutils/popmaildir.c
new file mode 100644 (file)
index 0000000..1a72b87
--- /dev/null
@@ -0,0 +1,235 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * popmaildir: a simple yet powerful POP3 client
+ * Delivers contents of remote mailboxes to local Maildir
+ *
+ * Inspired by original utility by Nikola Vladov
+ *
+ * Copyright (C) 2008 by Vladimir Dronnikov <dronnikov@gmail.com>
+ *
+ * Licensed under GPLv2, see file LICENSE in this tarball for details.
+ */
+#include "libbb.h"
+#include "mail.h"
+
+static void pop3_checkr(const char *fmt, const char *param, char **ret)
+{
+       const char *msg = command(fmt, param);
+       char *answer = xmalloc_fgetline(stdin);
+       if (answer && '+' == answer[0]) {
+               if (timeout)
+                       alarm(0);
+               if (ret) {
+                       // skip "+OK "
+                       memmove(answer, answer + 4, strlen(answer) - 4);
+                       *ret = answer;
+               } else
+                       free(answer);
+               return;
+       }
+       bb_error_msg_and_die("%s failed: %s", msg, answer);
+}
+
+static void pop3_check(const char *fmt, const char *param)
+{
+       pop3_checkr(fmt, param, NULL);
+}
+
+int popmaildir_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int popmaildir_main(int argc UNUSED_PARAM, char **argv)
+{
+       char *buf;
+       unsigned nmsg;
+       char *hostname;
+       pid_t pid;
+       const char *retr;
+#if ENABLE_FEATURE_POPMAILDIR_DELIVERY
+       const char *delivery;
+#endif
+       unsigned opt_nlines = 0;
+
+       enum {
+               OPT_b = 1 << 0,         // -b binary mode. Ignored
+               OPT_d = 1 << 1,         // -d,-dd,-ddd debug. Ignored
+               OPT_m = 1 << 2,         // -m show used memory. Ignored
+               OPT_V = 1 << 3,         // -V version. Ignored
+               OPT_c = 1 << 4,         // -c use tcpclient. Ignored
+               OPT_a = 1 << 5,         // -a use APOP protocol
+               OPT_s = 1 << 6,         // -s skip authorization
+               OPT_T = 1 << 7,         // -T get messages with TOP instead with RETR
+               OPT_k = 1 << 8,         // -k keep retrieved messages on the server
+               OPT_t = 1 << 9,         // -t90 set timeout to 90 sec
+               OPT_R = 1 << 10,        // -R20000 remove old messages on the server >= 20000 bytes (requires -k). Ignored
+               OPT_Z = 1 << 11,        // -Z11-23 remove messages from 11 to 23 (dangerous). Ignored
+               OPT_L = 1 << 12,        // -L50000 not retrieve new messages >= 50000 bytes. Ignored
+               OPT_H = 1 << 13,        // -H30 type first 30 lines of a message; (-L12000 -H30). Ignored
+               OPT_M = 1 << 14,        // -M\"program arg1 arg2 ...\"; deliver by program. Treated like -F
+               OPT_F = 1 << 15,        // -F\"program arg1 arg2 ...\"; filter by program. Treated like -M
+       };
+
+       // init global variables
+       INIT_G();
+
+       // parse options
+       opt_complementary = "-1:dd:t+:R+:L+:H+";
+       opts = getopt32(argv,
+               "bdmVcasTkt:" "R:Z:L:H:" USE_FEATURE_POPMAILDIR_DELIVERY("M:F:"),
+               &timeout, NULL, NULL, NULL, &opt_nlines
+               USE_FEATURE_POPMAILDIR_DELIVERY(, &delivery, &delivery) // we treat -M and -F the same
+       );
+       //argc -= optind;
+       argv += optind;
+
+       // get auth info
+       if (!(opts & OPT_s))
+               get_cred_or_die(STDIN_FILENO);
+
+       // goto maildir
+       xchdir(*argv++);
+
+       // launch connect helper, if any
+       if (*argv)
+               launch_helper((const char **)argv);
+
+       // get server greeting
+       pop3_checkr(NULL, NULL, &buf);
+
+       // authenticate (if no -s given)
+       if (!(opts & OPT_s)) {
+               // server supports APOP and we want it?
+               if ('<' == buf[0] && (opts & OPT_a)) {
+                       union { // save a bit of stack
+                               md5_ctx_t ctx;
+                               char hex[16 * 2 + 1];
+                       } md5;
+                       uint32_t res[16 / 4];
+
+                       char *s = strchr(buf, '>');
+                       if (s)
+                               s[1] = '\0';
+                       // get md5 sum of "<stamp>password" string
+                       md5_begin(&md5.ctx);
+                       md5_hash(buf, strlen(buf), &md5.ctx);
+                       md5_hash(G.pass, strlen(G.pass), &md5.ctx);
+                       md5_end(res, &md5.ctx);
+                       *bin2hex(md5.hex, (char*)res, 16) = '\0';
+                       // APOP
+                       s = xasprintf("%s %s", G.user, md5.hex);
+                       pop3_check("APOP %s", s);
+                       free(s);
+                       free(buf);
+               // server ignores APOP -> use simple text authentication
+               } else {
+                       // USER
+                       pop3_check("USER %s", G.user);
+                       // PASS
+                       pop3_check("PASS %s", G.pass);
+               }
+       }
+
+       // get mailbox statistics
+       pop3_checkr("STAT", NULL, &buf);
+
+       // prepare message filename suffix
+       hostname = safe_gethostname();
+       pid = getpid();
+
+       // get messages counter
+       // NOTE: we don't use xatou(buf) since buf is "nmsg nbytes"
+       // we only need nmsg and atoi is just exactly what we need
+       // if atoi fails to convert buf into number it returns 0
+       // in this case the following loop simply will not be executed
+       nmsg = atoi(buf);
+       free(buf);
+
+       // loop through messages
+       retr = (opts & OPT_T) ? xasprintf("TOP %%u %u", opt_nlines) : "RETR %u";
+       for (; nmsg; nmsg--) {
+
+               char *filename;
+               char *target;
+               char *answer;
+               FILE *fp;
+#if ENABLE_FEATURE_POPMAILDIR_DELIVERY
+               int rc;
+#endif
+               // generate unique filename
+               filename  = xasprintf("tmp/%llu.%u.%s",
+                       monotonic_us(), (unsigned)pid, hostname);
+
+               // retrieve message in ./tmp/ unless filter is specified
+               pop3_check(retr, (const char *)(ptrdiff_t)nmsg);
+
+#if ENABLE_FEATURE_POPMAILDIR_DELIVERY
+               // delivery helper ordered? -> setup pipe
+               if (opts & (OPT_F|OPT_M)) {
+                       // helper will have $FILENAME set to filename
+                       xsetenv("FILENAME", filename);
+                       fp = popen(delivery, "w");
+                       unsetenv("FILENAME");
+                       if (!fp) {
+                               bb_perror_msg("delivery helper");
+                               break;
+                       }
+               } else
+#endif
+               // create and open file filename
+               fp = xfopen_for_write(filename);
+
+               // copy stdin to fp (either filename or delivery helper)
+               while ((answer = xmalloc_fgets_str(stdin, "\r\n")) != NULL) {
+                       char *s = answer;
+                       if ('.' == answer[0]) {
+                               if ('.' == answer[1])
+                                       s++;
+                               else if ('\r' == answer[1] && '\n' == answer[2] && '\0' == answer[3])
+                                       break;
+                       }
+                       //*strchrnul(s, '\r') = '\n';
+                       fputs(s, fp);
+                       free(answer);
+               }
+
+#if ENABLE_FEATURE_POPMAILDIR_DELIVERY
+               // analyse delivery status
+               if (opts & (OPT_F|OPT_M)) {
+                       rc = pclose(fp);
+                       if (99 == rc) // 99 means bail out
+                               break;
+//                     if (rc) // !0 means skip to the next message
+                               goto skip;
+//                     // 0 means continue
+               } else {
+                       // close filename
+                       fclose(fp);
+               }
+#endif
+
+               // delete message from server
+               if (!(opts & OPT_k))
+                       pop3_check("DELE %u", (const char*)(ptrdiff_t)nmsg);
+
+               // atomically move message to ./new/
+               target = xstrdup(filename);
+               strncpy(target, "new", 3);
+               // ... or just stop receiving on failure
+               if (rename_or_warn(filename, target))
+                       break;
+               free(target);
+
+#if ENABLE_FEATURE_POPMAILDIR_DELIVERY
+ skip:
+#endif
+               free(filename);
+       }
+
+       // Bye
+       pop3_check("QUIT", NULL);
+
+       if (ENABLE_FEATURE_CLEAN_UP) {
+               free(G.user);
+               free(G.pass);
+       }
+
+       return EXIT_SUCCESS;
+}
diff --git a/mailutils/sendmail.c b/mailutils/sendmail.c
new file mode 100644 (file)
index 0000000..7e57a94
--- /dev/null
@@ -0,0 +1,259 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * bare bones sendmail
+ *
+ * Copyright (C) 2008 by Vladimir Dronnikov <dronnikov@gmail.com>
+ *
+ * Licensed under GPLv2, see file LICENSE in this tarball for details.
+ */
+#include "libbb.h"
+#include "mail.h"
+
+static int smtp_checkp(const char *fmt, const char *param, int code)
+{
+       char *answer;
+       const char *msg = command(fmt, param);
+       // read stdin
+       // if the string has a form \d\d\d- -- read next string. E.g. EHLO response
+       // parse first bytes to a number
+       // if code = -1 then just return this number
+       // if code != -1 then checks whether the number equals the code
+       // if not equal -> die saying msg
+       while ((answer = xmalloc_fgetline(stdin)) != NULL)
+               if (strlen(answer) <= 3 || '-' != answer[3])
+                       break;
+       if (answer) {
+               int n = atoi(answer);
+               if (timeout)
+                       alarm(0);
+               free(answer);
+               if (-1 == code || n == code)
+                       return n;
+       }
+       bb_error_msg_and_die("%s failed", msg);
+}
+
+static int smtp_check(const char *fmt, int code)
+{
+       return smtp_checkp(fmt, NULL, code);
+}
+
+// strip argument of bad chars
+static char *sane_address(char *str)
+{
+       char *s = str;
+       char *p = s;
+       while (*s) {
+               if (isalnum(*s) || '_' == *s || '-' == *s || '.' == *s || '@' == *s) {
+                       *p++ = *s;
+               }
+               s++;
+       }
+       *p = '\0';
+       return str;
+}
+
+static void rcptto(const char *s)
+{
+       smtp_checkp("RCPT TO:<%s>", s, 250);
+}
+
+int sendmail_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int sendmail_main(int argc UNUSED_PARAM, char **argv)
+{
+       char *opt_connect = opt_connect;
+       char *opt_from;
+       char *s;
+       llist_t *list = NULL;
+       char *domain = sane_address(safe_getdomainname());
+       int code;
+
+       enum {
+       //--- standard options
+               OPT_t = 1 << 0,         // read message for recipients, append them to those on cmdline
+               OPT_f = 1 << 1,         // sender address
+               OPT_o = 1 << 2,         // various options. -oi IMPLIED! others are IGNORED!
+       //--- BB specific options
+               OPT_w = 1 << 3,         // network timeout
+               OPT_H = 1 << 4,         // use external connection helper
+               OPT_S = 1 << 5,         // specify connection string
+               OPT_a = 1 << 6,         // authentication tokens
+       };
+
+       // init global variables
+       INIT_G();
+
+       // save initial stdin since body is piped!
+       xdup2(STDIN_FILENO, 3);
+       G.fp0 = fdopen(3, "r");
+
+       // parse options
+       // -f is required. -H and -S are mutually exclusive
+       opt_complementary = "f:w+:H--S:S--H:a::";
+       // N.B. since -H and -S are mutually exclusive they do not interfere in opt_connect
+       // -a is for ssmtp (http://downloads.openwrt.org/people/nico/man/man8/ssmtp.8.html) compatibility,
+       // it is still under development.
+       opts = getopt32(argv, "tf:o:w:H:S:a::", &opt_from, NULL, &timeout, &opt_connect, &opt_connect, &list);
+       //argc -= optind;
+       argv += optind;
+
+       // process -a[upm]<token> options
+       if ((opts & OPT_a) && !list)
+               bb_show_usage();
+       while (list) {
+               char *a = (char *) llist_pop(&list);
+               if ('u' == a[0])
+                       G.user = xstrdup(a+1);
+               if ('p' == a[0])
+                       G.pass = xstrdup(a+1);
+               // N.B. we support only AUTH LOGIN so far
+               //if ('m' == a[0])
+               //      G.method = xstrdup(a+1);
+       }
+       // N.B. list == NULL here
+       //bb_info_msg("OPT[%x] AU[%s], AP[%s], AM[%s], ARGV[%s]", opts, au, ap, am, *argv);
+
+       // connect to server
+
+       // connection helper ordered? ->
+       if (opts & OPT_H) {
+               const char *args[] = { "sh", "-c", opt_connect, NULL };
+               // plug it in
+               launch_helper(args);
+       // vanilla connection
+       } else {
+               int fd;
+               // host[:port] not explicitly specified? -> use $SMTPHOST
+               // no $SMTPHOST ? -> use localhost
+               if (!(opts & OPT_S)) {
+                       opt_connect = getenv("SMTPHOST");
+                       if (!opt_connect)
+                               opt_connect = (char *)"127.0.0.1";
+               }
+               // do connect
+               fd = create_and_connect_stream_or_die(opt_connect, 25);
+               // and make ourselves a simple IO filter
+               xmove_fd(fd, STDIN_FILENO);
+               xdup2(STDIN_FILENO, STDOUT_FILENO);
+       }
+       // N.B. from now we know nothing about network :)
+
+       // wait for initial server OK
+       // N.B. if we used openssl the initial 220 answer is already swallowed during openssl TLS init procedure
+       // so we need to kick the server to see whether we are ok
+       code = smtp_check("NOOP", -1);
+       // 220 on plain connection, 250 on openssl-helped TLS session
+       if (220 == code)
+               smtp_check(NULL, 250); // reread the code to stay in sync
+       else if (250 != code)
+               bb_error_msg_and_die("INIT failed");
+
+       // we should start with modern EHLO
+       if (250 != smtp_checkp("EHLO %s", domain, -1)) {
+               smtp_checkp("HELO %s", domain, 250);
+       }
+       if (ENABLE_FEATURE_CLEAN_UP)
+               free(domain);
+
+       // perform authentication
+       if (opts & OPT_a) {
+               smtp_check("AUTH LOGIN", 334);
+               // we must read credentials unless they are given via -a[up] options
+               if (!G.user || !G.pass)
+                       get_cred_or_die(4);
+               encode_base64(NULL, G.user, NULL);
+               smtp_check("", 334);
+               encode_base64(NULL, G.pass, NULL);
+               smtp_check("", 235);
+       }
+
+       // set sender
+       // N.B. we have here a very loosely defined algotythm
+       // since sendmail historically offers no means to specify secrets on cmdline.
+       // 1) server can require no authentication ->
+       //      we must just provide a (possibly fake) reply address.
+       // 2) server can require AUTH ->
+       //      we must provide valid username and password along with a (possibly fake) reply address.
+       //      For the sake of security username and password are to be read either from console or from a secured file.
+       //      Since reading from console may defeat usability, the solution is either to read from a predefined
+       //      file descriptor (e.g. 4), or again from a secured file.
+
+       // got no sender address? -> use system username as a resort
+       // N.B. we marked -f as required option!
+       //if (!G.user) {
+       //      // N.B. IMHO getenv("USER") can be way easily spoofed!
+       //      G.user = xuid2uname(getuid());
+       //      opt_from = xasprintf("%s@%s", G.user, domain);
+       //}
+       //if (ENABLE_FEATURE_CLEAN_UP)
+       //      free(domain);
+       smtp_checkp("MAIL FROM:<%s>", opt_from, 250);
+
+       // process message
+
+       // read recipients from message and add them to those given on cmdline.
+       // this means we scan stdin for To:, Cc:, Bcc: lines until an empty line
+       // and then use the rest of stdin as message body
+       code = 0; // set "analyze headers" mode
+       while ((s = xmalloc_fgetline(G.fp0)) != NULL) {
+               // put message lines doubling leading dots
+               if (code) {
+                       // escape leading dots
+                       // N.B. this feature is implied even if no -i (-oi) switch given
+                       // N.B. we need to escape the leading dot regardless of
+                       // whether it is single or not character on the line
+                       if ('.' == s[0] /*&& '\0' == s[1] */)
+                               printf(".");
+                       // dump read line
+                       printf("%s\r\n", s);
+                       free(s);
+                       continue;
+               }
+
+               // analyze headers
+               // To: or Cc: headers add recipients
+               if (0 == strncasecmp("To: ", s, 4) || 0 == strncasecmp("Bcc: " + 1, s, 4)) {
+                       rcptto(sane_address(s+4));
+//                     goto addh;
+                       llist_add_to_end(&list, s);
+               // Bcc: header adds blind copy (hidden) recipient
+               } else if (0 == strncasecmp("Bcc: ", s, 5)) {
+                       rcptto(sane_address(s+5));
+                       free(s);
+                       // N.B. Bcc: vanishes from headers!
+               // other headers go verbatim
+               } else if (s[0]) {
+// addh:
+                       llist_add_to_end(&list, s);
+               // the empty line stops analyzing headers
+               } else {
+                       free(s);
+                       // put recipients specified on cmdline
+                       while (*argv) {
+                               s = sane_address(*argv);
+                               rcptto(s);
+                               llist_add_to_end(&list, xasprintf("To: %s", s));
+                               argv++;
+                       }
+                       // enter "put message" mode
+                       smtp_check("DATA", 354);
+                       // dump the headers
+                       while (list) {
+                               printf("%s\r\n", (char *) llist_pop(&list));
+                       }
+                       printf("%s\r\n" + 2); // quirk for format string to be reused
+                       // stop analyzing headers
+                       code++;
+               }
+       }
+
+       // finalize the message
+       smtp_check(".", 250);
+       // ... and say goodbye
+       smtp_check("QUIT", 221);
+       // cleanup
+       if (ENABLE_FEATURE_CLEAN_UP)
+               fclose(G.fp0);
+
+       return EXIT_SUCCESS;
+}
diff --git a/miscutils/Config.in b/miscutils/Config.in
new file mode 100644 (file)
index 0000000..7feaf4a
--- /dev/null
@@ -0,0 +1,584 @@
+#
+# For a description of the syntax of this configuration file,
+# see scripts/kbuild/config-language.txt.
+#
+
+menu "Miscellaneous Utilities"
+
+config ADJTIMEX
+       bool "adjtimex"
+       default n
+       help
+         Adjtimex reads and optionally sets adjustment parameters for
+         the Linux clock adjustment algorithm.
+
+config BBCONFIG
+       bool "bbconfig"
+       default n
+       help
+         The bbconfig applet will print the config file with which
+         busybox was built.
+
+config CHAT
+       bool "chat"
+       default n
+       help
+         Simple chat utility.
+
+config FEATURE_CHAT_NOFAIL
+       bool "Enable NOFAIL expect strings"
+       depends on CHAT
+       default y
+       help
+         When enabled expect strings which are started with a dash trigger
+         no-fail mode. That is when expectation is not met within timeout
+         the script is not terminated but sends next SEND string and waits
+         for next EXPECT string. This allows to compose far more flexible
+         scripts.
+
+config FEATURE_CHAT_TTY_HIFI
+       bool "Force STDIN to be a TTY"
+       depends on CHAT
+       default n
+       help
+         Original chat always treats STDIN as a TTY device and sets for it
+         so-called raw mode. This option turns on such behaviour.
+
+config FEATURE_CHAT_IMPLICIT_CR
+       bool "Enable implicit Carriage Return"
+       depends on CHAT
+       default y
+       help
+         When enabled make chat to terminate all SEND strings with a "\r"
+         unless "\c" is met anywhere in the string.
+
+config FEATURE_CHAT_SWALLOW_OPTS
+       bool "Swallow options"
+       depends on CHAT
+       default n
+       help
+         Busybox chat require no options. To make it not fail when used
+         in place of original chat (which has a bunch of options) turn
+         this on.
+
+config FEATURE_CHAT_SEND_ESCAPES
+       bool "Support weird SEND escapes"
+       depends on CHAT
+       default n
+       help
+         Original chat uses some escape sequences in SEND arguments which
+         are not sent to device but rather performs special actions.
+         E.g. "\K" means to send a break sequence to device.
+         "\d" delays execution for a second, "\p" -- for a 1/100 of second.
+         Before turning this option on think twice: do you really need them?
+
+config FEATURE_CHAT_VAR_ABORT_LEN
+       bool "Support variable-length ABORT conditions"
+       depends on CHAT
+       default n
+       help
+         Original chat uses fixed 50-bytes length ABORT conditions. Say N here.
+
+config FEATURE_CHAT_CLR_ABORT
+       bool "Support revoking of ABORT conditions"
+       depends on CHAT
+       default n
+       help
+         Support CLR_ABORT directive.
+
+config CHRT
+       bool "chrt"
+       default n
+       help
+         manipulate real-time attributes of a process.
+         This requires sched_{g,s}etparam support in your libc.
+
+config CROND
+       bool "crond"
+       default n
+       select FEATURE_SUID
+       select FEATURE_SYSLOG
+       help
+         Crond is a background daemon that parses individual crontab
+         files and executes commands on behalf of the users in question.
+         This is a port of dcron from slackware. It uses files of the
+         format /var/spool/cron/crontabs/<username> files, for example:
+             $ cat /var/spool/cron/crontabs/root
+             # Run daily cron jobs at 4:40 every day:
+             40 4 * * * /etc/cron/daily > /dev/null 2>&1
+
+config FEATURE_CROND_D
+       bool "Support option -d to redirect output to stderr"
+       depends on CROND
+       default n
+       help
+         -d sets loglevel to 0 (most verbose) and directs all output to stderr.
+
+config FEATURE_CROND_CALL_SENDMAIL
+       bool "Using /usr/sbin/sendmail?"
+       default n
+       depends on CROND
+       help
+         Support calling /usr/sbin/sendmail for send cmd outputs.
+
+config FEATURE_CROND_DIR
+       string "crond spool directory"
+       default "/var/spool/cron"
+       depends on CROND || CRONTAB
+       help
+         Location of crond spool.
+
+config CRONTAB
+       bool "crontab"
+       default n
+       select FEATURE_SUID
+       help
+         Crontab manipulates the crontab for a particular user. Only
+         the superuser may specify a different user and/or crontab directory.
+         Note that Busybox binary must be setuid root for this applet to
+         work properly.
+
+config DC
+       bool "dc"
+       default n
+       help
+         Dc is a reverse-polish desk calculator which supports unlimited
+         precision arithmetic.
+
+config FEATURE_DC_LIBM
+       bool "Enable power and exp functions (requires libm)"
+       default n
+       depends on DC
+       help
+         Enable power and exp functions.
+         NOTE: This will require libm to be present for linking.
+
+config DEVFSD
+       bool "devfsd (obsolete)"
+       default n
+       select FEATURE_SYSLOG
+       help
+         This is deprecated and should NOT be used anymore.
+         Use linux >= 2.6 (optionally with hotplug) and mdev instead!
+         See docs/mdev.txt for detailed instructions on how to use mdev
+         instead.
+
+         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 should NOT be used anymore.
+         Use linux >= 2.6 (optionally with hotplug) and mdev instead!
+
+         For legacy systems -- if there is no way around devfsd -- this
+         tells busybox to look for names like /dev/loop/0 instead of
+         /dev/loop0. If your /dev directory has normal names instead of
+         devfs names, you don't want this.
+
+config DEVMEM
+       bool "devmem"
+       default n
+       help
+         devmem is a small program that reads and writes from physical
+         memory using /dev/mem.
+
+config EJECT
+       bool "eject"
+       default n
+       help
+         Used to eject cdroms. (defaults to /dev/cdrom)
+
+config FEATURE_EJECT_SCSI
+       bool "SCSI support"
+       default n
+       depends on EJECT
+       help
+         Add the -s option to eject, this allows to eject SCSI-Devices and
+         usb-storage devices.
+
+config FBSPLASH
+       bool "fbsplash"
+       default n
+       help
+         Shows splash image and progress bar on framebuffer device.
+         Can be used during boot phase of an embedded device. ~2kb.
+         Usage:
+         - use kernel option 'vga=xxx' or otherwise enable fb device.
+         - put somewhere fbsplash.cfg file and an image in .ppm format.
+         - $ setsid fbsplash [params] &
+           -c: hide cursor
+           -d /dev/fbN: framebuffer device (if not /dev/fb0)
+           -s path_to_image_file (can be "-" for stdin)
+           -i path_to_cfg_file (can be "-" for stdin)
+           -f path_to_fifo (can be "-" for stdin)
+         - if you want to run it only in presence of kernel parameter:
+           grep -q "fbsplash=on" </proc/cmdline && setsid fbsplash [params] &
+         - commands for fifo:
+           "NN" (ASCII decimal number) - percentage to show on progress bar
+           "exit" - well you guessed it
+
+config FLASH_ERASEALL
+       bool "flash_eraseall"
+       default n
+       help
+         The flash_eraseall binary from mtd-utils as of git head c4c6a59eb.
+         This utility is used to erase the whole MTD device.
+
+config IONICE
+       bool "ionice"
+       default n
+       help
+         Set/set program io scheduling class and priority
+         Requires kernel >= 2.6.13
+
+config INOTIFYD
+       bool "inotifyd"
+       default n
+       help
+         Simple inotify daemon. Reports filesystem changes. Requires
+         kernel >= 2.6.13
+
+config LAST
+       bool "last"
+       default n
+       select FEATURE_WTMP
+       help
+         'last' displays a list of the last users that logged into the system.
+
+choice
+       prompt "Choose last implementation"
+       depends on LAST
+       default FEATURE_LAST_SMALL
+
+config FEATURE_LAST_SMALL
+       bool "small"
+       help
+         This is a small version of last with just the basic set of
+         features.
+
+config FEATURE_LAST_FANCY
+       bool "huge"
+       help
+         'last' displays detailed information about the last users that
+         logged into the system (mimics sysvinit last). +900 bytes.
+endchoice
+
+config LESS
+       bool "less"
+       default n
+       help
+         'less' is a pager, meaning that it displays text files. It possesses
+         a wide array of features, and is an improvement over 'more'.
+
+config FEATURE_LESS_MAXLINES
+       int "Max number of input lines less will try to eat"
+       default 9999999
+       depends on LESS
+
+config FEATURE_LESS_BRACKETS
+       bool "Enable bracket searching"
+       default y
+       depends on LESS
+       help
+         This option adds the capability to search for matching left and right
+         brackets, facilitating programming.
+
+config FEATURE_LESS_FLAGS
+       bool "Enable extra flags"
+       default y
+       depends on LESS
+       help
+         The extra flags provided do the following:
+
+         The -M flag enables a more sophisticated status line.
+         The -m flag enables a simpler status line with a percentage.
+
+config FEATURE_LESS_MARKS
+       bool "Enable marks"
+       default n
+       depends on LESS
+       help
+         Marks enable positions in a file to be stored for easy reference.
+
+config FEATURE_LESS_REGEXP
+       bool "Enable regular expressions"
+       default n
+       depends on LESS
+       help
+         Enable regular expressions, allowing complex file searches.
+
+config FEATURE_LESS_WINCH
+       bool "Enable automatic resizing on window size changes"
+       default n
+       depends on LESS
+       help
+         Makes less track window size changes.
+
+config FEATURE_LESS_DASHCMD
+       bool "Enable flag changes ('-' command)"
+       default n
+       depends on LESS
+       help
+         This enables the ability to change command-line flags within
+         less itself ('-' keyboard command).
+
+config FEATURE_LESS_LINENUMS
+       bool "Enable dynamic switching of line numbers"
+       default n
+       depends on FEATURE_LESS_DASHCMD
+       help
+         Enable "-N" command.
+
+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"
+       default n
+       depends on HDPARM
+       help
+         Enables the 'hdparm -d' option to get/set using_dma flag.
+
+config MAKEDEVS
+       bool "makedevs"
+       default n
+       help
+         'makedevs' is a utility used to create a batch of devices with
+         one command.
+         .
+         There are two choices for command line behaviour, the interface
+         as used by LEAF/Linux Router Project, or a device table file.
+         .
+         'leaf' is traditionally what busybox follows, it allows multiple
+         devices of a particluar type to be created per command.
+         e.g. /dev/hda[0-9]
+         Device properties are passed as command line arguments.
+         .
+         'table' reads device properties from a file or stdin, allowing
+         a batch of unrelated devices to be made with one command.
+         User/group names are allowed as an alternative to uid/gid.
+
+choice
+       prompt "Choose makedevs behaviour"
+       depends on MAKEDEVS
+       default FEATURE_MAKEDEVS_TABLE
+
+config FEATURE_MAKEDEVS_LEAF
+       bool "leaf"
+
+config FEATURE_MAKEDEVS_TABLE
+       bool "table"
+
+endchoice
+
+config MAN
+       bool "man"
+       default n
+       help
+         Format and display manual pages.
+
+config MICROCOM
+       bool "microcom"
+       default n
+       help
+         The poor man's minicom utility for chatting with serial port devices.
+
+config MOUNTPOINT
+       bool "mountpoint"
+       default n
+       help
+         mountpoint checks if the directory is a mountpoint.
+
+config MT
+       bool "mt"
+       default n
+       help
+         mt is used to control tape devices. You can use the mt utility
+         to advance or rewind a tape past a specified number of archive
+         files on the tape.
+
+config RAIDAUTORUN
+       bool "raidautorun"
+       default n
+       help
+         raidautorun tells the kernel md driver to
+         search and start RAID arrays.
+
+config READAHEAD
+       bool "readahead"
+       default n
+       depends on LFS
+       help
+         Preload the files listed on the command line into RAM cache so that
+         subsequent reads on these files will not block on disk I/O.
+
+         This applet just calls the readahead(2) system call on each file.
+         It is mainly useful in system startup scripts to preload files
+         or executables before they are used. When used at the right time
+         (in particular when a CPU bound process is running) it can
+         significantly speed up system startup.
+
+         As readahead(2) blocks until each file has been read, it is best to
+         run this applet as a background job.
+
+config RUNLEVEL
+       bool "runlevel"
+       default n
+       help
+         find the current and previous system runlevel.
+
+         This applet uses utmp but does not rely on busybox supporing
+         utmp on purpose. It is used by e.g. emdebian via /etc/init.d/rc.
+
+config RX
+       bool "rx"
+       default n
+       help
+         Receive files using the Xmodem protocol.
+
+config SETSID
+       bool "setsid"
+       default n
+       help
+         setsid runs a program in a new session
+
+config STRINGS
+       bool "strings"
+       default n
+       help
+         strings prints the printable character sequences for each file
+         specified.
+
+config TASKSET
+       bool "taskset"
+       default n
+       help
+         Retrieve or set a processes's CPU affinity.
+         This requires sched_{g,s}etaffinity support in your libc.
+
+config FEATURE_TASKSET_FANCY
+       bool "Fancy output"
+       default y
+       depends on TASKSET
+       help
+         Add code for fancy output. This merely silences a compiler-warning
+         and adds about 135 Bytes. May be needed for machines with alot
+         of CPUs.
+
+config TIME
+       bool "time"
+       default n
+       help
+         The time command runs the specified program with the given arguments.
+         When the command finishes, time writes a message to standard output
+         giving timing statistics about this program run.
+
+config TIMEOUT
+       bool "timeout"
+       default n
+       help
+         Runs a program and watches it. If it does not terminate in
+         specified number of seconds, it is sent a signal.
+
+config TTYSIZE
+       bool "ttysize"
+       default n
+       help
+         A replacement for "stty size". Unlike stty, can report only width,
+         only height, or both, in any order. It also does not complain on
+         error, but returns default 80x24.
+         Usage in shell scripts: width=`ttysize w`.
+
+config WATCHDOG
+       bool "watchdog"
+       default n
+       help
+         The watchdog utility is used with hardware or software watchdog
+         device drivers. It opens the specified watchdog device special file
+         and periodically writes a magic character to the device. If the
+         watchdog applet ever fails to write the magic character within a
+         certain amount of time, the watchdog device assumes the system has
+         hung, and will cause the hardware to reboot.
+
+endmenu
diff --git a/miscutils/Kbuild b/miscutils/Kbuild
new file mode 100644 (file)
index 0000000..23d7d8d
--- /dev/null
@@ -0,0 +1,41 @@
+# Makefile for busybox
+#
+# Copyright (C) 1999-2005 by Erik Andersen <andersen@codepoet.org>
+#
+# Licensed under the GPL v2, see the file LICENSE in this tarball.
+
+lib-y:=
+lib-$(CONFIG_ADJTIMEX)    += adjtimex.o
+lib-$(CONFIG_BBCONFIG)    += bbconfig.o
+lib-$(CONFIG_CHAT)        += chat.o
+lib-$(CONFIG_CHRT)        += chrt.o
+lib-$(CONFIG_CROND)       += crond.o
+lib-$(CONFIG_CRONTAB)     += crontab.o
+lib-$(CONFIG_DC)          += dc.o
+lib-$(CONFIG_DEVFSD)      += devfsd.o
+lib-$(CONFIG_DEVMEM)      += devmem.o
+lib-$(CONFIG_EJECT)       += eject.o
+lib-$(CONFIG_FBSPLASH)    += fbsplash.o
+lib-$(CONFIG_FLASH_ERASEALL)   += flash_eraseall.o
+lib-$(CONFIG_IONICE)      += ionice.o
+lib-$(CONFIG_HDPARM)      += hdparm.o
+lib-$(CONFIG_INOTIFYD)    += inotifyd.o
+lib-$(CONFIG_FEATURE_LAST_SMALL)+= last.o
+lib-$(CONFIG_FEATURE_LAST_FANCY)+= last_fancy.o
+lib-$(CONFIG_LESS)        += less.o
+lib-$(CONFIG_MAKEDEVS)    += makedevs.o
+lib-$(CONFIG_MAN)         += man.o
+lib-$(CONFIG_MICROCOM)    += microcom.o
+lib-$(CONFIG_MOUNTPOINT)  += mountpoint.o
+lib-$(CONFIG_MT)          += mt.o
+lib-$(CONFIG_RAIDAUTORUN) += raidautorun.o
+lib-$(CONFIG_READAHEAD)   += readahead.o
+lib-$(CONFIG_RUNLEVEL)    += runlevel.o
+lib-$(CONFIG_RX)          += rx.o
+lib-$(CONFIG_SETSID)      += setsid.o
+lib-$(CONFIG_STRINGS)     += strings.o
+lib-$(CONFIG_TASKSET)     += taskset.o
+lib-$(CONFIG_TIME)        += time.o
+lib-$(CONFIG_TIMEOUT)     += timeout.o
+lib-$(CONFIG_TTYSIZE)     += ttysize.o
+lib-$(CONFIG_WATCHDOG)    += watchdog.o
diff --git a/miscutils/adjtimex.c b/miscutils/adjtimex.c
new file mode 100644 (file)
index 0000000..07f0834
--- /dev/null
@@ -0,0 +1,145 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * adjtimex.c - read, and possibly modify, the Linux kernel `timex' variables.
+ *
+ * Originally written: October 1997
+ * Last hack: March 2001
+ * Copyright 1997, 2000, 2001 Larry Doolittle <LRDoolittle@lbl.gov>
+ *
+ * busyboxed 20 March 2001, Larry Doolittle <ldoolitt@recycle.lbl.gov>
+ *
+ * Licensed under GPLv2 or later, see file License in this tarball for details.
+ */
+
+#include "libbb.h"
+#include <sys/timex.h>
+
+static const uint16_t statlist_bit[] = {
+       STA_PLL,
+       STA_PPSFREQ,
+       STA_PPSTIME,
+       STA_FLL,
+       STA_INS,
+       STA_DEL,
+       STA_UNSYNC,
+       STA_FREQHOLD,
+       STA_PPSSIGNAL,
+       STA_PPSJITTER,
+       STA_PPSWANDER,
+       STA_PPSERROR,
+       STA_CLOCKERR,
+       0
+};
+static const char statlist_name[] =
+       "PLL"       "\0"
+       "PPSFREQ"   "\0"
+       "PPSTIME"   "\0"
+       "FFL"       "\0"
+       "INS"       "\0"
+       "DEL"       "\0"
+       "UNSYNC"    "\0"
+       "FREQHOLD"  "\0"
+       "PPSSIGNAL" "\0"
+       "PPSJITTER" "\0"
+       "PPSWANDER" "\0"
+       "PPSERROR"  "\0"
+       "CLOCKERR"
+;
+
+static const char ret_code_descript[] =
+       "clock synchronized" "\0"
+       "insert leap second" "\0"
+       "delete leap second" "\0"
+       "leap second in progress" "\0"
+       "leap second has occurred" "\0"
+       "clock not synchronized"
+;
+
+int adjtimex_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int adjtimex_main(int argc, char **argv)
+{
+       enum {
+               OPT_quiet = 0x1
+       };
+       unsigned opt;
+       char *opt_o, *opt_f, *opt_p, *opt_t;
+       struct timex txc;
+       int i, ret;
+       const char *descript;
+       txc.modes=0;
+
+       opt = getopt32(argv, "qo:f:p:t:",
+                       &opt_o, &opt_f, &opt_p, &opt_t);
+       //if (opt & 0x1) // -q
+       if (opt & 0x2) { // -o
+               txc.offset = xatol(opt_o);
+               txc.modes |= ADJ_OFFSET_SINGLESHOT;
+       }
+       if (opt & 0x4) { // -f
+               txc.freq = xatol(opt_f);
+               txc.modes |= ADJ_FREQUENCY;
+       }
+       if (opt & 0x8) { // -p
+               txc.constant = xatol(opt_p);
+               txc.modes |= ADJ_TIMECONST;
+       }
+       if (opt & 0x10) { // -t
+               txc.tick = xatol(opt_t);
+               txc.modes |= ADJ_TICK;
+       }
+       if (argc != optind) { /* no valid non-option parameters */
+               bb_show_usage();
+       }
+
+       ret = adjtimex(&txc);
+
+       if (ret < 0) {
+               bb_perror_nomsg_and_die();
+       }
+
+       if (!(opt & OPT_quiet)) {
+               int sep;
+               const char *name;
+
+               printf(
+                       "    mode:         %d\n"
+                       "-o  offset:       %ld\n"
+                       "-f  frequency:    %ld\n"
+                       "    maxerror:     %ld\n"
+                       "    esterror:     %ld\n"
+                       "    status:       %d (",
+               txc.modes, txc.offset, txc.freq, txc.maxerror,
+               txc.esterror, txc.status);
+
+               /* representative output of next code fragment:
+                  "PLL | PPSTIME" */
+               name = statlist_name;
+               sep = 0;
+               for (i = 0; statlist_bit[i]; i++) {
+                       if (txc.status & statlist_bit[i]) {
+                               if (sep)
+                                       fputs(" | ", stdout);
+                               fputs(name, stdout);
+                               sep = 1;
+                       }
+                       name += strlen(name) + 1;
+               }
+
+               descript = "error";
+               if (ret <= 5)
+                       descript = nth_string(ret_code_descript, ret);
+               printf(")\n"
+                       "-p  timeconstant: %ld\n"
+                       "    precision:    %ld\n"
+                       "    tolerance:    %ld\n"
+                       "-t  tick:         %ld\n"
+                       "    time.tv_sec:  %ld\n"
+                       "    time.tv_usec: %ld\n"
+                       "    return value: %d (%s)\n",
+               txc.constant,
+               txc.precision, txc.tolerance, txc.tick,
+               (long)txc.time.tv_sec, (long)txc.time.tv_usec, ret, descript);
+       }
+
+       return 0;
+}
diff --git a/miscutils/bbconfig.c b/miscutils/bbconfig.c
new file mode 100644 (file)
index 0000000..689052e
--- /dev/null
@@ -0,0 +1,12 @@
+/* vi: set sw=4 ts=4: */
+/* This file was released into the public domain by Paul Fox.
+ */
+#include "libbb.h"
+#include "bbconfigopts.h"
+
+int bbconfig_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int bbconfig_main(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
+{
+       printf(bbconfig_config);
+       return 0;
+}
diff --git a/miscutils/chat.c b/miscutils/chat.c
new file mode 100644 (file)
index 0000000..3ffd7b2
--- /dev/null
@@ -0,0 +1,435 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * bare bones chat utility
+ * inspired by ppp's chat
+ *
+ * Copyright (C) 2008 by Vladimir Dronnikov <dronnikov@gmail.com>
+ *
+ * Licensed under GPLv2, see file LICENSE in this tarball for details.
+ */
+#include "libbb.h"
+
+// default timeout: 45 sec
+#define        DEFAULT_CHAT_TIMEOUT 45*1000
+// max length of "abort string",
+// i.e. device reply which causes termination
+#define MAX_ABORT_LEN 50
+
+// possible exit codes
+enum {
+       ERR_OK = 0,     // all's well
+       ERR_MEM,        // read too much while expecting
+       ERR_IO,         // signalled or I/O error
+       ERR_TIMEOUT,    // timed out while expecting
+       ERR_ABORT,      // first abort condition was met
+//     ERR_ABORT2,     // second abort condition was met
+//     ...
+};
+
+// exit code
+#define exitcode bb_got_signal
+
+// trap for critical signals
+static void signal_handler(UNUSED_PARAM int signo)
+{
+       // report I/O error condition
+       exitcode = ERR_IO;
+}
+
+#if !ENABLE_FEATURE_CHAT_IMPLICIT_CR
+#define unescape(s, nocr) unescape(s)
+#endif
+static size_t unescape(char *s, int *nocr)
+{
+       char *start = s;
+       char *p = s;
+
+       while (*s) {
+               char c = *s;
+               // do we need special processing?
+               // standard escapes + \s for space and \N for \0
+               // \c inhibits terminating \r for commands and is noop for expects
+               if ('\\' == c) {
+                       c = *++s;
+                       if (c) {
+#if ENABLE_FEATURE_CHAT_IMPLICIT_CR
+                               if ('c' == c) {
+                                       *nocr = 1;
+                                       goto next;
+                               }
+#endif
+                               if ('N' == c) {
+                                       c = '\0';
+                               } else if ('s' == c) {
+                                       c = ' ';
+#if ENABLE_FEATURE_CHAT_NOFAIL
+                               // unescape leading dash only
+                               // TODO: and only for expect, not command string
+                               } else if ('-' == c && (start + 1 == s)) {
+                                       //c = '-';
+#endif
+                               } else {
+                                       c = bb_process_escape_sequence((const char **)&s);
+                                       s--;
+                               }
+                       }
+               // ^A becomes \001, ^B -- \002 and so on...
+               } else if ('^' == c) {
+                       c = *++s-'@';
+               }
+               // put unescaped char
+               *p++ = c;
+#if ENABLE_FEATURE_CHAT_IMPLICIT_CR
+ next:
+#endif
+               // next char
+               s++;
+       }
+       *p = '\0';
+
+       return p - start;
+}
+
+int chat_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int chat_main(int argc UNUSED_PARAM, char **argv)
+{
+       int record_fd = -1;
+       bool echo = 0;
+       // collection of device replies which cause unconditional termination
+       llist_t *aborts = NULL;
+       // inactivity period
+       int timeout = DEFAULT_CHAT_TIMEOUT;
+       // maximum length of abort string
+#if ENABLE_FEATURE_CHAT_VAR_ABORT_LEN
+       size_t max_abort_len = 0;
+#else
+#define max_abort_len MAX_ABORT_LEN
+#endif
+#if ENABLE_FEATURE_CHAT_TTY_HIFI
+       struct termios tio0, tio;
+#endif
+       // directive names
+       enum {
+               DIR_HANGUP = 0,
+               DIR_ABORT,
+#if ENABLE_FEATURE_CHAT_CLR_ABORT
+               DIR_CLR_ABORT,
+#endif
+               DIR_TIMEOUT,
+               DIR_ECHO,
+               DIR_SAY,
+               DIR_RECORD,
+       };
+
+       // make x* functions fail with correct exitcode
+       xfunc_error_retval = ERR_IO;
+
+       // trap vanilla signals to prevent process from being killed suddenly
+       bb_signals(0
+               + (1 << SIGHUP)
+               + (1 << SIGINT)
+               + (1 << SIGTERM)
+               + (1 << SIGPIPE)
+               , signal_handler);
+
+#if ENABLE_FEATURE_CHAT_TTY_HIFI
+       tcgetattr(STDIN_FILENO, &tio);
+       tio0 = tio;
+       cfmakeraw(&tio);
+       tcsetattr(STDIN_FILENO, TCSAFLUSH, &tio);
+#endif
+
+#if ENABLE_FEATURE_CHAT_SWALLOW_OPTS
+       getopt32(argv, "vVsSE");
+       argv += optind;
+#else
+       argv++; // goto first arg
+#endif
+       // handle chat expect-send pairs
+       while (*argv) {
+               // directive given? process it
+               int key = index_in_strings(
+                       "HANGUP\0" "ABORT\0"
+#if ENABLE_FEATURE_CHAT_CLR_ABORT
+                       "CLR_ABORT\0"
+#endif
+                       "TIMEOUT\0" "ECHO\0" "SAY\0" "RECORD\0"
+                       , *argv
+               );
+               if (key >= 0) {
+                       // cache directive value
+                       char *arg = *++argv;
+                       // OFF -> 0, anything else -> 1
+                       bool onoff = (0 != strcmp("OFF", arg));
+                       // process directive
+                       if (DIR_HANGUP == key) {
+                               // turn SIGHUP on/off
+                               signal(SIGHUP, onoff ? signal_handler : SIG_IGN);
+                       } else if (DIR_ABORT == key) {
+                               // append the string to abort conditions
+#if ENABLE_FEATURE_CHAT_VAR_ABORT_LEN
+                               size_t len = strlen(arg);
+                               if (len > max_abort_len)
+                                       max_abort_len = len;
+#endif
+                               llist_add_to_end(&aborts, arg);
+#if ENABLE_FEATURE_CHAT_CLR_ABORT
+                       } else if (DIR_CLR_ABORT == key) {
+                               // remove the string from abort conditions
+                               // N.B. gotta refresh maximum length too...
+#if ENABLE_FEATURE_CHAT_VAR_ABORT_LEN
+                               max_abort_len = 0;
+#endif
+                               for (llist_t *l = aborts; l; l = l->link) {
+#if ENABLE_FEATURE_CHAT_VAR_ABORT_LEN
+                                       size_t len = strlen(l->data);
+#endif
+                                       if (!strcmp(arg, l->data)) {
+                                               llist_unlink(&aborts, l);
+                                               continue;
+                                       }
+#if ENABLE_FEATURE_CHAT_VAR_ABORT_LEN
+                                       if (len > max_abort_len)
+                                               max_abort_len = len;
+#endif
+                               }
+#endif
+                       } else if (DIR_TIMEOUT == key) {
+                               // set new timeout
+                               // -1 means OFF
+                               timeout = atoi(arg) * 1000;
+                               // 0 means default
+                               // >0 means value in msecs
+                               if (!timeout)
+                                       timeout = DEFAULT_CHAT_TIMEOUT;
+                       } else if (DIR_ECHO == key) {
+                               // turn echo on/off
+                               // N.B. echo means dumping device input/output to stderr
+                               echo = onoff;
+                       } else if (DIR_RECORD == key) {
+                               // turn record on/off
+                               // N.B. record means dumping device input to a file
+                                       // close previous record_fd
+                               if (record_fd > 0)
+                                       close(record_fd);
+                               // N.B. do we have to die here on open error?
+                               record_fd = (onoff) ? xopen(arg, O_WRONLY|O_CREAT|O_TRUNC) : -1;
+                       } else if (DIR_SAY == key) {
+                               // just print argument verbatim
+                               // TODO: should we use full_write() to avoid unistd/stdio conflict?
+                               bb_error_msg("%s", arg);
+                       }
+                       // next, please!
+                       argv++;
+               // ordinary expect-send pair!
+               } else {
+                       //-----------------------
+                       // do expect
+                       //-----------------------
+                       int expect_len;
+                       size_t buf_len = 0;
+                       size_t max_len = max_abort_len;
+
+                       struct pollfd pfd;
+#if ENABLE_FEATURE_CHAT_NOFAIL
+                       int nofail = 0;
+#endif
+                       char *expect = *argv++;
+
+                       // sanity check: shall we really expect something?
+                       if (!expect)
+                               goto expect_done;
+
+#if ENABLE_FEATURE_CHAT_NOFAIL
+                       // if expect starts with -
+                       if ('-' == *expect) {
+                               // swallow -
+                               expect++;
+                               // and enter nofail mode
+                               nofail++;
+                       }
+#endif
+
+#ifdef ___TEST___BUF___ // test behaviour with a small buffer
+#      undef COMMON_BUFSIZE
+#      define COMMON_BUFSIZE 6
+#endif
+                       // expand escape sequences in expect
+                       expect_len = unescape(expect, &expect_len /*dummy*/);
+                       if (expect_len > max_len)
+                               max_len = expect_len;
+                       // sanity check:
+                       // we should expect more than nothing but not more than input buffer
+                       // TODO: later we'll get rid of fixed-size buffer
+                       if (!expect_len)
+                               goto expect_done;
+                       if (max_len >= COMMON_BUFSIZE) {
+                               exitcode = ERR_MEM;
+                               goto expect_done;
+                       }
+
+                       // get reply
+                       pfd.fd = STDIN_FILENO;
+                       pfd.events = POLLIN;
+                       while (!exitcode
+                           && poll(&pfd, 1, timeout) > 0
+                           && (pfd.revents & POLLIN)
+                       ) {
+#define buf bb_common_bufsiz1
+                               llist_t *l;
+                               ssize_t delta;
+
+                               // read next char from device
+                               if (safe_read(STDIN_FILENO, buf+buf_len, 1) > 0) {
+                                       // dump device input if RECORD fname
+                                       if (record_fd > 0) {
+                                               full_write(record_fd, buf+buf_len, 1);
+                                       }
+                                       // dump device input if ECHO ON
+                                       if (echo > 0) {
+//                                             if (buf[buf_len] < ' ') {
+//                                                     full_write(STDERR_FILENO, "^", 1);
+//                                                     buf[buf_len] += '@';
+//                                             }
+                                               full_write(STDERR_FILENO, buf+buf_len, 1);
+                                       }
+                                       buf_len++;
+                                       // move input frame if we've reached higher bound
+                                       if (buf_len > COMMON_BUFSIZE) {
+                                               memmove(buf, buf+buf_len-max_len, max_len);
+                                               buf_len = max_len;
+                                       }
+                               }
+                               // N.B. rule of thumb: values being looked for can
+                               // be found only at the end of input buffer
+                               // this allows to get rid of strstr() and memmem()
+
+                               // TODO: make expect and abort strings processed uniformly
+                               // abort condition is met? -> bail out
+                               for (l = aborts, exitcode = ERR_ABORT; l; l = l->link, ++exitcode) {
+                                       size_t len = strlen(l->data);
+                                       delta = buf_len-len;
+                                       if (delta >= 0 && !memcmp(buf+delta, l->data, len))
+                                               goto expect_done;
+                               }
+                               exitcode = ERR_OK;
+
+                               // expected reply received? -> goto next command
+                               delta = buf_len - expect_len;
+                               if (delta >= 0 && !memcmp(buf+delta, expect, expect_len))
+                                       goto expect_done;
+#undef buf
+                       } /* while (have data) */
+
+                       // device timed out or unexpected reply received
+                       exitcode = ERR_TIMEOUT;
+ expect_done:
+#if ENABLE_FEATURE_CHAT_NOFAIL
+                       // on success and when in nofail mode
+                       // we should skip following subsend-subexpect pairs
+                       if (nofail) {
+                               if (!exitcode) {
+                                       // find last send before non-dashed expect
+                                       while (*argv && argv[1] && '-' == argv[1][0])
+                                               argv += 2;
+                                       // skip the pair
+                                       // N.B. do we really need this?!
+                                       if (!*argv++ || !*argv++)
+                                               break;
+                               }
+                               // nofail mode also clears all but IO errors (or signals)
+                               if (ERR_IO != exitcode)
+                                       exitcode = ERR_OK;
+                       }
+#endif
+                       // bail out unless we expected successfully
+                       if (exitcode)
+                               break;
+
+                       //-----------------------
+                       // do send
+                       //-----------------------
+                       if (*argv) {
+#if ENABLE_FEATURE_CHAT_IMPLICIT_CR
+                               int nocr = 0; // inhibit terminating command with \r
+#endif
+                               char *loaded = NULL; // loaded command
+                               size_t len;
+                               char *buf = *argv++;
+
+                               // if command starts with @
+                               // load "real" command from file named after @
+                               if ('@' == *buf) {
+                                       // skip the @ and any following white-space
+                                       trim(++buf);
+                                       buf = loaded = xmalloc_xopen_read_close(buf, NULL);
+                               }
+                               // expand escape sequences in command
+                               len = unescape(buf, &nocr);
+
+                               // send command
+                               alarm(timeout);
+                               pfd.fd = STDOUT_FILENO;
+                               pfd.events = POLLOUT;
+                               while (len && !exitcode
+                                   && poll(&pfd, 1, -1) > 0
+                                   && (pfd.revents & POLLOUT)
+                               ) {
+#if ENABLE_FEATURE_CHAT_SEND_ESCAPES
+                                       // "\\d" means 1 sec delay, "\\p" means 0.01 sec delay
+                                       // "\\K" means send BREAK
+                                       char c = *buf;
+                                       if ('\\' == c) {
+                                               c = *++buf;
+                                               if ('d' == c) {
+                                                       sleep(1);
+                                                       len--;
+                                                       continue;
+                                               }
+                                               if ('p' == c) {
+                                                       usleep(10000);
+                                                       len--;
+                                                       continue;
+                                               }
+                                               if ('K' == c) {
+                                                       tcsendbreak(STDOUT_FILENO, 0);
+                                                       len--;
+                                                       continue;
+                                               }
+                                               buf--;
+                                       }
+                                       if (safe_write(STDOUT_FILENO, buf, 1) != 1)
+                                               break;
+                                       len--;
+                                       buf++;
+#else
+                                       len -= full_write(STDOUT_FILENO, buf, len);
+#endif
+                               } /* while (can write) */
+                               alarm(0);
+
+                               // report I/O error if there still exists at least one non-sent char
+                               if (len)
+                                       exitcode = ERR_IO;
+
+                               // free loaded command (if any)
+                               if (loaded)
+                                       free(loaded);
+#if ENABLE_FEATURE_CHAT_IMPLICIT_CR
+                               // or terminate command with \r (if not inhibited)
+                               else if (!nocr)
+                                       xwrite(STDOUT_FILENO, "\r", 1);
+#endif
+                               // bail out unless we sent command successfully
+                               if (exitcode)
+                                       break;
+                       } /* if (*argv) */
+               }
+       } /* while (*argv) */
+
+#if ENABLE_FEATURE_CHAT_TTY_HIFI
+       tcsetattr(STDIN_FILENO, TCSAFLUSH, &tio0);
+#endif
+
+       return exitcode;
+}
diff --git a/miscutils/chrt.c b/miscutils/chrt.c
new file mode 100644 (file)
index 0000000..cc5660b
--- /dev/null
@@ -0,0 +1,123 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * chrt - manipulate real-time attributes of a process
+ * Copyright (c) 2006-2007 Bernhard Reutner-Fischer
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+#include <sched.h>
+#include "libbb.h"
+#ifndef _POSIX_PRIORITY_SCHEDULING
+#warning your system may be foobared
+#endif
+static const struct {
+       int policy;
+       char name[12];
+} policies[] = {
+       {SCHED_OTHER, "SCHED_OTHER"},
+       {SCHED_FIFO, "SCHED_FIFO"},
+       {SCHED_RR, "SCHED_RR"}
+};
+
+static void show_min_max(int pol)
+{
+       const char *fmt = "%s min/max priority\t: %d/%d\n\0%s not supported?\n";
+       int max, min;
+       max = sched_get_priority_max(pol);
+       min = sched_get_priority_min(pol);
+       if (max >= 0 && min >= 0)
+               printf(fmt, policies[pol].name, min, max);
+       else {
+               fmt += 29;
+               printf(fmt, policies[pol].name);
+       }
+}
+
+#define OPT_m (1<<0)
+#define OPT_p (1<<1)
+#define OPT_r (1<<2)
+#define OPT_f (1<<3)
+#define OPT_o (1<<4)
+
+int chrt_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int chrt_main(int argc UNUSED_PARAM, char **argv)
+{
+       pid_t pid = 0;
+       unsigned opt;
+       struct sched_param sp;
+       char *pid_str;
+       char *priority = priority; /* for compiler */
+       const char *current_new;
+       int policy = SCHED_RR;
+
+       /* at least 1 arg; only one policy accepted */
+       opt_complementary = "-1:r--fo:f--ro:r--fo";
+       opt = getopt32(argv, "+mprfo");
+       if (opt & OPT_r)
+               policy = SCHED_RR;
+       if (opt & OPT_f)
+               policy = SCHED_FIFO;
+       if (opt & OPT_o)
+               policy = SCHED_OTHER;
+       if (opt & OPT_m) { /* print min/max */
+               show_min_max(SCHED_FIFO);
+               show_min_max(SCHED_RR);
+               show_min_max(SCHED_OTHER);
+               fflush_stdout_and_exit(EXIT_SUCCESS);
+       }
+
+       argv += optind;
+       if (opt & OPT_p) {
+               pid_str = *argv++;
+               if (*argv) { /* "-p <priority> <pid> [...]" */
+                       priority = pid_str;
+                       pid_str = *argv;
+               }
+               /* else "-p <pid>", and *argv == NULL */
+               pid = xatoul_range(pid_str, 1, ((unsigned)(pid_t)ULONG_MAX) >> 1);
+       } else {
+               priority = *argv++;
+               if (!*argv)
+                       bb_show_usage();
+       }
+
+       current_new = "current\0new";
+       if (opt & OPT_p) {
+               int pol;
+ print_rt_info:
+               pol = sched_getscheduler(pid);
+               if (pol < 0)
+                       bb_perror_msg_and_die("can't %cet pid %d's policy", 'g', pid);
+               printf("pid %d's %s scheduling policy: %s\n",
+                               pid, current_new, policies[pol].name);
+               if (sched_getparam(pid, &sp))
+                       bb_perror_msg_and_die("can't get pid %d's attributes", pid);
+               printf("pid %d's %s scheduling priority: %d\n",
+                               pid, current_new, sp.sched_priority);
+               if (!*argv) {
+                       /* Either it was just "-p <pid>",
+                        * or it was "-p <priority> <pid>" and we came here
+                        * for the second time (see goto below) */
+                       return EXIT_SUCCESS;
+               }
+               *argv = NULL;
+               current_new += 8;
+       }
+
+       /* from the manpage of sched_getscheduler:
+       [...] sched_priority can have a value in the range 0 to 99.
+       [...] SCHED_OTHER or SCHED_BATCH must be assigned static priority 0.
+       [...] SCHED_FIFO or SCHED_RR can have static priority in 1..99 range.
+       */
+       sp.sched_priority = xstrtou_range(priority, 0, policy != SCHED_OTHER ? 1 : 0, 99);
+
+       if (sched_setscheduler(pid, policy, &sp) < 0)
+               bb_perror_msg_and_die("can't %cet pid %d's policy", 's', pid);
+
+       if (!*argv) /* "-p <priority> <pid> [...]" */
+               goto print_rt_info;
+
+       BB_EXECVP(*argv, argv);
+       bb_simple_perror_msg_and_die(*argv);
+}
diff --git a/miscutils/crond.c b/miscutils/crond.c
new file mode 100644 (file)
index 0000000..804fe0b
--- /dev/null
@@ -0,0 +1,929 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * crond -d[#] -c <crondir> -f -b
+ *
+ * run as root, but NOT setuid root
+ *
+ * Copyright 1994 Matthew Dillon (dillon@apollo.west.oic.com)
+ * (version 2.3.2)
+ * Vladimir Oleynik <dzo@simtreas.ru> (C) 2002
+ *
+ * Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
+ */
+
+#include "libbb.h"
+#include <syslog.h>
+
+/* glibc frees previous setenv'ed value when we do next setenv()
+ * of the same variable. uclibc does not do this! */
+#if (defined(__GLIBC__) && !defined(__UCLIBC__)) /* || OTHER_SAFE_LIBC... */
+#define SETENV_LEAKS 0
+#else
+#define SETENV_LEAKS 1
+#endif
+
+
+#define TMPDIR          CONFIG_FEATURE_CROND_DIR
+#define CRONTABS        CONFIG_FEATURE_CROND_DIR "/crontabs"
+#ifndef SENDMAIL
+#define SENDMAIL        "sendmail"
+#endif
+#ifndef SENDMAIL_ARGS
+#define SENDMAIL_ARGS   "-ti", "oem"
+#endif
+#ifndef CRONUPDATE
+#define CRONUPDATE      "cron.update"
+#endif
+#ifndef MAXLINES
+#define MAXLINES        256    /* max lines in non-root crontabs */
+#endif
+
+
+typedef struct CronFile {
+       struct CronFile *cf_Next;
+       struct CronLine *cf_LineBase;
+       char *cf_User;                  /* username                     */
+       smallint cf_Ready;              /* bool: one or more jobs ready */
+       smallint cf_Running;            /* bool: one or more jobs running */
+       smallint cf_Deleted;            /* marked for deletion, ignore  */
+} CronFile;
+
+typedef struct CronLine {
+       struct CronLine *cl_Next;
+       char *cl_Shell;         /* shell command                        */
+       pid_t cl_Pid;           /* running pid, 0, or armed (-1)        */
+#if ENABLE_FEATURE_CROND_CALL_SENDMAIL
+       int cl_MailPos;         /* 'empty file' size                    */
+       smallint cl_MailFlag;   /* running pid is for mail              */
+       char *cl_MailTo;        /* whom to mail results                 */
+#endif
+       /* ordered by size, not in natural order. makes code smaller: */
+       char cl_Dow[7];         /* 0-6, beginning sunday                */
+       char cl_Mons[12];       /* 0-11                                 */
+       char cl_Hrs[24];        /* 0-23                                 */
+       char cl_Days[32];       /* 1-31                                 */
+       char cl_Mins[60];       /* 0-59                                 */
+} CronLine;
+
+
+#define DaemonUid 0
+
+
+enum {
+       OPT_l = (1 << 0),
+       OPT_L = (1 << 1),
+       OPT_f = (1 << 2),
+       OPT_b = (1 << 3),
+       OPT_S = (1 << 4),
+       OPT_c = (1 << 5),
+       OPT_d = (1 << 6) * ENABLE_FEATURE_CROND_D,
+};
+#if ENABLE_FEATURE_CROND_D
+#define DebugOpt (option_mask32 & OPT_d)
+#else
+#define DebugOpt 0
+#endif
+
+
+struct globals {
+       unsigned LogLevel; /* = 8; */
+       const char *LogFile;
+       const char *CDir; /* = CRONTABS; */
+       CronFile *FileBase;
+#if SETENV_LEAKS
+       char *env_var_user;
+       char *env_var_home;
+#endif
+};
+#define G (*(struct globals*)&bb_common_bufsiz1)
+#define LogLevel           (G.LogLevel               )
+#define LogFile            (G.LogFile                )
+#define CDir               (G.CDir                   )
+#define FileBase           (G.FileBase               )
+#define env_var_user       (G.env_var_user           )
+#define env_var_home       (G.env_var_home           )
+#define INIT_G() do { \
+       LogLevel = 8; \
+       CDir = CRONTABS; \
+} while (0)
+
+
+static void CheckUpdates(void);
+static void SynchronizeDir(void);
+static int TestJobs(time_t t1, time_t t2);
+static void RunJobs(void);
+static int CheckJobs(void);
+static void RunJob(const char *user, CronLine *line);
+#if ENABLE_FEATURE_CROND_CALL_SENDMAIL
+static void EndJob(const char *user, CronLine *line);
+#else
+#define EndJob(user, line)  ((line)->cl_Pid = 0)
+#endif
+static void DeleteFile(const char *userName);
+
+
+#define LVL5  "\x05"
+#define LVL7  "\x07"
+#define LVL8  "\x08"
+#define LVL9  "\x09"
+#define WARN9 "\x49"
+#define DIE9  "\xc9"
+/* level >= 20 is "error" */
+#define ERR20 "\x14"
+
+static void crondlog(const char *ctl, ...)
+{
+       va_list va;
+       int level = (ctl[0] & 0x1f);
+
+       va_start(va, ctl);
+       if (level >= (int)LogLevel) {
+               /* Debug mode: all to (non-redirected) stderr, */
+               /* Syslog mode: all to syslog (logmode = LOGMODE_SYSLOG), */
+               if (!DebugOpt && LogFile) {
+                       /* Otherwise (log to file): we reopen log file at every write: */
+                       int logfd = open3_or_warn(LogFile, O_WRONLY | O_CREAT | O_APPEND, 0666);
+                       if (logfd >= 0)
+                               xmove_fd(logfd, STDERR_FILENO);
+               }
+// TODO: ERR -> error, WARN -> warning, LVL -> info
+               bb_verror_msg(ctl + 1, va, /* strerr: */ NULL);
+       }
+       va_end(va);
+       if (ctl[0] & 0x80)
+               exit(20);
+}
+
+int crond_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int crond_main(int argc UNUSED_PARAM, char **argv)
+{
+       unsigned opt;
+
+       INIT_G();
+
+       /* "-b after -f is ignored", and so on for every pair a-b */
+       opt_complementary = "f-b:b-f:S-L:L-S" USE_FEATURE_CROND_D(":d-l")
+                       ":l+:d+"; /* -l and -d have numeric param */
+       opt = getopt32(argv, "l:L:fbSc:" USE_FEATURE_CROND_D("d:"),
+                       &LogLevel, &LogFile, &CDir
+                       USE_FEATURE_CROND_D(,&LogLevel));
+       /* both -d N and -l N set the same variable: LogLevel */
+
+       if (!(opt & OPT_f)) {
+               /* close stdin, stdout, stderr.
+                * close unused descriptors - don't need them. */
+               bb_daemonize_or_rexec(DAEMON_CLOSE_EXTRA_FDS, argv);
+       }
+
+       if (!DebugOpt && LogFile == NULL) {
+               /* logging to syslog */
+               openlog(applet_name, LOG_CONS | LOG_PID, LOG_CRON);
+               logmode = LOGMODE_SYSLOG;
+       }
+
+       xchdir(CDir);
+       //signal(SIGHUP, SIG_IGN); /* ? original crond dies on HUP... */
+       xsetenv("SHELL", DEFAULT_SHELL); /* once, for all future children */
+       crondlog(LVL9 "crond (busybox "BB_VER") started, log level %d", LogLevel);
+       SynchronizeDir();
+
+       /* main loop - synchronize to 1 second after the minute, minimum sleep
+        * of 1 second. */
+       {
+               time_t t1 = time(NULL);
+               time_t t2;
+               long dt;
+               int rescan = 60;
+               int sleep_time = 60;
+
+               write_pidfile("/var/run/crond.pid");
+               for (;;) {
+                       sleep((sleep_time + 1) - (time(NULL) % sleep_time));
+
+                       t2 = time(NULL);
+                       dt = (long)t2 - (long)t1;
+
+                       /*
+                        * The file 'cron.update' is checked to determine new cron
+                        * jobs.  The directory is rescanned once an hour to deal
+                        * with any screwups.
+                        *
+                        * check for disparity.  Disparities over an hour either way
+                        * result in resynchronization.  A reverse-indexed disparity
+                        * less then an hour causes us to effectively sleep until we
+                        * match the original time (i.e. no re-execution of jobs that
+                        * have just been run).  A forward-indexed disparity less then
+                        * an hour causes intermediate jobs to be run, but only once
+                        * in the worst case.
+                        *
+                        * when running jobs, the inequality used is greater but not
+                        * equal to t1, and less then or equal to t2.
+                        */
+                       if (--rescan == 0) {
+                               rescan = 60;
+                               SynchronizeDir();
+                       }
+                       CheckUpdates();
+                       if (DebugOpt)
+                               crondlog(LVL5 "wakeup dt=%ld", dt);
+                       if (dt < -60 * 60 || dt > 60 * 60) {
+                               crondlog(WARN9 "time disparity of %d minutes detected", dt / 60);
+                       } else if (dt > 0) {
+                               TestJobs(t1, t2);
+                               RunJobs();
+                               sleep(5);
+                               if (CheckJobs() > 0) {
+                                       sleep_time = 10;
+                               } else {
+                                       sleep_time = 60;
+                               }
+                       }
+                       t1 = t2;
+               }
+       }
+       return 0; /* not reached */
+}
+
+#if SETENV_LEAKS
+/* We set environment *before* vfork (because we want to use vfork),
+ * so we cannot use setenv() - repeated calls to setenv() may leak memory!
+ * Using putenv(), and freeing memory after unsetenv() won't leak */
+static void safe_setenv(char **pvar_val, const char *var, const char *val)
+{
+       char *var_val = *pvar_val;
+
+       if (var_val) {
+               bb_unsetenv(var_val);
+               free(var_val);
+       }
+       *pvar_val = xasprintf("%s=%s", var, val);
+       putenv(*pvar_val);
+}
+#endif
+
+static void SetEnv(struct passwd *pas)
+{
+#if SETENV_LEAKS
+       safe_setenv(&env_var_user, "USER", pas->pw_name);
+       safe_setenv(&env_var_home, "HOME", pas->pw_dir);
+       /* if we want to set user's shell instead: */
+       /*safe_setenv(env_var_user, "SHELL", pas->pw_shell);*/
+#else
+       xsetenv("USER", pas->pw_name);
+       xsetenv("HOME", pas->pw_dir);
+#endif
+       /* currently, we use constant one: */
+       /*setenv("SHELL", DEFAULT_SHELL, 1); - done earlier */
+}
+
+static void ChangeUser(struct passwd *pas)
+{
+       /* careful: we're after vfork! */
+       change_identity(pas); /* - initgroups, setgid, setuid */
+       if (chdir(pas->pw_dir) < 0) {
+               crondlog(LVL9 "can't chdir(%s)", pas->pw_dir);
+               if (chdir(TMPDIR) < 0) {
+                       crondlog(DIE9 "can't chdir(%s)", TMPDIR); /* exits */
+               }
+       }
+}
+
+static const char DowAry[] ALIGN1 =
+       "sun""mon""tue""wed""thu""fri""sat"
+       /* "Sun""Mon""Tue""Wed""Thu""Fri""Sat" */
+;
+
+static const char MonAry[] ALIGN1 =
+       "jan""feb""mar""apr""may""jun""jul""aug""sep""oct""nov""dec"
+       /* "Jan""Feb""Mar""Apr""May""Jun""Jul""Aug""Sep""Oct""Nov""Dec" */
+;
+
+static void ParseField(char *user, char *ary, int modvalue, int off,
+                               const char *names, char *ptr)
+/* 'names' is a pointer to a set of 3-char abbreviations */
+{
+       char *base = ptr;
+       int n1 = -1;
+       int n2 = -1;
+
+       // this can't happen due to config_read()
+       /*if (base == NULL)
+               return;*/
+
+       while (1) {
+               int skip = 0;
+
+               /* Handle numeric digit or symbol or '*' */
+               if (*ptr == '*') {
+                       n1 = 0;         /* everything will be filled */
+                       n2 = modvalue - 1;
+                       skip = 1;
+                       ++ptr;
+               } else if (isdigit(*ptr)) {
+                       if (n1 < 0) {
+                               n1 = strtol(ptr, &ptr, 10) + off;
+                       } else {
+                               n2 = strtol(ptr, &ptr, 10) + off;
+                       }
+                       skip = 1;
+               } else if (names) {
+                       int i;
+
+                       for (i = 0; names[i]; i += 3) {
+                               /* was using strncmp before... */
+                               if (strncasecmp(ptr, &names[i], 3) == 0) {
+                                       ptr += 3;
+                                       if (n1 < 0) {
+                                               n1 = i / 3;
+                                       } else {
+                                               n2 = i / 3;
+                                       }
+                                       skip = 1;
+                                       break;
+                               }
+                       }
+               }
+
+               /* handle optional range '-' */
+               if (skip == 0) {
+                       goto err;
+               }
+               if (*ptr == '-' && n2 < 0) {
+                       ++ptr;
+                       continue;
+               }
+
+               /*
+                * collapse single-value ranges, handle skipmark, and fill
+                * in the character array appropriately.
+                */
+               if (n2 < 0) {
+                       n2 = n1;
+               }
+               if (*ptr == '/') {
+                       skip = strtol(ptr + 1, &ptr, 10);
+               }
+
+               /*
+                * fill array, using a failsafe is the easiest way to prevent
+                * an endless loop
+                */
+               {
+                       int s0 = 1;
+                       int failsafe = 1024;
+
+                       --n1;
+                       do {
+                               n1 = (n1 + 1) % modvalue;
+
+                               if (--s0 == 0) {
+                                       ary[n1 % modvalue] = 1;
+                                       s0 = skip;
+                               }
+                               if (--failsafe == 0) {
+                                       goto err;
+                               }
+                       } while (n1 != n2);
+
+               }
+               if (*ptr != ',') {
+                       break;
+               }
+               ++ptr;
+               n1 = -1;
+               n2 = -1;
+       }
+
+       if (*ptr) {
+ err:
+               crondlog(WARN9 "user %s: parse error at %s", user, base);
+               return;
+       }
+
+       if (DebugOpt && (LogLevel <= 5)) { /* like LVL5 */
+               /* can't use crondlog, it inserts '\n' */
+               int i;
+               for (i = 0; i < modvalue; ++i)
+                       fprintf(stderr, "%d", (unsigned char)ary[i]);
+               fputc('\n', stderr);
+       }
+}
+
+static void FixDayDow(CronLine *line)
+{
+       unsigned i;
+       int weekUsed = 0;
+       int daysUsed = 0;
+
+       for (i = 0; i < ARRAY_SIZE(line->cl_Dow); ++i) {
+               if (line->cl_Dow[i] == 0) {
+                       weekUsed = 1;
+                       break;
+               }
+       }
+       for (i = 0; i < ARRAY_SIZE(line->cl_Days); ++i) {
+               if (line->cl_Days[i] == 0) {
+                       daysUsed = 1;
+                       break;
+               }
+       }
+       if (weekUsed != daysUsed) {
+               if (weekUsed)
+                       memset(line->cl_Days, 0, sizeof(line->cl_Days));
+               else /* daysUsed */
+                       memset(line->cl_Dow, 0, sizeof(line->cl_Dow));
+       }
+}
+
+static void SynchronizeFile(const char *fileName)
+{
+       struct parser_t *parser;
+       struct stat sbuf;
+       int maxLines;
+       char *tokens[6];
+#if ENABLE_FEATURE_CROND_CALL_SENDMAIL
+       char *mailTo = NULL;
+#endif
+
+       if (!fileName)
+               return;
+
+       DeleteFile(fileName);
+       parser = config_open(fileName);
+       if (!parser)
+               return;
+
+       maxLines = (strcmp(fileName, "root") == 0) ? 65535 : MAXLINES;
+
+       if (fstat(fileno(parser->fp), &sbuf) == 0 && sbuf.st_uid == DaemonUid) {
+               CronFile *file = xzalloc(sizeof(CronFile));
+               CronLine **pline;
+               int n;
+
+               file->cf_User = xstrdup(fileName);
+               pline = &file->cf_LineBase;
+
+               while (1) {
+                       CronLine *line;
+
+                       if (!--maxLines)
+                               break;
+                       n = config_read(parser, tokens, 6, 1, "# \t", PARSE_NORMAL | PARSE_KEEP_COPY);
+                       if (!n)
+                               break;
+
+                       if (DebugOpt)
+                               crondlog(LVL5 "user:%s entry:%s", fileName, parser->data);
+
+                       /* check if line is setting MAILTO= */
+                       if (0 == strncmp(tokens[0], "MAILTO=", 7)) {
+#if ENABLE_FEATURE_CROND_CALL_SENDMAIL
+                               free(mailTo);
+                               mailTo = (tokens[0][7]) ? xstrdup(&tokens[0][7]) : NULL;
+#endif /* otherwise just ignore such lines */
+                               continue;
+                       }
+                       /* check if a minimum of tokens is specified */
+                       if (n < 6)
+                               continue;
+                       *pline = line = xzalloc(sizeof(*line));
+                       /* parse date ranges */
+                       ParseField(file->cf_User, line->cl_Mins, 60, 0, NULL, tokens[0]);
+                       ParseField(file->cf_User, line->cl_Hrs, 24, 0, NULL, tokens[1]);
+                       ParseField(file->cf_User, line->cl_Days, 32, 0, NULL, tokens[2]);
+                       ParseField(file->cf_User, line->cl_Mons, 12, -1, MonAry, tokens[3]);
+                       ParseField(file->cf_User, line->cl_Dow, 7, 0, DowAry, tokens[4]);
+                       /*
+                        * fix days and dow - if one is not "*" and the other
+                        * is "*", the other is set to 0, and vise-versa
+                        */
+                       FixDayDow(line);
+#if ENABLE_FEATURE_CROND_CALL_SENDMAIL
+                       /* copy mailto (can be NULL) */
+                       line->cl_MailTo = xstrdup(mailTo);
+#endif
+                       /* copy command */
+                       line->cl_Shell = xstrdup(tokens[5]);
+                       if (DebugOpt) {
+                               crondlog(LVL5 " command:%s", tokens[5]);
+                       }
+                       pline = &line->cl_Next;
+//bb_error_msg("M[%s]F[%s][%s][%s][%s][%s][%s]", mailTo, tokens[0], tokens[1], tokens[2], tokens[3], tokens[4], tokens[5]);
+               }
+               *pline = NULL;
+
+               file->cf_Next = FileBase;
+               FileBase = file;
+
+               if (maxLines == 0) {
+                       crondlog(WARN9 "user %s: too many lines", fileName);
+               }
+       }
+       config_close(parser);
+}
+
+static void CheckUpdates(void)
+{
+       FILE *fi;
+       char buf[256];
+
+       fi = fopen_for_read(CRONUPDATE);
+       if (fi != NULL) {
+               unlink(CRONUPDATE);
+               while (fgets(buf, sizeof(buf), fi) != NULL) {
+                       /* use first word only */
+                       SynchronizeFile(strtok(buf, " \t\r\n"));
+               }
+               fclose(fi);
+       }
+}
+
+static void SynchronizeDir(void)
+{
+       CronFile *file;
+       /* Attempt to delete the database. */
+ again:
+       for (file = FileBase; file; file = file->cf_Next) {
+               if (!file->cf_Deleted) {
+                       DeleteFile(file->cf_User);
+                       goto again;
+               }
+       }
+
+       /*
+        * Remove cron update file
+        *
+        * Re-chdir, in case directory was renamed & deleted, or otherwise
+        * screwed up.
+        *
+        * scan directory and add associated users
+        */
+       unlink(CRONUPDATE);
+       if (chdir(CDir) < 0) {
+               crondlog(DIE9 "can't chdir(%s)", CDir);
+       }
+       {
+               DIR *dir = opendir(".");
+               struct dirent *den;
+
+               if (!dir)
+                       crondlog(DIE9 "can't chdir(%s)", "."); /* exits */
+               while ((den = readdir(dir)) != NULL) {
+                       if (strchr(den->d_name, '.') != NULL) {
+                               continue;
+                       }
+                       if (getpwnam(den->d_name)) {
+                               SynchronizeFile(den->d_name);
+                       } else {
+                               crondlog(LVL7 "ignoring %s", den->d_name);
+                       }
+               }
+               closedir(dir);
+       }
+}
+
+/*
+ *  DeleteFile() - delete user database
+ *
+ *  Note: multiple entries for same user may exist if we were unable to
+ *  completely delete a database due to running processes.
+ */
+static void DeleteFile(const char *userName)
+{
+       CronFile **pfile = &FileBase;
+       CronFile *file;
+
+       while ((file = *pfile) != NULL) {
+               if (strcmp(userName, file->cf_User) == 0) {
+                       CronLine **pline = &file->cf_LineBase;
+                       CronLine *line;
+
+                       file->cf_Running = 0;
+                       file->cf_Deleted = 1;
+
+                       while ((line = *pline) != NULL) {
+                               if (line->cl_Pid > 0) {
+                                       file->cf_Running = 1;
+                                       pline = &line->cl_Next;
+                               } else {
+                                       *pline = line->cl_Next;
+                                       free(line->cl_Shell);
+                                       free(line);
+                               }
+                       }
+                       if (file->cf_Running == 0) {
+                               *pfile = file->cf_Next;
+                               free(file->cf_User);
+                               free(file);
+                       } else {
+                               pfile = &file->cf_Next;
+                       }
+               } else {
+                       pfile = &file->cf_Next;
+               }
+       }
+}
+
+/*
+ * TestJobs()
+ *
+ * determine which jobs need to be run.  Under normal conditions, the
+ * period is about a minute (one scan).  Worst case it will be one
+ * hour (60 scans).
+ */
+static int TestJobs(time_t t1, time_t t2)
+{
+       int nJobs = 0;
+       time_t t;
+
+       /* Find jobs > t1 and <= t2 */
+
+       for (t = t1 - t1 % 60; t <= t2; t += 60) {
+               struct tm *tp;
+               CronFile *file;
+               CronLine *line;
+
+               if (t <= t1)
+                       continue;
+
+               tp = localtime(&t);
+               for (file = FileBase; file; file = file->cf_Next) {
+                       if (DebugOpt)
+                               crondlog(LVL5 "file %s:", file->cf_User);
+                       if (file->cf_Deleted)
+                               continue;
+                       for (line = file->cf_LineBase; line; line = line->cl_Next) {
+                               if (DebugOpt)
+                                       crondlog(LVL5 " line %s", line->cl_Shell);
+                               if (line->cl_Mins[tp->tm_min] && line->cl_Hrs[tp->tm_hour]
+                                && (line->cl_Days[tp->tm_mday] || line->cl_Dow[tp->tm_wday])
+                                && line->cl_Mons[tp->tm_mon]
+                               ) {
+                                       if (DebugOpt) {
+                                               crondlog(LVL5 " job: %d %s",
+                                                       (int)line->cl_Pid, line->cl_Shell);
+                                       }
+                                       if (line->cl_Pid > 0) {
+                                               crondlog(LVL8 "user %s: process already running: %s",
+                                                       file->cf_User, line->cl_Shell);
+                                       } else if (line->cl_Pid == 0) {
+                                               line->cl_Pid = -1;
+                                               file->cf_Ready = 1;
+                                               ++nJobs;
+                                       }
+                               }
+                       }
+               }
+       }
+       return nJobs;
+}
+
+static void RunJobs(void)
+{
+       CronFile *file;
+       CronLine *line;
+
+       for (file = FileBase; file; file = file->cf_Next) {
+               if (!file->cf_Ready)
+                       continue;
+
+               file->cf_Ready = 0;
+               for (line = file->cf_LineBase; line; line = line->cl_Next) {
+                       if (line->cl_Pid >= 0)
+                               continue;
+
+                       RunJob(file->cf_User, line);
+                       crondlog(LVL8 "USER %s pid %3d cmd %s",
+                               file->cf_User, (int)line->cl_Pid, line->cl_Shell);
+                       if (line->cl_Pid < 0) {
+                               file->cf_Ready = 1;
+                       } else if (line->cl_Pid > 0) {
+                               file->cf_Running = 1;
+                       }
+               }
+       }
+}
+
+/*
+ * CheckJobs() - check for job completion
+ *
+ * Check for job completion, return number of jobs still running after
+ * all done.
+ */
+static int CheckJobs(void)
+{
+       CronFile *file;
+       CronLine *line;
+       int nStillRunning = 0;
+
+       for (file = FileBase; file; file = file->cf_Next) {
+               if (file->cf_Running) {
+                       file->cf_Running = 0;
+
+                       for (line = file->cf_LineBase; line; line = line->cl_Next) {
+                               int status, r;
+                               if (line->cl_Pid <= 0)
+                                       continue;
+
+                               r = waitpid(line->cl_Pid, &status, WNOHANG);
+                               if (r < 0 || r == line->cl_Pid) {
+                                       EndJob(file->cf_User, line);
+                                       if (line->cl_Pid) {
+                                               file->cf_Running = 1;
+                                       }
+                               } else if (r == 0) {
+                                       file->cf_Running = 1;
+                               }
+                       }
+               }
+               nStillRunning += file->cf_Running;
+       }
+       return nStillRunning;
+}
+
+#if ENABLE_FEATURE_CROND_CALL_SENDMAIL
+
+// TODO: sendmail should be _run-time_ option, not compile-time!
+
+static void
+ForkJob(const char *user, CronLine *line, int mailFd,
+               const char *prog, const char *cmd, const char *arg,
+               const char *mail_filename)
+{
+       struct passwd *pas;
+       pid_t pid;
+
+       /* prepare things before vfork */
+       pas = getpwnam(user);
+       if (!pas) {
+               crondlog(LVL9 "can't get uid for %s", user);
+               goto err;
+       }
+       SetEnv(pas);
+
+       pid = vfork();
+       if (pid == 0) {
+               /* CHILD */
+               /* change running state to the user in question */
+               ChangeUser(pas);
+               if (DebugOpt) {
+                       crondlog(LVL5 "child running %s", prog);
+               }
+               if (mailFd >= 0) {
+                       xmove_fd(mailFd, mail_filename ? 1 : 0);
+                       dup2(1, 2);
+               }
+               /* crond 3.0pl1-100 puts tasks in separate process groups */
+               bb_setpgrp();
+               execlp(prog, prog, cmd, arg, (char *) NULL);
+               crondlog(ERR20 "can't exec, user %s cmd %s %s %s", user, prog, cmd, arg);
+               if (mail_filename) {
+                       fdprintf(1, "Exec failed: %s -c %s\n", prog, arg);
+               }
+               _exit(EXIT_SUCCESS);
+       }
+
+       line->cl_Pid = pid;
+       if (pid < 0) {
+               /* FORK FAILED */
+               crondlog(ERR20 "can't vfork");
+ err:
+               line->cl_Pid = 0;
+               if (mail_filename) {
+                       unlink(mail_filename);
+               }
+       } else if (mail_filename) {
+               /* PARENT, FORK SUCCESS
+                * rename mail-file based on pid of process
+                */
+               char mailFile2[128];
+
+               snprintf(mailFile2, sizeof(mailFile2), "%s/cron.%s.%d", TMPDIR, user, pid);
+               rename(mail_filename, mailFile2); // TODO: xrename?
+       }
+
+       /*
+        * Close the mail file descriptor.. we can't just leave it open in
+        * a structure, closing it later, because we might run out of descriptors
+        */
+       if (mailFd >= 0) {
+               close(mailFd);
+       }
+}
+
+static void RunJob(const char *user, CronLine *line)
+{
+       char mailFile[128];
+       int mailFd = -1;
+
+       line->cl_Pid = 0;
+       line->cl_MailFlag = 0;
+
+       if (line->cl_MailTo) {
+               /* open mail file - owner root so nobody can screw with it. */
+               snprintf(mailFile, sizeof(mailFile), "%s/cron.%s.%d", TMPDIR, user, getpid());
+               mailFd = open(mailFile, O_CREAT | O_TRUNC | O_WRONLY | O_EXCL | O_APPEND, 0600);
+
+               if (mailFd >= 0) {
+                       line->cl_MailFlag = 1;
+                       fdprintf(mailFd, "To: %s\nSubject: cron: %s\n\n", line->cl_MailTo,
+                               line->cl_Shell);
+                       line->cl_MailPos = lseek(mailFd, 0, SEEK_CUR);
+               } else {
+                       crondlog(ERR20 "cannot create mail file %s for user %s, "
+                                       "discarding output", mailFile, user);
+               }
+       }
+
+       ForkJob(user, line, mailFd, DEFAULT_SHELL, "-c", line->cl_Shell, mailFile);
+}
+
+/*
+ * EndJob - called when job terminates and when mail terminates
+ */
+static void EndJob(const char *user, CronLine *line)
+{
+       int mailFd;
+       char mailFile[128];
+       struct stat sbuf;
+
+       /* No job */
+       if (line->cl_Pid <= 0) {
+               line->cl_Pid = 0;
+               return;
+       }
+
+       /*
+        * End of job and no mail file
+        * End of sendmail job
+        */
+       snprintf(mailFile, sizeof(mailFile), "%s/cron.%s.%d", TMPDIR, user, line->cl_Pid);
+       line->cl_Pid = 0;
+
+       if (line->cl_MailFlag == 0) {
+               return;
+       }
+       line->cl_MailFlag = 0;
+
+       /*
+        * End of primary job - check for mail file.  If size has increased and
+        * the file is still valid, we sendmail it.
+        */
+       mailFd = open(mailFile, O_RDONLY);
+       unlink(mailFile);
+       if (mailFd < 0) {
+               return;
+       }
+
+       if (fstat(mailFd, &sbuf) < 0 || sbuf.st_uid != DaemonUid
+        || sbuf.st_nlink != 0 || sbuf.st_size == line->cl_MailPos
+        || !S_ISREG(sbuf.st_mode)
+       ) {
+               close(mailFd);
+               return;
+       }
+       if (line->cl_MailTo)
+               ForkJob(user, line, mailFd, SENDMAIL, SENDMAIL_ARGS, NULL);
+}
+
+#else /* crond without sendmail */
+
+static void RunJob(const char *user, CronLine *line)
+{
+       struct passwd *pas;
+       pid_t pid;
+
+       /* prepare things before vfork */
+       pas = getpwnam(user);
+       if (!pas) {
+               crondlog(LVL9 "can't get uid for %s", user);
+               goto err;
+       }
+       SetEnv(pas);
+
+       /* fork as the user in question and run program */
+       pid = vfork();
+       if (pid == 0) {
+               /* CHILD */
+               /* change running state to the user in question */
+               ChangeUser(pas);
+               if (DebugOpt) {
+                       crondlog(LVL5 "child running %s", DEFAULT_SHELL);
+               }
+               /* crond 3.0pl1-100 puts tasks in separate process groups */
+               bb_setpgrp();
+               execl(DEFAULT_SHELL, DEFAULT_SHELL, "-c", line->cl_Shell, (char *) NULL);
+               crondlog(ERR20 "can't exec, user %s cmd %s %s %s", user,
+                                DEFAULT_SHELL, "-c", line->cl_Shell);
+               _exit(EXIT_SUCCESS);
+       }
+       if (pid < 0) {
+               /* FORK FAILED */
+               crondlog(ERR20 "can't vfork");
+ err:
+               pid = 0;
+       }
+       line->cl_Pid = pid;
+}
+
+#endif /* ENABLE_FEATURE_CROND_CALL_SENDMAIL */
diff --git a/miscutils/crontab.c b/miscutils/crontab.c
new file mode 100644 (file)
index 0000000..34b80ea
--- /dev/null
@@ -0,0 +1,227 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * CRONTAB
+ *
+ * usually setuid root, -c option only works if getuid() == geteuid()
+ *
+ * Copyright 1994 Matthew Dillon (dillon@apollo.west.oic.com)
+ * Vladimir Oleynik <dzo@simtreas.ru> (C) 2002
+ *
+ * Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
+ */
+
+#include "libbb.h"
+
+#define CRONTABS        CONFIG_FEATURE_CROND_DIR "/crontabs"
+#ifndef CRONUPDATE
+#define CRONUPDATE      "cron.update"
+#endif
+
+static void change_user(const struct passwd *pas)
+{
+       xsetenv("USER", pas->pw_name);
+       xsetenv("HOME", pas->pw_dir);
+       xsetenv("SHELL", DEFAULT_SHELL);
+
+       /* initgroups, setgid, setuid */
+       change_identity(pas);
+
+       if (chdir(pas->pw_dir) < 0) {
+               bb_perror_msg("chdir(%s) by %s failed",
+                               pas->pw_dir, pas->pw_name);
+               xchdir("/tmp");
+       }
+}
+
+static void edit_file(const struct passwd *pas, const char *file)
+{
+       const char *ptr;
+       int pid = vfork();
+
+       if (pid < 0) /* failure */
+               bb_perror_msg_and_die("vfork");
+       if (pid) { /* parent */
+               wait4pid(pid);
+               return;
+       }
+
+       /* CHILD - change user and run editor */
+       change_user(pas);
+       ptr = getenv("VISUAL");
+       if (!ptr) {
+               ptr = getenv("EDITOR");
+               if (!ptr)
+                       ptr = "vi";
+       }
+
+       BB_EXECLP(ptr, ptr, file, NULL);
+       bb_perror_msg_and_die("exec %s", ptr);
+}
+
+static int open_as_user(const struct passwd *pas, const char *file)
+{
+       pid_t pid;
+       char c;
+
+       pid = vfork();
+       if (pid < 0) /* ERROR */
+               bb_perror_msg_and_die("vfork");
+       if (pid) { /* PARENT */
+               if (wait4pid(pid) == 0) {
+                       /* exitcode 0: child says it can read */
+                       return open(file, O_RDONLY);
+               }
+               return -1;
+       }
+
+       /* CHILD */
+       /* initgroups, setgid, setuid */
+       change_identity(pas);
+       /* We just try to read one byte. If it works, file is readable
+        * under this user. We signal that by exiting with 0. */
+       _exit(safe_read(xopen(file, O_RDONLY), &c, 1) < 0);
+}
+
+int crontab_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int crontab_main(int argc UNUSED_PARAM, char **argv)
+{
+       const struct passwd *pas;
+       const char *crontab_dir = CRONTABS;
+       char *tmp_fname;
+       char *new_fname;
+       char *user_name;  /* -u USER */
+       int fd;
+       int src_fd;
+       int opt_ler;
+
+       /* file [opts]     Replace crontab from file
+        * - [opts]        Replace crontab from stdin
+        * -u user         User
+        * -c dir          Crontab directory
+        * -l              List crontab for user
+        * -e              Edit crontab for user
+        * -r              Delete crontab for user
+        * bbox also supports -d == -r, but most other crontab
+        * implementations do not. Deprecated.
+        */
+       enum {
+               OPT_u = (1 << 0),
+               OPT_c = (1 << 1),
+               OPT_l = (1 << 2),
+               OPT_e = (1 << 3),
+               OPT_r = (1 << 4),
+               OPT_ler = OPT_l + OPT_e + OPT_r,
+       };
+
+       opt_complementary = "?1:dr"; /* max one argument; -d implies -r */
+       opt_ler = getopt32(argv, "u:c:lerd", &user_name, &crontab_dir);
+       argv += optind;
+
+       if (sanitize_env_if_suid()) { /* Clears dangerous stuff, sets PATH */
+               /* run by non-root? */
+               if (opt_ler & (OPT_u|OPT_c))
+                       bb_error_msg_and_die("only root can use -c or -u");
+       }
+
+       if (opt_ler & OPT_u) {
+               pas = xgetpwnam(user_name);
+       } else {
+               pas = xgetpwuid(getuid());
+       }
+
+#define user_name DONT_USE_ME_BEYOND_THIS_POINT
+
+       /* From now on, keep only -l, -e, -r bits */
+       opt_ler &= OPT_ler;
+       if ((opt_ler - 1) & opt_ler) /* more than one bit set? */
+               bb_show_usage();
+
+       /* Read replacement file under user's UID/GID/group vector */
+       src_fd = STDIN_FILENO;
+       if (!opt_ler) { /* Replace? */
+               if (!argv[0])
+                       bb_show_usage();
+               if (NOT_LONE_DASH(argv[0])) {
+                       src_fd = open_as_user(pas, argv[0]);
+                       if (src_fd < 0)
+                               bb_error_msg_and_die("user %s cannot read %s",
+                                               pas->pw_name, argv[0]);
+               }
+       }
+
+       /* cd to our crontab directory */
+       xchdir(crontab_dir);
+
+       tmp_fname = NULL;
+
+       /* Handle requested operation */
+       switch (opt_ler) {
+
+       default: /* case OPT_r: Delete */
+               unlink(pas->pw_name);
+               break;
+
+       case OPT_l: /* List */
+               {
+                       char *args[2] = { pas->pw_name, NULL };
+                       return bb_cat(args);
+                       /* list exits,
+                        * the rest go play with cron update file */
+               }
+
+       case OPT_e: /* Edit */
+               tmp_fname = xasprintf("%s.%u", crontab_dir, (unsigned)getpid());
+               /* No O_EXCL: we don't want to be stuck if earlier crontabs
+                * were killed, leaving stale temp file behind */
+               src_fd = xopen3(tmp_fname, O_RDWR|O_CREAT|O_TRUNC, 0600);
+               fchown(src_fd, pas->pw_uid, pas->pw_gid);
+               fd = open(pas->pw_name, O_RDONLY);
+               if (fd >= 0) {
+                       bb_copyfd_eof(fd, src_fd);
+                       close(fd);
+                       xlseek(src_fd, 0, SEEK_SET);
+               }
+               close_on_exec_on(src_fd); /* don't want editor to see this fd */
+               edit_file(pas, tmp_fname);
+               /* fall through */
+
+       case 0: /* Replace (no -l, -e, or -r were given) */
+               new_fname = xasprintf("%s.new", pas->pw_name);
+               fd = open(new_fname, O_WRONLY|O_CREAT|O_TRUNC|O_APPEND, 0600);
+               if (fd >= 0) {
+                       bb_copyfd_eof(src_fd, fd);
+                       close(fd);
+                       xrename(new_fname, pas->pw_name);
+               } else {
+                       bb_error_msg("cannot create %s/%s",
+                                       crontab_dir, new_fname);
+               }
+               if (tmp_fname)
+                       unlink(tmp_fname);
+               /*free(tmp_fname);*/
+               /*free(new_fname);*/
+
+       } /* switch */
+
+       /* Bump notification file.  Handle window where crond picks file up
+        * before we can write our entry out.
+        */
+       while ((fd = open(CRONUPDATE, O_WRONLY|O_CREAT|O_APPEND, 0600)) >= 0) {
+               struct stat st;
+
+               fdprintf(fd, "%s\n", pas->pw_name);
+               if (fstat(fd, &st) != 0 || st.st_nlink != 0) {
+                       /*close(fd);*/
+                       break;
+               }
+               /* st.st_nlink == 0:
+                * file was deleted, maybe crond missed our notification */
+               close(fd);
+               /* loop */
+       }
+       if (fd < 0) {
+               bb_error_msg("cannot append to %s/%s",
+                               crontab_dir, CRONUPDATE);
+       }
+       return 0;
+}
diff --git a/miscutils/dc.c b/miscutils/dc.c
new file mode 100644 (file)
index 0000000..ff2bc3b
--- /dev/null
@@ -0,0 +1,256 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.h"
+#include <math.h>
+
+/* Tiny RPN calculator, because "expr" didn't give me bitwise operations. */
+
+
+struct globals {
+       unsigned pointer;
+       unsigned base;
+       double stack[1];
+};
+enum { STACK_SIZE = (COMMON_BUFSIZE - offsetof(struct globals, stack)) / sizeof(double) };
+#define G (*(struct globals*)&bb_common_bufsiz1)
+#define pointer   (G.pointer   )
+#define base      (G.base      )
+#define stack     (G.stack     )
+#define INIT_G() do { \
+       base = 10; \
+} while (0)
+
+
+static void push(double a)
+{
+       if (pointer >= STACK_SIZE)
+               bb_error_msg_and_die("stack overflow");
+       stack[pointer++] = a;
+}
+
+static double pop(void)
+{
+       if (pointer == 0)
+               bb_error_msg_and_die("stack underflow");
+       return stack[--pointer];
+}
+
+static void add(void)
+{
+       push(pop() + pop());
+}
+
+static void sub(void)
+{
+       double subtrahend = pop();
+
+       push(pop() - subtrahend);
+}
+
+static void mul(void)
+{
+       push(pop() * pop());
+}
+
+#if ENABLE_FEATURE_DC_LIBM
+static void power(void)
+{
+       double topower = pop();
+
+       push(pow(pop(), topower));
+}
+#endif
+
+static void divide(void)
+{
+       double divisor = pop();
+
+       push(pop() / divisor);
+}
+
+static void mod(void)
+{
+       unsigned d = pop();
+
+       push((unsigned) pop() % d);
+}
+
+static void and(void)
+{
+       push((unsigned) pop() & (unsigned) pop());
+}
+
+static void or(void)
+{
+       push((unsigned) pop() | (unsigned) pop());
+}
+
+static void eor(void)
+{
+       push((unsigned) pop() ^ (unsigned) pop());
+}
+
+static void not(void)
+{
+       push(~(unsigned) pop());
+}
+
+static void set_output_base(void)
+{
+       static const char bases[] ALIGN1 = { 2, 8, 10, 16, 0 };
+       unsigned b = (unsigned)pop();
+
+       base = *strchrnul(bases, b);
+       if (base == 0) {
+               bb_error_msg("error, base %u is not supported", b);
+               base = 10;
+       }
+}
+
+static void print_base(double print)
+{
+       unsigned x, i;
+
+       if (base == 10) {
+               printf("%g\n", print);
+               return;
+       }
+
+       x = (unsigned)print;
+       switch (base) {
+       case 16:
+               printf("%x\n", x);
+               break;
+       case 8:
+               printf("%o\n", x);
+               break;
+       default: /* base 2 */
+               i = (unsigned)INT_MAX + 1;
+               do {
+                       if (x & i) break;
+                       i >>= 1;
+               } while (i > 1);
+               do {
+                       bb_putchar('1' - !(x & i));
+                       i >>= 1;
+               } while (i);
+               bb_putchar('\n');
+       }
+}
+
+static void print_stack_no_pop(void)
+{
+       unsigned i = pointer;
+       while (i)
+               print_base(stack[--i]);
+}
+
+static void print_no_pop(void)
+{
+       print_base(stack[pointer-1]);
+}
+
+struct op {
+       const char name[4];
+       void (*function) (void);
+};
+
+static const struct op operators[] = {
+       {"+",   add},
+       {"add", add},
+       {"-",   sub},
+       {"sub", sub},
+       {"*",   mul},
+       {"mul", mul},
+       {"/",   divide},
+       {"div", divide},
+#if ENABLE_FEATURE_DC_LIBM
+       {"**",  power},
+       {"exp", power},
+       {"pow", power},
+#endif
+       {"%",   mod},
+       {"mod", mod},
+       {"and", and},
+       {"or",  or},
+       {"not", not},
+       {"eor", eor},
+       {"xor", eor},
+       {"p", print_no_pop},
+       {"f", print_stack_no_pop},
+       {"o", set_output_base},
+       { /* zero filled */ }
+};
+
+static void stack_machine(const char *argument)
+{
+       char *endPointer;
+       double d;
+       const struct op *o = operators;
+
+       if (argument == 0)
+               return;
+
+       d = strtod(argument, &endPointer);
+
+       if (endPointer != argument) {
+               push(d);
+               return;
+       }
+
+       while (o->name[0]) {
+               if (strcmp(o->name, argument) == 0) {
+                       o->function();
+                       return;
+               }
+               o++;
+       }
+       bb_error_msg_and_die("%s: syntax error", argument);
+}
+
+/* return pointer to next token in buffer and set *buffer to one char
+ * past the end of the above mentioned token
+ */
+static char *get_token(char **buffer)
+{
+       char *current = skip_whitespace(*buffer);
+       if (*current != '\0') {
+               *buffer = skip_non_whitespace(current);
+               return current;
+       }
+       return NULL;
+}
+
+int dc_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int dc_main(int argc UNUSED_PARAM, char **argv)
+{
+       INIT_G();
+
+       argv++;
+       if (!argv[0]) {
+               /* take stuff from stdin if no args are given */
+               char *line;
+               char *cursor;
+               char *token;
+               while ((line = xmalloc_fgetline(stdin)) != NULL) {
+                       cursor = line;
+                       while (1) {
+                               token = get_token(&cursor);
+                               if (!token) break;
+                               *cursor++ = '\0';
+                               stack_machine(token);
+                       }
+                       free(line);
+               }
+       } else {
+               if (argv[0][0] == '-')
+                       bb_show_usage();
+               do {
+                       stack_machine(*argv);
+               } while (*++argv);
+       }
+       return EXIT_SUCCESS;
+}
diff --git a/miscutils/devfsd.c b/miscutils/devfsd.c
new file mode 100644 (file)
index 0000000..61b97dc
--- /dev/null
@@ -0,0 +1,1801 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+/*
+       devfsd implementation for busybox
+
+       Copyright (C) 2003 by Tito Ragusa <farmatito@tiscali.it>
+
+       Busybox version is based on some previous work and ideas
+       Copyright (C) [2003] by [Matteo Croce] <3297627799@wind.it>
+
+       devfsd.c
+
+       Main file for  devfsd  (devfs daemon for Linux).
+
+    Copyright (C) 1998-2002  Richard Gooch
+
+       devfsd.h
+
+    Header file for  devfsd  (devfs daemon for Linux).
+
+    Copyright (C) 1998-2000  Richard Gooch
+
+       compat_name.c
+
+    Compatibility name file for  devfsd  (build compatibility names).
+
+    Copyright (C) 1998-2002  Richard Gooch
+
+       expression.c
+
+    This code provides Borne Shell-like expression expansion.
+
+    Copyright (C) 1997-1999  Richard Gooch
+
+       This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+    Richard Gooch may be reached by email at  rgooch@atnf.csiro.au
+    The postal address is:
+      Richard Gooch, c/o ATNF, P. O. Box 76, Epping, N.S.W., 2121, Australia.
+*/
+#include "libbb.h"
+#include "xregex.h"
+#include <syslog.h>
+
+#include <sys/un.h>
+#include <sys/sysmacros.h>
+
+/* Various defines taken from linux/major.h */
+#define IDE0_MAJOR     3
+#define IDE1_MAJOR     22
+#define IDE2_MAJOR     33
+#define IDE3_MAJOR     34
+#define IDE4_MAJOR     56
+#define IDE5_MAJOR     57
+#define IDE6_MAJOR     88
+#define IDE7_MAJOR     89
+#define IDE8_MAJOR     90
+#define IDE9_MAJOR     91
+
+
+/* Various defines taken from linux/devfs_fs.h */
+#define DEVFSD_PROTOCOL_REVISION_KERNEL  5
+#define        DEVFSD_IOCTL_BASE       'd'
+/*  These are the various ioctls  */
+#define DEVFSDIOC_GET_PROTO_REV         _IOR(DEVFSD_IOCTL_BASE, 0, int)
+#define DEVFSDIOC_SET_EVENT_MASK        _IOW(DEVFSD_IOCTL_BASE, 2, int)
+#define DEVFSDIOC_RELEASE_EVENT_QUEUE   _IOW(DEVFSD_IOCTL_BASE, 3, int)
+#define DEVFSDIOC_SET_CONFIG_DEBUG_MASK _IOW(DEVFSD_IOCTL_BASE, 4, int)
+#define DEVFSD_NOTIFY_REGISTERED    0
+#define DEVFSD_NOTIFY_UNREGISTERED  1
+#define DEVFSD_NOTIFY_ASYNC_OPEN    2
+#define DEVFSD_NOTIFY_CLOSE         3
+#define DEVFSD_NOTIFY_LOOKUP        4
+#define DEVFSD_NOTIFY_CHANGE        5
+#define DEVFSD_NOTIFY_CREATE        6
+#define DEVFSD_NOTIFY_DELETE        7
+#define DEVFS_PATHLEN               1024
+/*  Never change this otherwise the binary interface will change   */
+
+struct devfsd_notify_struct
+{      /*  Use native C types to ensure same types in kernel and user space     */
+       unsigned int type;           /*  DEVFSD_NOTIFY_* value                   */
+       unsigned int mode;           /*  Mode of the inode or device entry       */
+       unsigned int major;          /*  Major number of device entry            */
+       unsigned int minor;          /*  Minor number of device entry            */
+       unsigned int uid;            /*  Uid of process, inode or device entry   */
+       unsigned int gid;            /*  Gid of process, inode or device entry   */
+       unsigned int overrun_count;  /*  Number of lost events                   */
+       unsigned int namelen;        /*  Number of characters not including '\0' */
+       /*  The device name MUST come last                                       */
+       char devname[DEVFS_PATHLEN]; /*  This will be '\0' terminated            */
+};
+
+#define BUFFER_SIZE 16384
+#define DEVFSD_VERSION "1.3.25"
+#define CONFIG_FILE  "/etc/devfsd.conf"
+#define MODPROBE               "/sbin/modprobe"
+#define MODPROBE_SWITCH_1 "-k"
+#define MODPROBE_SWITCH_2 "-C"
+#define CONFIG_MODULES_DEVFS "/etc/modules.devfs"
+#define MAX_ARGS     (6 + 1)
+#define MAX_SUBEXPR  10
+#define STRING_LENGTH 255
+
+/* for get_uid_gid() */
+#define UID                    0
+#define GID                    1
+
+/* fork_and_execute() */
+# define DIE                   1
+# define NO_DIE                        0
+
+/* for dir_operation() */
+#define RESTORE                0
+#define SERVICE                1
+#define READ_CONFIG 2
+
+/*  Update only after changing code to reflect new protocol  */
+#define DEVFSD_PROTOCOL_REVISION_DAEMON  5
+
+/*  Compile-time check  */
+#if DEVFSD_PROTOCOL_REVISION_KERNEL != DEVFSD_PROTOCOL_REVISION_DAEMON
+#error protocol version mismatch. Update your kernel headers
+#endif
+
+#define AC_PERMISSIONS                         0
+#define AC_MODLOAD                                     1
+#define AC_EXECUTE                                     2
+#define AC_MFUNCTION                           3       /* not supported by busybox */
+#define AC_CFUNCTION                           4       /* not supported by busybox */
+#define AC_COPY                                                5
+#define AC_IGNORE                                      6
+#define AC_MKOLDCOMPAT                         7
+#define AC_MKNEWCOMPAT                         8
+#define AC_RMOLDCOMPAT                         9
+#define AC_RMNEWCOMPAT                         10
+#define AC_RESTORE                                     11
+
+struct permissions_type
+{
+       mode_t mode;
+       uid_t uid;
+       gid_t gid;
+};
+
+struct execute_type
+{
+       char *argv[MAX_ARGS + 1];  /*  argv[0] must always be the programme  */
+};
+
+struct copy_type
+{
+       const char *source;
+       const char *destination;
+};
+
+struct action_type
+{
+       unsigned int what;
+       unsigned int when;
+};
+
+struct config_entry_struct
+{
+       struct action_type action;
+       regex_t preg;
+       union
+       {
+       struct permissions_type permissions;
+       struct execute_type execute;
+       struct copy_type copy;
+       }
+       u;
+       struct config_entry_struct *next;
+};
+
+struct get_variable_info
+{
+       const struct devfsd_notify_struct *info;
+       const char *devname;
+       char devpath[STRING_LENGTH];
+};
+
+static void dir_operation(int , const char * , int,  unsigned long*);
+static void service(struct stat statbuf, char *path);
+static int st_expr_expand(char *, unsigned, const char *, const char *(*)(const char *, void *), void *);
+static const char *get_old_name(const char *, unsigned, char *, unsigned, unsigned);
+static int mksymlink(const char *oldpath, const char *newpath);
+static void read_config_file(char *path, int optional, unsigned long *event_mask);
+static void process_config_line(const char *, unsigned long *);
+static int  do_servicing(int, unsigned long);
+static void service_name(const struct devfsd_notify_struct *);
+static void action_permissions(const struct devfsd_notify_struct *, const struct config_entry_struct *);
+static void action_execute(const struct devfsd_notify_struct *, const struct config_entry_struct *,
+                                                       const regmatch_t *, unsigned);
+static void action_modload(const struct devfsd_notify_struct *info, const struct config_entry_struct *entry);
+static void action_copy(const struct devfsd_notify_struct *, const struct config_entry_struct *,
+                                                const regmatch_t *, unsigned);
+static void action_compat(const struct devfsd_notify_struct *, unsigned);
+static void free_config(void);
+static void restore(char *spath, struct stat source_stat, int rootlen);
+static int copy_inode(const char *, const struct stat *, mode_t, const char *, const struct stat *);
+static mode_t get_mode(const char *);
+static void signal_handler(int);
+static const char *get_variable(const char *, void *);
+static int make_dir_tree(const char *);
+static int expand_expression(char *, unsigned, const char *, const char *(*)(const char *, void *), void *,
+                                                        const char *, const regmatch_t *, unsigned);
+static void expand_regexp(char *, size_t, const char *, const char *, const regmatch_t *, unsigned);
+static const char *expand_variable(    char *, unsigned, unsigned *, const char *,
+                                                                       const char *(*)(const char *, void *), void *);
+static const char *get_variable_v2(const char *, const char *(*)(const char *, void *), void *);
+static char get_old_ide_name(unsigned , unsigned);
+static char *write_old_sd_name(char *, unsigned, unsigned, const char *);
+
+/* busybox functions */
+static int get_uid_gid(int flag, const char *string);
+static void safe_memcpy(char * dest, const char * src, int len);
+static unsigned int scan_dev_name_common(const char *d, unsigned int n, int addendum, const char *ptr);
+static unsigned int scan_dev_name(const char *d, unsigned int n, const char *ptr);
+
+/* Structs and vars */
+static struct config_entry_struct *first_config = NULL;
+static struct config_entry_struct *last_config = NULL;
+static char *mount_point = NULL;
+static volatile int caught_signal = FALSE;
+static volatile int caught_sighup = FALSE;
+static struct initial_symlink_struct {
+       const char *dest;
+       const char *name;
+} initial_symlinks[] = {
+       {"/proc/self/fd", "fd"},
+       {"fd/0", "stdin"},
+       {"fd/1", "stdout"},
+       {"fd/2", "stderr"},
+       {NULL, NULL},
+};
+
+static struct event_type {
+       unsigned int type;        /*  The DEVFSD_NOTIFY_* value                  */
+       const char *config_name;  /*  The name used in the config file           */
+} event_types[] = {
+       {DEVFSD_NOTIFY_REGISTERED,   "REGISTER"},
+       {DEVFSD_NOTIFY_UNREGISTERED, "UNREGISTER"},
+       {DEVFSD_NOTIFY_ASYNC_OPEN,   "ASYNC_OPEN"},
+       {DEVFSD_NOTIFY_CLOSE,        "CLOSE"},
+       {DEVFSD_NOTIFY_LOOKUP,       "LOOKUP"},
+       {DEVFSD_NOTIFY_CHANGE,       "CHANGE"},
+       {DEVFSD_NOTIFY_CREATE,       "CREATE"},
+       {DEVFSD_NOTIFY_DELETE,       "DELETE"},
+       {0xffffffff,                 NULL}
+};
+
+/* Busybox messages */
+
+static const char bb_msg_proto_rev[] ALIGN1          = "protocol revision";
+static const char bb_msg_bad_config[] ALIGN1         = "bad %s config file: %s";
+static const char bb_msg_small_buffer[] ALIGN1       = "buffer too small";
+static const char bb_msg_variable_not_found[] ALIGN1 = "variable: %s not found";
+
+/* Busybox stuff */
+#if ENABLE_DEVFSD_VERBOSE || ENABLE_DEBUG
+#define info_logger(p, fmt, args...)                 bb_info_msg(fmt, ## args)
+#define msg_logger(p, fmt, args...)                  bb_error_msg(fmt, ## args)
+#define msg_logger_and_die(p, fmt, args...)          bb_error_msg_and_die(fmt, ## args)
+#define error_logger(p, fmt, args...)                bb_perror_msg(fmt, ## args)
+#define error_logger_and_die(p, fmt, args...)        bb_perror_msg_and_die(fmt, ## args)
+#else
+#define info_logger(p, fmt, args...)
+#define msg_logger(p, fmt, args...)
+#define msg_logger_and_die(p, fmt, args...)           exit(EXIT_FAILURE)
+#define error_logger(p, fmt, args...)
+#define error_logger_and_die(p, fmt, args...)         exit(EXIT_FAILURE)
+#endif
+
+static void safe_memcpy(char *dest, const char *src, int len)
+{
+       memcpy(dest , src, len);
+       dest[len] = '\0';
+}
+
+static unsigned int scan_dev_name_common(const char *d, unsigned int n, int addendum, const char *ptr)
+{
+       if (d[n - 4] == 'd' && d[n - 3] == 'i' && d[n - 2] == 's' && d[n - 1] == 'c')
+               return 2 + addendum;
+       if (d[n - 2] == 'c' && d[n - 1] == 'd')
+               return 3 + addendum;
+       if (ptr[0] == 'p' && ptr[1] == 'a' && ptr[2] == 'r' && ptr[3] == 't')
+               return 4 + addendum;
+       if (ptr[n - 2] == 'm' && ptr[n - 1] == 't')
+               return 5 + addendum;
+       return 0;
+}
+
+static unsigned int scan_dev_name(const char *d, unsigned int n, const char *ptr)
+{
+       if (d[0] == 's' && d[1] == 'c' && d[2] == 's' && d[3] == 'i' && d[4] == '/') {
+               if (d[n - 7] == 'g' && d[n - 6] == 'e' && d[n - 5] == 'n'
+                       && d[n - 4] == 'e' && d[n - 3] == 'r' && d[n - 2] == 'i' && d[n - 1] == 'c'
+               )
+                       return 1;
+               return scan_dev_name_common(d, n, 0, ptr);
+       }
+       if (d[0] == 'i' && d[1] == 'd' && d[2] == 'e' && d[3] == '/'
+               && d[4] == 'h' && d[5] == 'o' && d[6] == 's' && d[7] == 't'
+       )
+               return scan_dev_name_common(d, n, 4, ptr);
+       if (d[0] == 's' && d[1] == 'b' && d[2] == 'p' && d[3] == '/')
+               return 10;
+       if (d[0] == 'v' && d[1] == 'c' && d[2] == 'c' && d[3] == '/')
+               return 11;
+       if (d[0] == 'p' && d[1] == 't' && d[2] == 'y' && d[3] == '/')
+               return 12;
+       return 0;
+}
+
+/*  Public functions follow  */
+
+int devfsd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int devfsd_main(int argc, char **argv)
+{
+       int print_version = FALSE;
+       int do_daemon = TRUE;
+       int no_polling = FALSE;
+       int do_scan;
+       int fd, proto_rev, count;
+       unsigned long event_mask = 0;
+       struct sigaction new_action;
+       struct initial_symlink_struct *curr;
+
+       if (argc < 2)
+               bb_show_usage();
+
+       for (count = 2; count < argc; ++count) {
+               if (argv[count][0] == '-') {
+                       if (argv[count][1] == 'v' && !argv[count][2]) /* -v */
+                               print_version = TRUE;
+                       else if (ENABLE_DEVFSD_FG_NP && argv[count][1] == 'f'
+                        && argv[count][2] == 'g' && !argv[count][3]) /* -fg */
+                               do_daemon = FALSE;
+                       else if (ENABLE_DEVFSD_FG_NP && argv[count][1] == 'n'
+                        && argv[count][2] == 'p' && !argv[count][3]) /* -np */
+                               no_polling = TRUE;
+                       else
+                               bb_show_usage();
+               }
+       }
+
+       mount_point = bb_simplify_path(argv[1]);
+
+       xchdir(mount_point);
+
+       fd = xopen(".devfsd", O_RDONLY);
+       close_on_exec_on(fd);
+       xioctl(fd, DEVFSDIOC_GET_PROTO_REV, &proto_rev);
+
+       /*setup initial entries */
+       for (curr = initial_symlinks; curr->dest != NULL; ++curr)
+               symlink(curr->dest, curr->name);
+
+       /* NB: The check for CONFIG_FILE is done in read_config_file() */
+
+       if (print_version || (DEVFSD_PROTOCOL_REVISION_DAEMON != proto_rev)) {
+               printf("%s v%s\nDaemon %s:\t%d\nKernel-side %s:\t%d\n",
+                               applet_name, DEVFSD_VERSION, bb_msg_proto_rev,
+                               DEVFSD_PROTOCOL_REVISION_DAEMON, bb_msg_proto_rev, proto_rev);
+               if (DEVFSD_PROTOCOL_REVISION_DAEMON != proto_rev)
+                       bb_error_msg_and_die("%s mismatch!", bb_msg_proto_rev);
+               exit(EXIT_SUCCESS); /* -v */
+       }
+       /*  Tell kernel we are special(i.e. we get to see hidden entries)  */
+       xioctl(fd, DEVFSDIOC_SET_EVENT_MASK, 0);
+
+       /*  Set up SIGHUP and SIGUSR1 handlers  */
+       sigemptyset(&new_action.sa_mask);
+       new_action.sa_flags = 0;
+       new_action.sa_handler = signal_handler;
+       sigaction_set(SIGHUP, &new_action);
+       sigaction_set(SIGUSR1, &new_action);
+
+       printf("%s v%s started for %s\n", applet_name, DEVFSD_VERSION, mount_point);
+
+       /*  Set umask so that mknod(2), open(2) and mkdir(2) have complete control over permissions  */
+       umask(0);
+       read_config_file((char*)CONFIG_FILE, FALSE, &event_mask);
+       /*  Do the scan before forking, so that boot scripts see the finished product  */
+       dir_operation(SERVICE, mount_point, 0, NULL);
+
+       if (ENABLE_DEVFSD_FG_NP && no_polling)
+               exit(EXIT_SUCCESS);
+
+       if (ENABLE_DEVFSD_VERBOSE || ENABLE_DEBUG)
+               logmode = LOGMODE_BOTH;
+       else if (do_daemon == TRUE)
+               logmode = LOGMODE_SYSLOG;
+       /* This is the default */
+       /*else
+               logmode = LOGMODE_STDIO; */
+
+       if (do_daemon) {
+               /*  Release so that the child can grab it  */
+               xioctl(fd, DEVFSDIOC_RELEASE_EVENT_QUEUE, 0);
+               bb_daemonize_or_rexec(0, argv);
+       } else if (ENABLE_DEVFSD_FG_NP) {
+               setpgid(0, 0);  /*  Become process group leader                    */
+       }
+
+       while (TRUE) {
+               do_scan = do_servicing(fd, event_mask);
+
+               free_config();
+               read_config_file((char*)CONFIG_FILE, FALSE, &event_mask);
+               if (do_scan)
+                       dir_operation(SERVICE, mount_point, 0, NULL);
+       }
+       if (ENABLE_FEATURE_CLEAN_UP) free(mount_point);
+}   /*  End Function main  */
+
+
+/*  Private functions follow  */
+
+static void read_config_file(char *path, int optional, unsigned long *event_mask)
+/*  [SUMMARY] Read a configuration database.
+    <path> The path to read the database from. If this is a directory, all
+    entries in that directory will be read(except hidden entries).
+    <optional> If TRUE, the routine will silently ignore a missing config file.
+    <event_mask> The event mask is written here. This is not initialised.
+    [RETURNS] Nothing.
+*/
+{
+       struct stat statbuf;
+       FILE *fp;
+       char buf[STRING_LENGTH];
+       char *line = NULL;
+       char *p;
+
+       if (stat(path, &statbuf) == 0) {
+               /* Don't read 0 length files: ignored */
+               /*if (statbuf.st_size == 0)
+                               return;*/
+               if (S_ISDIR(statbuf.st_mode)) {
+                       p = bb_simplify_path(path);
+                       dir_operation(READ_CONFIG, p, 0, event_mask);
+                       free(p);
+                       return;
+               }
+               fp = fopen_for_read(path);
+               if (fp != NULL) {
+                       while (fgets(buf, STRING_LENGTH, fp) != NULL) {
+                               /*  Skip whitespace  */
+                               line = buf;
+                               line = skip_whitespace(line);
+                               if (line[0] == '\0' || line[0] == '#')
+                                       continue;
+                               process_config_line(line, event_mask);
+                       }
+                       fclose(fp);
+               } else {
+                       goto read_config_file_err;
+               }
+       } else {
+read_config_file_err:
+               if (optional == 0 && errno == ENOENT)
+                       error_logger_and_die(LOG_ERR, "read config file: %s", path);
+       }
+}   /*  End Function read_config_file   */
+
+static void process_config_line(const char *line, unsigned long *event_mask)
+/*  [SUMMARY] Process a line from a configuration file.
+    <line> The configuration line.
+    <event_mask> The event mask is written here. This is not initialised.
+    [RETURNS] Nothing.
+*/
+{
+       int  num_args, count;
+       struct config_entry_struct *new;
+       char p[MAX_ARGS][STRING_LENGTH];
+       char when[STRING_LENGTH], what[STRING_LENGTH];
+       char name[STRING_LENGTH];
+       const char *msg = "";
+       char *ptr;
+       int i;
+
+       /* !!!! Only Uppercase Keywords in devsfd.conf */
+       static const char options[] ALIGN1 =
+               "CLEAR_CONFIG\0""INCLUDE\0""OPTIONAL_INCLUDE\0"
+               "RESTORE\0""PERMISSIONS\0""MODLOAD\0""EXECUTE\0"
+               "COPY\0""IGNORE\0""MKOLDCOMPAT\0""MKNEWCOMPAT\0"
+               "RMOLDCOMPAT\0""RMNEWCOMPAT\0";
+
+       for (count = 0; count < MAX_ARGS; ++count)
+               p[count][0] = '\0';
+       num_args = sscanf(line, "%s %s %s %s %s %s %s %s %s %s",
+                       when, name, what,
+                       p[0], p[1], p[2], p[3], p[4], p[5], p[6]);
+
+       i = index_in_strings(options, when);
+
+       /* "CLEAR_CONFIG" */
+       if (i == 0) {
+               free_config();
+               *event_mask = 0;
+               return;
+       }
+
+       if (num_args < 2)
+               goto process_config_line_err;
+
+       /* "INCLUDE" & "OPTIONAL_INCLUDE" */
+       if (i == 1 || i == 2) {
+               st_expr_expand(name, STRING_LENGTH, name, get_variable, NULL);
+               info_logger(LOG_INFO, "%sinclude: %s", (toupper(when[0]) == 'I') ? "": "optional_", name);
+               read_config_file(name, (toupper(when[0]) == 'I') ? FALSE : TRUE, event_mask);
+               return;
+       }
+       /* "RESTORE" */
+       if (i == 3) {
+               dir_operation(RESTORE, name, strlen(name),NULL);
+               return;
+       }
+       if (num_args < 3)
+               goto process_config_line_err;
+
+       new = xzalloc(sizeof *new);
+
+       for (count = 0; event_types[count].config_name != NULL; ++count) {
+               if (strcasecmp(when, event_types[count].config_name) != 0)
+                       continue;
+               new->action.when = event_types[count].type;
+               break;
+       }
+       if (event_types[count].config_name == NULL) {
+               msg = "WHEN in";
+               goto process_config_line_err;
+       }
+
+       i = index_in_strings(options, what);
+
+       switch (i) {
+               case 4: /* "PERMISSIONS" */
+                       new->action.what = AC_PERMISSIONS;
+                       /*  Get user and group  */
+                       ptr = strchr(p[0], '.');
+                       if (ptr == NULL) {
+                               msg = "UID.GID";
+                               goto process_config_line_err; /*"missing '.' in UID.GID"*/
+                       }
+
+                       *ptr++ = '\0';
+                       new->u.permissions.uid = get_uid_gid(UID, p[0]);
+                       new->u.permissions.gid = get_uid_gid(GID, ptr);
+                       /*  Get mode  */
+                       new->u.permissions.mode = get_mode(p[1]);
+                       break;
+               case 5: /*  MODLOAD */
+                       /*This  action will pass "/dev/$devname"(i.e. "/dev/" prefixed to
+                       the device name) to the module loading  facility.  In  addition,
+                       the /etc/modules.devfs configuration file is used.*/
+                        if (ENABLE_DEVFSD_MODLOAD)
+                               new->action.what = AC_MODLOAD;
+                        break;
+               case 6: /* EXECUTE */
+                       new->action.what = AC_EXECUTE;
+                       num_args -= 3;
+
+                       for (count = 0; count < num_args; ++count)
+                               new->u.execute.argv[count] = xstrdup(p[count]);
+
+                       new->u.execute.argv[num_args] = NULL;
+                       break;
+               case 7: /* COPY */
+                       new->action.what = AC_COPY;
+                       num_args -= 3;
+                       if (num_args != 2)
+                               goto process_config_line_err; /* missing path and function in line */
+
+                       new->u.copy.source = xstrdup(p[0]);
+                       new->u.copy.destination = xstrdup(p[1]);
+                       break;
+               case 8: /* IGNORE */
+               /* FALLTROUGH */
+               case 9: /* MKOLDCOMPAT */
+               /* FALLTROUGH */
+               case 10: /* MKNEWCOMPAT */
+               /* FALLTROUGH */
+               case 11:/* RMOLDCOMPAT */
+               /* FALLTROUGH */
+               case 12: /* RMNEWCOMPAT */
+               /*      AC_IGNORE                                       6
+                       AC_MKOLDCOMPAT                          7
+                       AC_MKNEWCOMPAT                          8
+                       AC_RMOLDCOMPAT                          9
+                       AC_RMNEWCOMPAT                          10*/
+                       new->action.what = i - 2;
+                       break;
+               default:
+                       msg = "WHAT in";
+                       goto process_config_line_err;
+               /*esac*/
+       } /* switch (i) */
+
+       xregcomp(&new->preg, name, REG_EXTENDED);
+
+       *event_mask |= 1 << new->action.when;
+       new->next = NULL;
+       if (first_config == NULL)
+               first_config = new;
+       else
+               last_config->next = new;
+       last_config = new;
+       return;
+
+ process_config_line_err:
+       msg_logger_and_die(LOG_ERR, bb_msg_bad_config, msg , line);
+}  /*  End Function process_config_line   */
+
+static int do_servicing(int fd, unsigned long event_mask)
+/*  [SUMMARY] Service devfs changes until a signal is received.
+    <fd> The open control file.
+    <event_mask> The event mask.
+    [RETURNS] TRUE if SIGHUP was caught, else FALSE.
+*/
+{
+       ssize_t bytes;
+       struct devfsd_notify_struct info;
+
+       /* (void*) cast is only in order to match prototype */
+       xioctl(fd, DEVFSDIOC_SET_EVENT_MASK, (void*)event_mask);
+       while (!caught_signal) {
+               errno = 0;
+               bytes = read(fd,(char *) &info, sizeof info);
+               if (caught_signal)
+                       break;      /*  Must test for this first     */
+               if (errno == EINTR)
+                       continue;  /*  Yes, the order is important  */
+               if (bytes < 1)
+                       break;
+               service_name(&info);
+       }
+       if (caught_signal) {
+               int c_sighup = caught_sighup;
+
+               caught_signal = FALSE;
+               caught_sighup = FALSE;
+               return c_sighup;
+       }
+       msg_logger_and_die(LOG_ERR, "read error on control file");
+}   /*  End Function do_servicing  */
+
+static void service_name(const struct devfsd_notify_struct *info)
+/*  [SUMMARY] Service a single devfs change.
+    <info> The devfs change.
+    [RETURNS] Nothing.
+*/
+{
+       unsigned int n;
+       regmatch_t mbuf[MAX_SUBEXPR];
+       struct config_entry_struct *entry;
+
+       if (ENABLE_DEBUG && info->overrun_count > 0)
+               msg_logger(LOG_ERR, "lost %u events", info->overrun_count);
+
+       /*  Discard lookups on "/dev/log" and "/dev/initctl"  */
+       if (info->type == DEVFSD_NOTIFY_LOOKUP
+               && ((info->devname[0] == 'l' && info->devname[1] == 'o'
+               && info->devname[2] == 'g' && !info->devname[3])
+               || (info->devname[0] == 'i' && info->devname[1] == 'n'
+               && info->devname[2] == 'i' && info->devname[3] == 't'
+               && info->devname[4] == 'c' && info->devname[5] == 't'
+               && info->devname[6] == 'l' && !info->devname[7]))
+       )
+               return;
+
+       for (entry = first_config; entry != NULL; entry = entry->next) {
+               /*  First check if action matches the type, then check if name matches */
+               if (info->type != entry->action.when
+               || regexec(&entry->preg, info->devname, MAX_SUBEXPR, mbuf, 0) != 0)
+                       continue;
+               for (n = 0;(n < MAX_SUBEXPR) && (mbuf[n].rm_so != -1); ++n)
+                       /* VOID */;
+
+               switch (entry->action.what) {
+                       case AC_PERMISSIONS:
+                               action_permissions(info, entry);
+                               break;
+                       case AC_MODLOAD:
+                               if (ENABLE_DEVFSD_MODLOAD)
+                                       action_modload(info, entry);
+                               break;
+                       case AC_EXECUTE:
+                               action_execute(info, entry, mbuf, n);
+                               break;
+                       case AC_COPY:
+                               action_copy(info, entry, mbuf, n);
+                               break;
+                       case AC_IGNORE:
+                               return;
+                               /*break;*/
+                       case AC_MKOLDCOMPAT:
+                       case AC_MKNEWCOMPAT:
+                       case AC_RMOLDCOMPAT:
+                       case AC_RMNEWCOMPAT:
+                               action_compat(info, entry->action.what);
+                               break;
+                       default:
+                               msg_logger_and_die(LOG_ERR, "Unknown action");
+               }
+       }
+}   /*  End Function service_name  */
+
+static void action_permissions(const struct devfsd_notify_struct *info,
+                               const struct config_entry_struct *entry)
+/*  [SUMMARY] Update permissions for a device entry.
+    <info> The devfs change.
+    <entry> The config file entry.
+    [RETURNS] Nothing.
+*/
+{
+       struct stat statbuf;
+
+       if (stat(info->devname, &statbuf) != 0
+        || chmod(info->devname, (statbuf.st_mode & S_IFMT) | (entry->u.permissions.mode & ~S_IFMT)) != 0
+        || chown(info->devname, entry->u.permissions.uid, entry->u.permissions.gid) != 0
+       )
+               error_logger(LOG_ERR, "Can't chmod or chown: %s", info->devname);
+}   /*  End Function action_permissions  */
+
+static void action_modload(const struct devfsd_notify_struct *info,
+                           const struct config_entry_struct *entry UNUSED_PARAM)
+/*  [SUMMARY] Load a module.
+    <info> The devfs change.
+    <entry> The config file entry.
+    [RETURNS] Nothing.
+*/
+{
+       char *argv[6];
+
+       argv[0] = (char*)MODPROBE;
+       argv[1] = (char*)MODPROBE_SWITCH_1; /* "-k" */
+       argv[2] = (char*)MODPROBE_SWITCH_2; /* "-C" */
+       argv[3] = (char*)CONFIG_MODULES_DEVFS;
+       argv[4] = concat_path_file("/dev", info->devname); /* device */
+       argv[5] = NULL;
+
+       wait4pid(xspawn(argv));
+       free(argv[4]);
+}  /*  End Function action_modload  */
+
+static void action_execute(const struct devfsd_notify_struct *info,
+                           const struct config_entry_struct *entry,
+                           const regmatch_t *regexpr, unsigned int numexpr)
+/*  [SUMMARY] Execute a programme.
+    <info> The devfs change.
+    <entry> The config file entry.
+    <regexpr> The number of subexpression(start, end) offsets within the
+    device name.
+    <numexpr> The number of elements within <<regexpr>>.
+    [RETURNS] Nothing.
+*/
+{
+       unsigned int count;
+       struct get_variable_info gv_info;
+       char *argv[MAX_ARGS + 1];
+       char largv[MAX_ARGS + 1][STRING_LENGTH];
+
+       gv_info.info = info;
+       gv_info.devname = info->devname;
+       snprintf(gv_info.devpath, sizeof(gv_info.devpath), "%s/%s", mount_point, info->devname);
+       for (count = 0; entry->u.execute.argv[count] != NULL; ++count) {
+               expand_expression(largv[count], STRING_LENGTH,
+                               entry->u.execute.argv[count],
+                               get_variable, &gv_info,
+                               gv_info.devname, regexpr, numexpr);
+               argv[count] = largv[count];
+       }
+       argv[count] = NULL;
+       wait4pid(spawn(argv));
+}   /*  End Function action_execute  */
+
+
+static void action_copy(const struct devfsd_notify_struct *info,
+                        const struct config_entry_struct *entry,
+                        const regmatch_t *regexpr, unsigned int numexpr)
+/*  [SUMMARY] Copy permissions.
+    <info> The devfs change.
+    <entry> The config file entry.
+    <regexpr> This list of subexpression(start, end) offsets within the
+    device name.
+    <numexpr> The number of elements in <<regexpr>>.
+    [RETURNS] Nothing.
+*/
+{
+       mode_t new_mode;
+       struct get_variable_info gv_info;
+       struct stat source_stat, dest_stat;
+       char source[STRING_LENGTH], destination[STRING_LENGTH];
+       int ret = 0;
+
+       dest_stat.st_mode = 0;
+
+       if ((info->type == DEVFSD_NOTIFY_CHANGE) && S_ISLNK(info->mode))
+               return;
+       gv_info.info = info;
+       gv_info.devname = info->devname;
+
+       snprintf(gv_info.devpath, sizeof(gv_info.devpath), "%s/%s", mount_point, info->devname);
+       expand_expression(source, STRING_LENGTH, entry->u.copy.source,
+                               get_variable, &gv_info, gv_info.devname,
+                               regexpr, numexpr);
+
+       expand_expression(destination, STRING_LENGTH, entry->u.copy.destination,
+                               get_variable, &gv_info, gv_info.devname,
+                               regexpr, numexpr);
+
+       if (!make_dir_tree(destination) || lstat(source, &source_stat) != 0)
+                       return;
+       lstat(destination, &dest_stat);
+       new_mode = source_stat.st_mode & ~S_ISVTX;
+       if (info->type == DEVFSD_NOTIFY_CREATE)
+               new_mode |= S_ISVTX;
+       else if ((info->type == DEVFSD_NOTIFY_CHANGE) &&(dest_stat.st_mode & S_ISVTX))
+               new_mode |= S_ISVTX;
+       ret = copy_inode(destination, &dest_stat, new_mode, source, &source_stat);
+       if (ENABLE_DEBUG && ret && (errno != EEXIST))
+               error_logger(LOG_ERR, "copy_inode: %s to %s", source, destination);
+}   /*  End Function action_copy  */
+
+static void action_compat(const struct devfsd_notify_struct *info, unsigned int action)
+/*  [SUMMARY] Process a compatibility request.
+    <info> The devfs change.
+    <action> The action to take.
+    [RETURNS] Nothing.
+*/
+{
+       int ret;
+       const char *compat_name = NULL;
+       const char *dest_name = info->devname;
+       const char *ptr;
+       char compat_buf[STRING_LENGTH], dest_buf[STRING_LENGTH];
+       int mode, host, bus, target, lun;
+       unsigned int i;
+       char rewind_;
+       /* 1 to 5  "scsi/" , 6 to 9 "ide/host" */
+       static const char *const fmt[] = {
+               NULL ,
+               "sg/c%db%dt%du%d",              /* scsi/generic */
+               "sd/c%db%dt%du%d",              /* scsi/disc */
+               "sr/c%db%dt%du%d",              /* scsi/cd */
+               "sd/c%db%dt%du%dp%d",           /* scsi/part */
+               "st/c%db%dt%du%dm%d%c",         /* scsi/mt */
+               "ide/hd/c%db%dt%du%d",          /* ide/host/disc */
+               "ide/cd/c%db%dt%du%d",          /* ide/host/cd */
+               "ide/hd/c%db%dt%du%dp%d",       /* ide/host/part */
+               "ide/mt/c%db%dt%du%d%s",        /* ide/host/mt */
+               NULL
+       };
+
+       /*  First construct compatibility name  */
+       switch (action) {
+               case AC_MKOLDCOMPAT:
+               case AC_RMOLDCOMPAT:
+                       compat_name = get_old_name(info->devname, info->namelen, compat_buf, info->major, info->minor);
+                       break;
+               case AC_MKNEWCOMPAT:
+               case AC_RMNEWCOMPAT:
+                       ptr = bb_basename(info->devname);
+                       i = scan_dev_name(info->devname, info->namelen, ptr);
+
+                       /* nothing found */
+                       if (i == 0 || i > 9)
+                               return;
+
+                       sscanf(info->devname + ((i < 6) ? 5 : 4), "host%d/bus%d/target%d/lun%d/", &host, &bus, &target, &lun);
+                       snprintf(dest_buf, sizeof(dest_buf), "../%s", info->devname + (( i > 5) ? 4 : 0));
+                       dest_name = dest_buf;
+                       compat_name = compat_buf;
+
+
+                       /* 1 == scsi/generic  2 == scsi/disc 3 == scsi/cd 6 == ide/host/disc 7 == ide/host/cd */
+                       if (i == 1 || i == 2 || i == 3 || i == 6 || i ==7)
+                               sprintf(compat_buf, fmt[i], host, bus, target, lun);
+
+                       /* 4 == scsi/part 8 == ide/host/part */
+                       if (i == 4 || i == 8)
+                               sprintf(compat_buf, fmt[i], host, bus, target, lun, atoi(ptr + 4));
+
+                       /* 5 == scsi/mt */
+                       if (i == 5) {
+                               rewind_ = info->devname[info->namelen - 1];
+                               if (rewind_ != 'n')
+                                       rewind_ = '\0';
+                               mode=0;
+                               if (ptr[2] ==  'l' /*108*/ || ptr[2] == 'm'/*109*/)
+                                       mode = ptr[2] - 107; /* 1 or 2 */
+                               if (ptr[2] ==  'a')
+                                       mode = 3;
+                               sprintf(compat_buf, fmt[i], host, bus, target, lun, mode, rewind_);
+                       }
+
+                       /* 9 == ide/host/mt */
+                       if (i ==  9)
+                               snprintf(compat_buf, sizeof(compat_buf), fmt[i], host, bus, target, lun, ptr + 2);
+               /* esac */
+       } /* switch (action) */
+
+       if (compat_name == NULL)
+               return;
+
+       /*  Now decide what to do with it  */
+       switch (action) {
+               case AC_MKOLDCOMPAT:
+               case AC_MKNEWCOMPAT:
+                       mksymlink(dest_name, compat_name);
+                       break;
+               case AC_RMOLDCOMPAT:
+               case AC_RMNEWCOMPAT:
+                       ret = unlink(compat_name);
+                       if (ENABLE_DEBUG && ret)
+                               error_logger(LOG_ERR, "unlink: %s", compat_name);
+                       break;
+               /*esac*/
+       } /* switch (action) */
+}   /*  End Function action_compat  */
+
+static void restore(char *spath, struct stat source_stat, int rootlen)
+{
+       char *dpath;
+       struct stat dest_stat;
+
+       dest_stat.st_mode = 0;
+       dpath = concat_path_file(mount_point, spath + rootlen);
+       lstat(dpath, &dest_stat);
+       free(dpath);
+       if (S_ISLNK(source_stat.st_mode) || (source_stat.st_mode & S_ISVTX))
+               copy_inode(dpath, &dest_stat,(source_stat.st_mode & ~S_ISVTX) , spath, &source_stat);
+
+       if (S_ISDIR(source_stat.st_mode))
+               dir_operation(RESTORE, spath, rootlen,NULL);
+}
+
+
+static int copy_inode(const char *destpath, const struct stat *dest_stat,
+                       mode_t new_mode,
+                       const char *sourcepath, const struct stat *source_stat)
+/*  [SUMMARY] Copy an inode.
+    <destpath> The destination path. An existing inode may be deleted.
+    <dest_stat> The destination stat(2) information.
+    <new_mode> The desired new mode for the destination.
+    <sourcepath> The source path.
+    <source_stat> The source stat(2) information.
+    [RETURNS] TRUE on success, else FALSE.
+*/
+{
+       int source_len, dest_len;
+       char source_link[STRING_LENGTH], dest_link[STRING_LENGTH];
+       int fd, val;
+       struct sockaddr_un un_addr;
+       char symlink_val[STRING_LENGTH];
+
+       if ((source_stat->st_mode & S_IFMT) ==(dest_stat->st_mode & S_IFMT)) {
+               /*  Same type  */
+               if (S_ISLNK(source_stat->st_mode)) {
+                       source_len = readlink(sourcepath, source_link, STRING_LENGTH - 1);
+                       if ((source_len < 0)
+                        || (dest_len = readlink(destpath, dest_link, STRING_LENGTH - 1)) < 0
+                       )
+                               return FALSE;
+                       source_link[source_len] = '\0';
+                       dest_link[dest_len]     = '\0';
+                       if ((source_len != dest_len) || (strcmp(source_link, dest_link) != 0)) {
+                               unlink(destpath);
+                               symlink(source_link, destpath);
+                       }
+                       return TRUE;
+               }   /*  Else not a symlink  */
+               chmod(destpath, new_mode & ~S_IFMT);
+               chown(destpath, source_stat->st_uid, source_stat->st_gid);
+               return TRUE;
+       }
+       /*  Different types: unlink and create  */
+       unlink(destpath);
+       switch (source_stat->st_mode & S_IFMT) {
+               case S_IFSOCK:
+                       fd = socket(AF_UNIX, SOCK_STREAM, 0);
+                       if (fd < 0)
+                               break;
+                       un_addr.sun_family = AF_UNIX;
+                       snprintf(un_addr.sun_path, sizeof(un_addr.sun_path), "%s", destpath);
+                       val = bind(fd,(struct sockaddr *) &un_addr,(int) sizeof un_addr);
+                       close(fd);
+                       if (val != 0 || chmod(destpath, new_mode & ~S_IFMT) != 0)
+                               break;
+                       goto do_chown;
+               case S_IFLNK:
+                       val = readlink(sourcepath, symlink_val, STRING_LENGTH - 1);
+                       if (val < 0)
+                               break;
+                       symlink_val[val] = '\0';
+                       if (symlink(symlink_val, destpath) == 0)
+                               return TRUE;
+                       break;
+               case S_IFREG:
+                       fd = open(destpath, O_RDONLY | O_CREAT, new_mode & ~S_IFMT);
+                       if (fd < 0)
+                               break;
+                       close(fd);
+                       if (chmod(destpath, new_mode & ~S_IFMT) != 0)
+                               break;
+                       goto do_chown;
+               case S_IFBLK:
+               case S_IFCHR:
+               case S_IFIFO:
+                       if (mknod(destpath, new_mode, source_stat->st_rdev) != 0)
+                               break;
+                       goto do_chown;
+               case S_IFDIR:
+                       if (mkdir(destpath, new_mode & ~S_IFMT) != 0)
+                               break;
+do_chown:
+                       if (chown(destpath, source_stat->st_uid, source_stat->st_gid) == 0)
+                               return TRUE;
+               /*break;*/
+       }
+       return FALSE;
+}   /*  End Function copy_inode  */
+
+static void free_config(void)
+/*  [SUMMARY] Free the configuration information.
+    [RETURNS] Nothing.
+*/
+{
+       struct config_entry_struct *c_entry;
+       void *next;
+
+       for (c_entry = first_config; c_entry != NULL; c_entry = next) {
+               unsigned int count;
+
+               next = c_entry->next;
+               regfree(&c_entry->preg);
+               if (c_entry->action.what == AC_EXECUTE) {
+                       for (count = 0; count < MAX_ARGS; ++count) {
+                               if (c_entry->u.execute.argv[count] == NULL)
+                                       break;
+                               free(c_entry->u.execute.argv[count]);
+                       }
+               }
+               free(c_entry);
+       }
+       first_config = NULL;
+       last_config = NULL;
+}   /*  End Function free_config  */
+
+static int get_uid_gid(int flag, const char *string)
+/*  [SUMMARY] Convert a string to a UID or GID value.
+       <flag> "UID" or "GID".
+       <string> The string.
+    [RETURNS] The UID or GID value.
+*/
+{
+       struct passwd *pw_ent;
+       struct group *grp_ent;
+       static const char *msg;
+
+       if (ENABLE_DEVFSD_VERBOSE)
+               msg = "user";
+
+       if (isdigit(string[0]) ||((string[0] == '-') && isdigit(string[1])))
+               return atoi(string);
+
+       if (flag == UID && (pw_ent = getpwnam(string)) != NULL)
+               return pw_ent->pw_uid;
+
+       if (flag == GID && (grp_ent = getgrnam(string)) != NULL)
+               return grp_ent->gr_gid;
+       else if (ENABLE_DEVFSD_VERBOSE)
+               msg = "group";
+
+       if (ENABLE_DEVFSD_VERBOSE)
+               msg_logger(LOG_ERR, "unknown %s: %s, defaulting to %cid=0",  msg, string, msg[0]);
+       return 0;
+}/*  End Function get_uid_gid  */
+
+static mode_t get_mode(const char *string)
+/*  [SUMMARY] Convert a string to a mode value.
+    <string> The string.
+    [RETURNS] The mode value.
+*/
+{
+       mode_t mode;
+       int i;
+
+       if (isdigit(string[0]))
+               return strtoul(string, NULL, 8);
+       if (strlen(string) != 9)
+               msg_logger_and_die(LOG_ERR, "bad mode: %s", string);
+
+       mode = 0;
+       i = S_IRUSR;
+       while (i > 0) {
+               if (string[0] == 'r' || string[0] == 'w' || string[0] == 'x')
+                       mode += i;
+               i = i / 2;
+               string++;
+       }
+       return mode;
+}   /*  End Function get_mode  */
+
+static void signal_handler(int sig)
+{
+       caught_signal = TRUE;
+       if (sig == SIGHUP)
+               caught_sighup = TRUE;
+
+       info_logger(LOG_INFO, "Caught signal %d", sig);
+}   /*  End Function signal_handler  */
+
+static const char *get_variable(const char *variable, void *info)
+{
+       static char sbuf[sizeof(int)*3 + 2]; /* sign and NUL */
+       static char *hostname;
+
+       struct get_variable_info *gv_info = info;
+       const char *field_names[] = {
+                       "hostname", "mntpt", "devpath", "devname",
+                       "uid", "gid", "mode", hostname, mount_point,
+                       gv_info->devpath, gv_info->devname, NULL
+       };
+       int i;
+
+       if (!hostname)
+               hostname = safe_gethostname();
+       /* index_in_str_array returns i>=0  */
+       i = index_in_str_array(field_names, variable);
+
+       if (i > 6 || i < 0 || (i > 1 && gv_info == NULL))
+               return NULL;
+       if (i >= 0 && i <= 3)
+               return field_names[i + 7];
+
+       if (i == 4)
+               sprintf(sbuf, "%u", gv_info->info->uid);
+       else if (i == 5)
+               sprintf(sbuf, "%u", gv_info->info->gid);
+       else if (i == 6)
+               sprintf(sbuf, "%o", gv_info->info->mode);
+       return sbuf;
+}   /*  End Function get_variable  */
+
+static void service(struct stat statbuf, char *path)
+{
+       struct devfsd_notify_struct info;
+
+       memset(&info, 0, sizeof info);
+       info.type = DEVFSD_NOTIFY_REGISTERED;
+       info.mode = statbuf.st_mode;
+       info.major = major(statbuf.st_rdev);
+       info.minor = minor(statbuf.st_rdev);
+       info.uid = statbuf.st_uid;
+       info.gid = statbuf.st_gid;
+       snprintf(info.devname, sizeof(info.devname), "%s", path + strlen(mount_point) + 1);
+       info.namelen = strlen(info.devname);
+       service_name(&info);
+       if (S_ISDIR(statbuf.st_mode))
+               dir_operation(SERVICE, path, 0, NULL);
+}
+
+static void dir_operation(int type, const char * dir_name, int var, unsigned long *event_mask)
+/*  [SUMMARY] Scan a directory tree and generate register events on leaf nodes.
+       <flag> To choose which function to perform
+       <dp> The directory pointer. This is closed upon completion.
+    <dir_name> The name of the directory.
+       <rootlen> string length parameter.
+    [RETURNS] Nothing.
+*/
+{
+       struct stat statbuf;
+       DIR *dp;
+       struct dirent *de;
+       char *path;
+
+       dp = warn_opendir(dir_name);
+       if (dp == NULL)
+               return;
+
+       while ((de = readdir(dp)) != NULL) {
+
+               if (de->d_name && DOT_OR_DOTDOT(de->d_name))
+                       continue;
+               path = concat_path_file(dir_name, de->d_name);
+               if (lstat(path, &statbuf) == 0) {
+                       switch (type) {
+                               case SERVICE:
+                                       service(statbuf, path);
+                                       break;
+                               case RESTORE:
+                                       restore(path, statbuf, var);
+                                       break;
+                               case READ_CONFIG:
+                                       read_config_file(path, var, event_mask);
+                                       break;
+                       }
+               }
+               free(path);
+       }
+       closedir(dp);
+}   /*  End Function do_scan_and_service  */
+
+static int mksymlink(const char *oldpath, const char *newpath)
+/*  [SUMMARY] Create a symlink, creating intervening directories as required.
+    <oldpath> The string contained in the symlink.
+    <newpath> The name of the new symlink.
+    [RETURNS] 0 on success, else -1.
+*/
+{
+       if (!make_dir_tree(newpath))
+               return -1;
+
+       if (symlink(oldpath, newpath) != 0) {
+               if (errno != EEXIST)
+                       return -1;
+       }
+       return 0;
+}   /*  End Function mksymlink  */
+
+
+static int make_dir_tree(const char *path)
+/*  [SUMMARY] Creating intervening directories for a path as required.
+    <path> The full pathname(including the leaf node).
+    [RETURNS] TRUE on success, else FALSE.
+*/
+{
+       if (bb_make_directory(dirname((char *)path), -1, FILEUTILS_RECUR) == -1)
+               return FALSE;
+       return TRUE;
+} /*  End Function make_dir_tree  */
+
+static int expand_expression(char *output, unsigned int outsize,
+                             const char *input,
+                             const char *(*get_variable_func)(const char *variable, void *info),
+                             void *info,
+                             const char *devname,
+                             const regmatch_t *ex, unsigned int numexp)
+/*  [SUMMARY] Expand environment variables and regular subexpressions in string.
+    <output> The output expanded expression is written here.
+    <length> The size of the output buffer.
+    <input> The input expression. This may equal <<output>>.
+    <get_variable> A function which will be used to get variable values. If
+    this returns NULL, the environment is searched instead. If this is NULL,
+    only the environment is searched.
+    <info> An arbitrary pointer passed to <<get_variable>>.
+    <devname> Device name; specifically, this is the string that contains all
+    of the regular subexpressions.
+    <ex> Array of start / end offsets into info->devname for each subexpression
+    <numexp> Number of regular subexpressions found in <<devname>>.
+    [RETURNS] TRUE on success, else FALSE.
+*/
+{
+       char temp[STRING_LENGTH];
+
+       if (!st_expr_expand(temp, STRING_LENGTH, input, get_variable_func, info))
+               return FALSE;
+       expand_regexp(output, outsize, temp, devname, ex, numexp);
+       return TRUE;
+}   /*  End Function expand_expression  */
+
+static void expand_regexp(char *output, size_t outsize, const char *input,
+                          const char *devname,
+                          const regmatch_t *ex, unsigned int numex)
+/*  [SUMMARY] Expand all occurrences of the regular subexpressions \0 to \9.
+    <output> The output expanded expression is written here.
+    <outsize> The size of the output buffer.
+    <input> The input expression. This may NOT equal <<output>>, because
+    supporting that would require yet another string-copy. However, it's not
+    hard to write a simple wrapper function to add this functionality for those
+    few cases that need it.
+    <devname> Device name; specifically, this is the string that contains all
+    of the regular subexpressions.
+    <ex> An array of start and end offsets into <<devname>>, one for each
+    subexpression
+    <numex> Number of subexpressions in the offset-array <<ex>>.
+    [RETURNS] Nothing.
+*/
+{
+       const char last_exp = '0' - 1 + numex;
+       int c = -1;
+
+       /*  Guarantee NULL termination by writing an explicit '\0' character into
+       the very last byte  */
+       if (outsize)
+               output[--outsize] = '\0';
+       /*  Copy the input string into the output buffer, replacing '\\' with '\'
+       and '\0' .. '\9' with subexpressions 0 .. 9, if they exist. Other \x
+       codes are deleted  */
+       while ((c != '\0') && (outsize != 0)) {
+               c = *input;
+               ++input;
+               if (c == '\\') {
+                       c = *input;
+                       ++input;
+                       if (c != '\\') {
+                               if ((c >= '0') && (c <= last_exp)) {
+                                       const regmatch_t *subexp = ex + (c - '0');
+                                       unsigned int sublen = subexp->rm_eo - subexp->rm_so;
+
+                                       /*  Range checking  */
+                                       if (sublen > outsize)
+                                               sublen = outsize;
+                                       strncpy(output, devname + subexp->rm_so, sublen);
+                                       output += sublen;
+                                       outsize -= sublen;
+                               }
+                               continue;
+                       }
+               }
+               *output = c;
+               ++output;
+               --outsize;
+       } /* while */
+}   /*  End Function expand_regexp  */
+
+
+/* from compat_name.c */
+
+struct translate_struct
+{
+       const char *match;    /*  The string to match to(up to length)                */
+       const char *format;   /*  Format of output, "%s" takes data past match string,
+                       NULL is effectively "%s"(just more efficient)       */
+};
+
+static struct translate_struct translate_table[] =
+{
+       {"sound/",     NULL},
+       {"printers/",  "lp%s"},
+       {"v4l/",       NULL},
+       {"parports/",  "parport%s"},
+       {"fb/",        "fb%s"},
+       {"netlink/",   NULL},
+       {"loop/",      "loop%s"},
+       {"floppy/",    "fd%s"},
+       {"rd/",        "ram%s"},
+       {"md/",        "md%s"},         /*  Meta-devices                         */
+       {"vc/",        "tty%s"},
+       {"misc/",      NULL},
+       {"isdn/",      NULL},
+       {"pg/",        "pg%s"},         /*  Parallel port generic ATAPI interface*/
+       {"i2c/",       "i2c-%s"},
+       {"staliomem/", "staliomem%s"},  /*  Stallion serial driver control       */
+       {"tts/E",      "ttyE%s"},       /*  Stallion serial driver               */
+       {"cua/E",      "cue%s"},        /*  Stallion serial driver callout       */
+       {"tts/R",      "ttyR%s"},       /*  Rocketport serial driver             */
+       {"cua/R",      "cur%s"},        /*  Rocketport serial driver callout     */
+       {"ip2/",       "ip2%s"},        /*  Computone serial driver control      */
+       {"tts/F",      "ttyF%s"},       /*  Computone serial driver              */
+       {"cua/F",      "cuf%s"},        /*  Computone serial driver callout      */
+       {"tts/C",      "ttyC%s"},       /*  Cyclades serial driver               */
+       {"cua/C",      "cub%s"},        /*  Cyclades serial driver callout       */
+       {"tts/",       "ttyS%s"},       /*  Generic serial: must be after others */
+       {"cua/",       "cua%s"},        /*  Generic serial: must be after others */
+       {"input/js",   "js%s"},         /*  Joystick driver                      */
+       {NULL,         NULL}
+};
+
+const char *get_old_name(const char *devname, unsigned int namelen,
+                         char *buffer, unsigned int major, unsigned int minor)
+/*  [SUMMARY] Translate a kernel-supplied name into an old name.
+    <devname> The device name provided by the kernel.
+    <namelen> The length of the name.
+    <buffer> A buffer that may be used. This should be at least 128 bytes long.
+    <major> The major number for the device.
+    <minor> The minor number for the device.
+    [RETURNS] A pointer to the old name if known, else NULL.
+*/
+{
+       const char *compat_name = NULL;
+       const char *ptr;
+       struct translate_struct *trans;
+       unsigned int i;
+       char mode;
+       int indexx;
+       const char *pty1;
+       const char *pty2;
+       size_t len;
+       /* 1 to 5  "scsi/" , 6 to 9 "ide/host", 10 sbp/, 11 vcc/, 12 pty/ */
+       static const char *const fmt[] = {
+               NULL ,
+               "sg%u",                 /* scsi/generic */
+               NULL,                   /* scsi/disc */
+               "sr%u",                 /* scsi/cd */
+               NULL,                   /* scsi/part */
+               "nst%u%c",              /* scsi/mt */
+               "hd%c"  ,               /* ide/host/disc */
+               "hd%c"  ,               /* ide/host/cd */
+               "hd%c%s",               /* ide/host/part */
+               "%sht%d",               /* ide/host/mt */
+               "sbpcd%u",              /* sbp/ */
+               "vcs%s",                /* vcc/ */
+               "%cty%c%c",             /* pty/ */
+               NULL
+       };
+
+       for (trans = translate_table; trans->match != NULL; ++trans) {
+                len = strlen(trans->match);
+
+               if (strncmp(devname, trans->match, len) == 0) {
+                       if (trans->format == NULL)
+                               return devname + len;
+                       sprintf(buffer, trans->format, devname + len);
+                       return buffer;
+               }
+       }
+
+       ptr = bb_basename(devname);
+       i = scan_dev_name(devname, namelen, ptr);
+
+       if (i > 0 && i < 13)
+               compat_name = buffer;
+       else
+               return NULL;
+
+       /* 1 == scsi/generic, 3 == scsi/cd, 10 == sbp/ */
+       if (i == 1 || i == 3 || i == 10)
+               sprintf(buffer, fmt[i], minor);
+
+       /* 2 ==scsi/disc, 4 == scsi/part */
+       if (i == 2 || i == 4)
+               compat_name = write_old_sd_name(buffer, major, minor,((i == 2) ? "" : (ptr + 4)));
+
+       /* 5 == scsi/mt */
+       if (i == 5) {
+               mode = ptr[2];
+               if (mode == 'n')
+                       mode = '\0';
+               sprintf(buffer, fmt[i], minor & 0x1f, mode);
+               if (devname[namelen - 1] != 'n')
+                       ++compat_name;
+       }
+       /* 6 == ide/host/disc, 7 == ide/host/cd, 8 == ide/host/part */
+       if (i == 6 || i == 7 || i == 8)
+               /* last arg should be ignored for i == 6 or i== 7 */
+               sprintf(buffer, fmt[i] , get_old_ide_name(major, minor), ptr + 4);
+
+       /* 9 ==  ide/host/mt */
+       if (i == 9)
+               sprintf(buffer, fmt[i], ptr + 2, minor & 0x7f);
+
+       /*  11 == vcc/ */
+       if (i == 11) {
+               sprintf(buffer, fmt[i], devname + 4);
+               if (buffer[3] == '0')
+                       buffer[3] = '\0';
+       }
+       /* 12 ==  pty/ */
+       if (i == 12) {
+               pty1 = "pqrstuvwxyzabcde";
+               pty2 = "0123456789abcdef";
+               indexx = atoi(devname + 5);
+               sprintf(buffer, fmt[i], (devname[4] == 'm') ? 'p' : 't', pty1[indexx >> 4], pty2[indexx & 0x0f]);
+       }
+       return compat_name;
+}   /*  End Function get_old_name  */
+
+static char get_old_ide_name(unsigned int major, unsigned int minor)
+/*  [SUMMARY] Get the old IDE name for a device.
+    <major> The major number for the device.
+    <minor> The minor number for the device.
+    [RETURNS] The drive letter.
+*/
+{
+       char letter = 'y';      /* 121 */
+       char c = 'a';           /*  97 */
+       int i = IDE0_MAJOR;
+
+       /* I hope it works like the previous code as it saves a few bytes. Tito ;P */
+       do {
+               if (i == IDE0_MAJOR || i == IDE1_MAJOR || i == IDE2_MAJOR
+                || i == IDE3_MAJOR || i == IDE4_MAJOR || i == IDE5_MAJOR
+                || i == IDE6_MAJOR || i == IDE7_MAJOR || i == IDE8_MAJOR
+                || i == IDE9_MAJOR
+               ) {
+                       if ((unsigned int)i == major) {
+                               letter = c;
+                               break;
+                       }
+                       c += 2;
+               }
+               i++;
+       } while (i <= IDE9_MAJOR);
+
+       if (minor > 63)
+               ++letter;
+       return letter;
+}   /*  End Function get_old_ide_name  */
+
+static char *write_old_sd_name(char *buffer,
+                               unsigned int major, unsigned int minor,
+                               const char *part)
+/*  [SUMMARY] Write the old SCSI disc name to a buffer.
+    <buffer> The buffer to write to.
+    <major> The major number for the device.
+    <minor> The minor number for the device.
+    <part> The partition string. Must be "" for a whole-disc entry.
+    [RETURNS] A pointer to the buffer on success, else NULL.
+*/
+{
+       unsigned int disc_index;
+
+       if (major == 8) {
+               sprintf(buffer, "sd%c%s", 'a' + (minor >> 4), part);
+               return buffer;
+       }
+       if ((major > 64) && (major < 72)) {
+               disc_index = ((major - 64) << 4) +(minor >> 4);
+               if (disc_index < 26)
+                       sprintf(buffer, "sd%c%s", 'a' + disc_index, part);
+               else
+                       sprintf(buffer, "sd%c%c%s", 'a' +(disc_index / 26) - 1, 'a' + disc_index % 26, part);
+               return buffer;
+       }
+       return NULL;
+}   /*  End Function write_old_sd_name  */
+
+
+/*  expression.c */
+
+/*EXPERIMENTAL_FUNCTION*/
+
+int st_expr_expand(char *output, unsigned int length, const char *input,
+                    const char *(*get_variable_func)(const char *variable,
+                                                 void *info),
+                    void *info)
+/*  [SUMMARY] Expand an expression using Borne Shell-like unquoted rules.
+    <output> The output expanded expression is written here.
+    <length> The size of the output buffer.
+    <input> The input expression. This may equal <<output>>.
+    <get_variable> A function which will be used to get variable values. If
+    this returns NULL, the environment is searched instead. If this is NULL,
+    only the environment is searched.
+    <info> An arbitrary pointer passed to <<get_variable>>.
+    [RETURNS] TRUE on success, else FALSE.
+*/
+{
+       char ch;
+       unsigned int len;
+       unsigned int out_pos = 0;
+       const char *env;
+       const char *ptr;
+       struct passwd *pwent;
+       char buffer[BUFFER_SIZE], tmp[STRING_LENGTH];
+
+       if (length > BUFFER_SIZE)
+               length = BUFFER_SIZE;
+       for (; TRUE; ++input) {
+               switch (ch = *input) {
+                       case '$':
+                               /*  Variable expansion  */
+                               input = expand_variable(buffer, length, &out_pos, ++input, get_variable_func, info);
+                               if (input == NULL)
+                                       return FALSE;
+                               break;
+                       case '~':
+                               /*  Home directory expansion  */
+                               ch = input[1];
+                               if (isspace(ch) ||(ch == '/') ||(ch == '\0')) {
+                                       /* User's own home directory: leave separator for next time */
+                                       env = getenv("HOME");
+                                       if (env == NULL) {
+                                               info_logger(LOG_INFO, bb_msg_variable_not_found, "HOME");
+                                               return FALSE;
+                                       }
+                                       len = strlen(env);
+                                       if (len + out_pos >= length)
+                                               goto st_expr_expand_out;
+                                       memcpy(buffer + out_pos, env, len + 1);
+                                       out_pos += len;
+                                       continue;
+                               }
+                               /*  Someone else's home directory  */
+                               for (ptr = ++input; !isspace(ch) && (ch != '/') && (ch != '\0'); ch = *++ptr)
+                                       /* VOID */;
+                               len = ptr - input;
+                               if (len >= sizeof tmp)
+                                       goto st_expr_expand_out;
+                               safe_memcpy(tmp, input, len);
+                               input = ptr - 1;
+                               pwent = getpwnam(tmp);
+                               if (pwent == NULL) {
+                                       info_logger(LOG_INFO, "no pwent for: %s", tmp);
+                                       return FALSE;
+                               }
+                               len = strlen(pwent->pw_dir);
+                               if (len + out_pos >= length)
+                                       goto st_expr_expand_out;
+                               memcpy(buffer + out_pos, pwent->pw_dir, len + 1);
+                               out_pos += len;
+                               break;
+                       case '\0':
+                       /* Falltrough */
+                       default:
+                               if (out_pos >= length)
+                                       goto st_expr_expand_out;
+                               buffer[out_pos++] = ch;
+                               if (ch == '\0') {
+                                       memcpy(output, buffer, out_pos);
+                                       return TRUE;
+                               }
+                               break;
+                       /* esac */
+               }
+       }
+       return FALSE;
+st_expr_expand_out:
+       info_logger(LOG_INFO, bb_msg_small_buffer);
+       return FALSE;
+}   /*  End Function st_expr_expand  */
+
+
+/*  Private functions follow  */
+
+static const char *expand_variable(char *buffer, unsigned int length,
+                                   unsigned int *out_pos, const char *input,
+                                   const char *(*func)(const char *variable,
+                                                        void *info),
+                                   void *info)
+/*  [SUMMARY] Expand a variable.
+    <buffer> The buffer to write to.
+    <length> The length of the output buffer.
+    <out_pos> The current output position. This is updated.
+    <input> A pointer to the input character pointer.
+    <func> A function which will be used to get variable values. If this
+    returns NULL, the environment is searched instead. If this is NULL, only
+    the environment is searched.
+    <info> An arbitrary pointer passed to <<func>>.
+    <errfp> Diagnostic messages are written here.
+    [RETURNS] A pointer to the end of this subexpression on success, else NULL.
+*/
+{
+       char ch;
+       int len;
+       unsigned int open_braces;
+       const char *env, *ptr;
+       char tmp[STRING_LENGTH];
+
+       ch = input[0];
+       if (ch == '$') {
+               /*  Special case for "$$": PID  */
+               sprintf(tmp, "%d",(int) getpid());
+               len = strlen(tmp);
+               if (len + *out_pos >= length)
+                       goto expand_variable_out;
+
+               memcpy(buffer + *out_pos, tmp, len + 1);
+               out_pos += len;
+               return input;
+       }
+       /*  Ordinary variable expansion, possibly in braces  */
+       if (ch != '{') {
+               /*  Simple variable expansion  */
+               for (ptr = input; isalnum(ch) || (ch == '_') || (ch == ':'); ch = *++ptr)
+                       /* VOID */;
+               len = ptr - input;
+               if ((size_t)len >= sizeof tmp)
+                       goto expand_variable_out;
+
+               safe_memcpy(tmp, input, len);
+               input = ptr - 1;
+               env = get_variable_v2(tmp, func, info);
+               if (env == NULL) {
+                       info_logger(LOG_INFO, bb_msg_variable_not_found, tmp);
+                       return NULL;
+               }
+               len = strlen(env);
+               if (len + *out_pos >= length)
+                       goto expand_variable_out;
+
+               memcpy(buffer + *out_pos, env, len + 1);
+               *out_pos += len;
+               return input;
+       }
+       /*  Variable in braces: check for ':' tricks  */
+       ch = *++input;
+       for (ptr = input; isalnum(ch) || (ch == '_'); ch = *++ptr)
+               /* VOID */;
+       if (ch == '}') {
+               /*  Must be simple variable expansion with "${var}"  */
+               len = ptr - input;
+               if ((size_t)len >= sizeof tmp)
+                       goto expand_variable_out;
+
+               safe_memcpy(tmp, input, len);
+               ptr = expand_variable(buffer, length, out_pos, tmp, func, info);
+               if (ptr == NULL)
+                       return NULL;
+               return input + len;
+       }
+       if (ch != ':' || ptr[1] != '-') {
+               info_logger(LOG_INFO, "illegal char in var name");
+               return NULL;
+       }
+       /*  It's that handy "${var:-word}" expression. Check if var is defined  */
+       len = ptr - input;
+       if ((size_t)len >= sizeof tmp)
+               goto expand_variable_out;
+
+       safe_memcpy(tmp, input, len);
+       /*  Move input pointer to ':'  */
+       input = ptr;
+       /*  First skip to closing brace, taking note of nested expressions  */
+       ptr += 2;
+       ch = ptr[0];
+       for (open_braces = 1; open_braces > 0; ch = *++ptr) {
+               switch (ch) {
+                       case '{':
+                               ++open_braces;
+                               break;
+                       case '}':
+                               --open_braces;
+                               break;
+                       case '\0':
+                               info_logger(LOG_INFO, "\"}\" not found in: %s", input);
+                               return NULL;
+                       default:
+                               break;
+               }
+       }
+       --ptr;
+       /*  At this point ptr should point to closing brace of "${var:-word}"  */
+       env = get_variable_v2(tmp, func, info);
+       if (env != NULL) {
+               /*  Found environment variable, so skip the input to the closing brace
+                       and return the variable  */
+               input = ptr;
+               len = strlen(env);
+               if (len + *out_pos >= length)
+                       goto expand_variable_out;
+
+               memcpy(buffer + *out_pos, env, len + 1);
+               *out_pos += len;
+               return input;
+       }
+       /*  Environment variable was not found, so process word. Advance input
+       pointer to start of word in "${var:-word}"  */
+       input += 2;
+       len = ptr - input;
+       if ((size_t)len >= sizeof tmp)
+               goto expand_variable_out;
+
+       safe_memcpy(tmp, input, len);
+       input = ptr;
+       if (!st_expr_expand(tmp, STRING_LENGTH, tmp, func, info))
+               return NULL;
+       len = strlen(tmp);
+       if (len + *out_pos >= length)
+               goto expand_variable_out;
+
+       memcpy(buffer + *out_pos, tmp, len + 1);
+       *out_pos += len;
+       return input;
+expand_variable_out:
+       info_logger(LOG_INFO, bb_msg_small_buffer);
+       return NULL;
+}   /*  End Function expand_variable  */
+
+
+static const char *get_variable_v2(const char *variable,
+                                 const char *(*func)(const char *variable, void *info),
+                                void *info)
+/*  [SUMMARY] Get a variable from the environment or .
+    <variable> The variable name.
+    <func> A function which will be used to get the variable. If this returns
+    NULL, the environment is searched instead. If this is NULL, only the
+    environment is searched.
+    [RETURNS] The value of the variable on success, else NULL.
+*/
+{
+       const char *value;
+
+       if (func != NULL) {
+               value = (*func)(variable, info);
+               if (value != NULL)
+                       return value;
+       }
+       return getenv(variable);
+}   /*  End Function get_variable  */
+
+/* END OF CODE */
diff --git a/miscutils/devmem.c b/miscutils/devmem.c
new file mode 100644 (file)
index 0000000..e13dedc
--- /dev/null
@@ -0,0 +1,128 @@
+/*
+ * Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
+ *  Copyright (C) 2000, Jan-Derk Bakker (J.D.Bakker@its.tudelft.nl)
+ *  Copyright (C) 2008, BusyBox Team. -solar 4/26/08
+ */
+
+#include "libbb.h"
+
+int devmem_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int devmem_main(int argc UNUSED_PARAM, char **argv)
+{
+       void *map_base, *virt_addr;
+       uint64_t read_result;
+       uint64_t writeval = writeval; /* for compiler */
+       off_t target;
+       unsigned page_size = getpagesize();
+       int fd;
+       int width = 8 * sizeof(int);
+
+       /* devmem ADDRESS [WIDTH [VALUE]] */
+// TODO: options?
+// -r: read and output only the value in hex, with 0x prefix
+// -w: write only, no reads before or after, and no output
+// or make this behavior default?
+// Let's try this and see how users react.
+
+       /* ADDRESS */
+       if (!argv[1])
+               bb_show_usage();
+       errno = 0;
+       target = bb_strtoull(argv[1], NULL, 0); /* allows hex, oct etc */
+
+       /* WIDTH */
+       if (argv[2]) {
+               if (isdigit(argv[2][0]) || argv[2][1])
+                       width = xatou(argv[2]);
+               else {
+                       static const char bhwl[] ALIGN1 = "bhwl";
+                       static const uint8_t sizes[] ALIGN1 = {
+                               8 * sizeof(char),
+                               8 * sizeof(short),
+                               8 * sizeof(int),
+                               8 * sizeof(long),
+                               0 /* bad */
+                       };
+                       width = strchrnul(bhwl, (argv[2][0] | 0x20)) - bhwl;
+                       width = sizes[width];
+               }
+               /* VALUE */
+               if (argv[3])
+                       writeval = bb_strtoull(argv[3], NULL, 0);
+       } else { /* argv[2] == NULL */
+               /* make argv[3] to be a valid thing to use */
+               argv--;
+       }
+       if (errno)
+               bb_show_usage(); /* bb_strtouXX failed */
+
+       fd = xopen("/dev/mem", argv[3] ? (O_RDWR | O_SYNC) : (O_RDONLY | O_SYNC));
+       map_base = mmap(NULL,
+                       page_size * 2 /* in case value spans page */,
+                       argv[3] ? (PROT_READ | PROT_WRITE) : PROT_READ,
+                       MAP_SHARED,
+                       fd,
+                       target & ~(off_t)(page_size - 1));
+       if (map_base == MAP_FAILED)
+               bb_perror_msg_and_die("mmap");
+
+//     printf("Memory mapped at address %p.\n", map_base);
+
+       virt_addr = (char*)map_base + (target & (page_size - 1));
+
+       if (!argv[3]) {
+               switch (width) {
+               case 8:
+                       read_result = *(volatile uint8_t*)virt_addr;
+                       break;
+               case 16:
+                       read_result = *(volatile uint16_t*)virt_addr;
+                       break;
+               case 32:
+                       read_result = *(volatile uint32_t*)virt_addr;
+                       break;
+               case 64:
+                       read_result = *(volatile uint64_t*)virt_addr;
+                       break;
+               default:
+                       bb_error_msg_and_die("bad width");
+               }
+//             printf("Value at address 0x%"OFF_FMT"X (%p): 0x%llX\n",
+//                     target, virt_addr,
+//                     (unsigned long long)read_result);
+               /* Zero-padded output shows the width of access just done */
+               printf("0x%0*llX\n", (width >> 2), (unsigned long long)read_result);
+       } else {
+               switch (width) {
+               case 8:
+                       *(volatile uint8_t*)virt_addr = writeval;
+//                     read_result = *(volatile uint8_t*)virt_addr;
+                       break;
+               case 16:
+                       *(volatile uint16_t*)virt_addr = writeval;
+//                     read_result = *(volatile uint16_t*)virt_addr;
+                       break;
+               case 32:
+                       *(volatile uint32_t*)virt_addr = writeval;
+//                     read_result = *(volatile uint32_t*)virt_addr;
+                       break;
+               case 64:
+                       *(volatile uint64_t*)virt_addr = writeval;
+//                     read_result = *(volatile uint64_t*)virt_addr;
+                       break;
+               default:
+                       bb_error_msg_and_die("bad width");
+               }
+//             printf("Written 0x%llX; readback 0x%llX\n",
+//                             (unsigned long long)writeval,
+//                             (unsigned long long)read_result);
+       }
+
+       if (ENABLE_FEATURE_CLEAN_UP) {
+               if (munmap(map_base, page_size * 2) == -1)
+                       bb_perror_msg_and_die("munmap");
+               close(fd);
+       }
+
+       return EXIT_SUCCESS;
+}
diff --git a/miscutils/eject.c b/miscutils/eject.c
new file mode 100644 (file)
index 0000000..ff3976e
--- /dev/null
@@ -0,0 +1,116 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * eject implementation for busybox
+ *
+ * Copyright (C) 2004  Peter Willis <psyphreak@phreaker.net>
+ * Copyright (C) 2005  Tito Ragusa <farmatito@tiscali.it>
+ *
+ * Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
+ */
+
+/*
+ * This is a simple hack of eject based on something Erik posted in #uclibc.
+ * Most of the dirty work blatantly ripped off from cat.c =)
+ */
+
+#include "libbb.h"
+
+/* various defines swiped from linux/cdrom.h */
+#define CDROMCLOSETRAY            0x5319  /* pendant of CDROMEJECT  */
+#define CDROMEJECT                0x5309  /* Ejects the cdrom media */
+#define CDROM_DRIVE_STATUS        0x5326  /* Get tray position, etc. */
+/* drive status possibilities returned by CDROM_DRIVE_STATUS ioctl */
+#define CDS_TRAY_OPEN        2
+
+#define dev_fd 3
+
+/* Code taken from the original eject (http://eject.sourceforge.net/),
+ * refactored it a bit for busybox (ne-bb@nicoerfurth.de) */
+
+#include <scsi/sg.h>
+#include <scsi/scsi.h>
+
+static void eject_scsi(const char *dev)
+{
+       static const char sg_commands[3][6] = {
+               { ALLOW_MEDIUM_REMOVAL, 0, 0, 0, 0, 0 },
+               { START_STOP, 0, 0, 0, 1, 0 },
+               { START_STOP, 0, 0, 0, 2, 0 }
+       };
+
+       unsigned i;
+       unsigned char sense_buffer[32];
+       unsigned char inqBuff[2];
+       sg_io_hdr_t io_hdr;
+
+       if ((ioctl(dev_fd, SG_GET_VERSION_NUM, &i) < 0) || (i < 30000))
+               bb_error_msg_and_die("not a sg device or old sg driver");
+
+       memset(&io_hdr, 0, sizeof(sg_io_hdr_t));
+       io_hdr.interface_id = 'S';
+       io_hdr.cmd_len = 6;
+       io_hdr.mx_sb_len = sizeof(sense_buffer);
+       io_hdr.dxfer_direction = SG_DXFER_NONE;
+       /* io_hdr.dxfer_len = 0; */
+       io_hdr.dxferp = inqBuff;
+       io_hdr.sbp = sense_buffer;
+       io_hdr.timeout = 2000;
+
+       for (i = 0; i < 3; i++) {
+               io_hdr.cmdp = (void *)sg_commands[i];
+               ioctl_or_perror_and_die(dev_fd, SG_IO, (void *)&io_hdr, "%s", dev);
+       }
+
+       /* force kernel to reread partition table when new disc is inserted */
+       ioctl(dev_fd, BLKRRPART);
+}
+
+#define FLAG_CLOSE  1
+#define FLAG_SMART  2
+#define FLAG_SCSI   4
+
+static void eject_cdrom(unsigned flags, const char *dev)
+{
+       int cmd = CDROMEJECT;
+
+       if (flags & FLAG_CLOSE
+        || (flags & FLAG_SMART && ioctl(dev_fd, CDROM_DRIVE_STATUS) == CDS_TRAY_OPEN)
+       ) {
+               cmd = CDROMCLOSETRAY;
+       }
+
+       ioctl_or_perror_and_die(dev_fd, cmd, NULL, "%s", dev);
+}
+
+int eject_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int eject_main(int argc UNUSED_PARAM, char **argv)
+{
+       unsigned flags;
+       const char *device;
+
+       opt_complementary = "?1:t--T:T--t";
+       flags = getopt32(argv, "tT" USE_FEATURE_EJECT_SCSI("s"));
+       device = argv[optind] ? argv[optind] : "/dev/cdrom";
+
+       /* We used to do "umount <device>" here, but it was buggy
+          if something was mounted OVER cdrom and
+          if cdrom is mounted many times.
+
+          This works equally well (or better):
+          #!/bin/sh
+          umount /dev/cdrom
+          eject /dev/cdrom
+       */
+
+       xmove_fd(xopen(device, O_RDONLY|O_NONBLOCK), dev_fd);
+
+       if (ENABLE_FEATURE_EJECT_SCSI && (flags & FLAG_SCSI))
+               eject_scsi(device);
+       else
+               eject_cdrom(flags, device);
+
+       if (ENABLE_FEATURE_CLEAN_UP)
+               close(dev_fd);
+
+       return EXIT_SUCCESS;
+}
diff --git a/miscutils/fbsplash.c b/miscutils/fbsplash.c
new file mode 100644 (file)
index 0000000..ec0f092
--- /dev/null
@@ -0,0 +1,407 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Copyright (C) 2008 Michele Sanges <michele.sanges@gmail.com>
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ *
+ * Usage:
+ * - use kernel option 'vga=xxx' or otherwise enable framebuffer device.
+ * - put somewhere fbsplash.cfg file and an image in .ppm format.
+ * - run applet: $ setsid fbsplash [params] &
+ *      -c: hide cursor
+ *      -d /dev/fbN: framebuffer device (if not /dev/fb0)
+ *      -s path_to_image_file (can be "-" for stdin)
+ *      -i path_to_cfg_file
+ *      -f path_to_fifo (can be "-" for stdin)
+ * - if you want to run it only in presence of a kernel parameter
+ *   (for example fbsplash=on), use:
+ *   grep -q "fbsplash=on" </proc/cmdline && setsid fbsplash [params]
+ * - commands for fifo:
+ *   "NN" (ASCII decimal number) - percentage to show on progress bar.
+ *   "exit" (or just close fifo) - well you guessed it.
+ */
+
+#include "libbb.h"
+#include <linux/fb.h>
+
+/* If you want logging messages on /tmp/fbsplash.log... */
+#define DEBUG 0
+
+#define BYTES_PER_PIXEL 2
+
+typedef unsigned short DATA;
+
+struct globals {
+#if DEBUG
+       bool bdebug_messages;   // enable/disable logging
+       FILE *logfile_fd;       // log file
+#endif
+       unsigned char *addr;    // pointer to framebuffer memory
+       unsigned ns[7];         // n-parameters
+       const char *image_filename;
+       struct fb_var_screeninfo scr_var;
+       struct fb_fix_screeninfo scr_fix;
+};
+#define G (*ptr_to_globals)
+#define INIT_G() do { \
+       SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
+} while (0)
+
+#define nbar_width     ns[0]   // progress bar width
+#define nbar_height    ns[1]   // progress bar height
+#define nbar_posx      ns[2]   // progress bar horizontal position
+#define nbar_posy      ns[3]   // progress bar vertical position
+#define nbar_colr      ns[4]   // progress bar color red component
+#define nbar_colg      ns[5]   // progress bar color green component
+#define nbar_colb      ns[6]   // progress bar color blue component
+
+#if DEBUG
+#define DEBUG_MESSAGE(strMessage, args...) \
+       if (G.bdebug_messages) { \
+               fprintf(G.logfile_fd, "[%s][%s] - %s\n", \
+               __FILE__, __FUNCTION__, strMessage);    \
+       }
+#else
+#define DEBUG_MESSAGE(...) ((void)0)
+#endif
+
+
+/**
+ *     Open and initialize the framebuffer device
+ * \param *strfb_device pointer to framebuffer device
+ */
+static void fb_open(const char *strfb_device)
+{
+       int fbfd = xopen(strfb_device, O_RDWR);
+
+       // framebuffer properties
+       xioctl(fbfd, FBIOGET_VSCREENINFO, &G.scr_var);
+       xioctl(fbfd, FBIOGET_FSCREENINFO, &G.scr_fix);
+
+       if (G.scr_var.bits_per_pixel != 16)
+               bb_error_msg_and_die("only 16 bpp is supported");
+
+       // map the device in memory
+       G.addr = mmap(NULL,
+                       G.scr_var.xres * G.scr_var.yres
+                       * BYTES_PER_PIXEL /*(G.scr_var.bits_per_pixel / 8)*/ ,
+                       PROT_WRITE, MAP_SHARED, fbfd, 0);
+       if (G.addr == MAP_FAILED)
+               bb_perror_msg_and_die("mmap");
+       close(fbfd);
+}
+
+
+/**
+ *     Draw hollow rectangle on framebuffer
+ */
+static void fb_drawrectangle(void)
+{
+       int cnt;
+       DATA thispix;
+       DATA *ptr1, *ptr2;
+       unsigned char nred = G.nbar_colr/2;
+       unsigned char ngreen =  G.nbar_colg/2;
+       unsigned char nblue = G.nbar_colb/2;
+
+       nred   >>= 3;  // 5-bit red
+       ngreen >>= 2;  // 6-bit green
+       nblue  >>= 3;  // 5-bit blue
+       thispix = nblue + (ngreen << 5) + (nred << (5+6));
+
+       // horizontal lines
+       ptr1 = (DATA*)(G.addr + (G.nbar_posy * G.scr_var.xres + G.nbar_posx) * BYTES_PER_PIXEL);
+       ptr2 = (DATA*)(G.addr + ((G.nbar_posy + G.nbar_height - 1) * G.scr_var.xres + G.nbar_posx) * BYTES_PER_PIXEL);
+       cnt = G.nbar_width - 1;
+       do {
+               *ptr1++ = thispix;
+               *ptr2++ = thispix;
+       } while (--cnt >= 0);
+
+       // vertical lines
+       ptr1 = (DATA*)(G.addr + (G.nbar_posy * G.scr_var.xres + G.nbar_posx) * BYTES_PER_PIXEL);
+       ptr2 = (DATA*)(G.addr + (G.nbar_posy * G.scr_var.xres + G.nbar_posx + G.nbar_width - 1) * BYTES_PER_PIXEL);
+       cnt = G.nbar_height - 1 /* HUH?!  G.nbar_posy + G.nbar_height - 1 - G.nbar_posy*/;
+       do {
+               *ptr1 = thispix; ptr1 += G.scr_var.xres;
+               *ptr2 = thispix; ptr2 += G.scr_var.xres;
+       } while (--cnt >= 0);
+}
+
+
+/**
+ *     Draw filled rectangle on framebuffer
+ * \param nx1pos,ny1pos upper left position
+ * \param nx2pos,ny2pos down right position
+ * \param nred,ngreen,nblue rgb color
+ */
+static void fb_drawfullrectangle(int nx1pos, int ny1pos, int nx2pos, int ny2pos,
+       unsigned char nred, unsigned char ngreen, unsigned char nblue)
+{
+       int cnt1, cnt2, nypos;
+       DATA thispix;
+       DATA *ptr;
+
+       nred   >>= 3;  // 5-bit red
+       ngreen >>= 2;  // 6-bit green
+       nblue  >>= 3;  // 5-bit blue
+       thispix = nblue + (ngreen << 5) + (nred << (5+6));
+
+       cnt1 = ny2pos - ny1pos;
+       nypos = ny1pos;
+       do {
+               ptr = (DATA*)(G.addr + (nypos * G.scr_var.xres + nx1pos) * BYTES_PER_PIXEL);
+               cnt2 = nx2pos - nx1pos;
+               do {
+                       *ptr++ = thispix;
+               } while (--cnt2 >= 0);
+
+               nypos++;
+       } while (--cnt1 >= 0);
+}
+
+
+/**
+ *     Draw a progress bar on framebuffer
+ * \param percent percentage of loading
+ */
+static void fb_drawprogressbar(unsigned percent)
+{
+       int i, left_x, top_y, width, height;
+
+       // outer box
+       left_x = G.nbar_posx;
+       top_y = G.nbar_posy;
+       width = G.nbar_width - 1;
+       height = G.nbar_height - 1;
+       if ((height | width) < 0)
+               return;
+       // NB: "width" of 1 actually makes rect with width of 2!
+       fb_drawrectangle();
+
+       // inner "empty" rectangle
+       left_x++;
+       top_y++;
+       width -= 2;
+       height -= 2;
+       if ((height | width) < 0)
+               return;
+       fb_drawfullrectangle(
+                       left_x, top_y,
+                                       left_x + width, top_y + height,
+                       G.nbar_colr, G.nbar_colg, G.nbar_colb);
+
+       if (percent > 0) {
+               // actual progress bar
+               width = width * percent / 100;
+               i = height;
+               if (height == 0)
+                       height++; // divide by 0 is bad
+               while (i >= 0) {
+                       // draw one-line thick "rectangle"
+                       // top line will have gray lvl 200, bottom one 100
+                       unsigned gray_level = 100 + i*100/height;
+                       fb_drawfullrectangle(
+                                       left_x, top_y, left_x + width, top_y,
+                                       gray_level, gray_level, gray_level);
+                       top_y++;
+                       i--;
+               }
+       }
+}
+
+
+/**
+ *     Draw image from PPM file
+ */
+static void fb_drawimage(void)
+{
+       char *head, *ptr;
+       FILE *theme_file;
+       unsigned char *pixline;
+       unsigned i, j, width, height, line_size;
+
+       theme_file = xfopen_stdin(G.image_filename);
+       head = xmalloc(256);
+
+       /* parse ppm header
+        * - A ppm image’s magic number is the two characters "P6".
+        * - Whitespace (blanks, TABs, CRs, LFs).
+        * - A width, formatted as ASCII characters in decimal.
+        * - Whitespace.
+        * - A height, again in ASCII decimal.
+        * - Whitespace.
+        * - The maximum color value (Maxval), again in ASCII decimal. Must be
+        *   less than 65536.
+        * - Newline or other single whitespace character.
+        * - A raster of Width * Height pixels in triplets of rgb
+        *   in pure binary by 1 (or not implemented 2) bytes.
+        */
+       while (1) {
+               if (fgets(head, 256, theme_file) == NULL
+                       /* do not overrun the buffer */
+                       || strlen(bb_common_bufsiz1) >= sizeof(bb_common_bufsiz1) - 256)
+                       bb_error_msg_and_die("bad PPM file '%s'", G.image_filename);
+
+               ptr = memchr(skip_whitespace(head), '#', 256);
+               if (ptr != NULL)
+                       *ptr = 0; /* ignore comments */
+               strcat(bb_common_bufsiz1, head);
+               // width, height, max_color_val
+               if (sscanf(bb_common_bufsiz1, "P6 %u %u %u", &width, &height, &i) == 3
+                       && i <= 255)
+                       break;
+               /* If we do not find a signature throughout the whole file then
+                  we will diagnose this via EOF on read in the head of the loop.  */
+       }
+
+       if (ENABLE_FEATURE_CLEAN_UP)
+               free(head);
+       if (width != G.scr_var.xres || height != G.scr_var.yres)
+               bb_error_msg_and_die("PPM %dx%d does not match screen %dx%d",
+                                                        width, height, G.scr_var.xres, G.scr_var.yres);
+       line_size = width*3;
+       if (width > G.scr_var.xres)
+               width = G.scr_var.xres;
+       if (height > G.scr_var.yres)
+               height = G.scr_var.yres;
+
+       pixline = xmalloc(line_size);
+       for (j = 0; j < height; j++) {
+               unsigned char *pixel = pixline;
+               DATA *src = (DATA *)(G.addr + j * G.scr_fix.line_length);
+
+               if (fread(pixline, 1, line_size, theme_file) != line_size)
+                       bb_error_msg_and_die("bad PPM file '%s'", G.image_filename);
+               for (i = 0; i < width; i++) {
+                       unsigned thispix;
+                       thispix = (((unsigned)pixel[0] << 8) & 0xf800)
+                               | (((unsigned)pixel[1] << 3) & 0x07e0)
+                               | (((unsigned)pixel[2] >> 3));
+                       *src++ = thispix;
+                       pixel += 3;
+               }
+       }
+       if (ENABLE_FEATURE_CLEAN_UP)
+               free(pixline);
+       fclose(theme_file);
+}
+
+
+/**
+ *     Parse configuration file
+ * \param *cfg_filename name of the configuration file
+ */
+static void init(const char *cfg_filename)
+{
+       static const char const param_names[] ALIGN1 =
+               "BAR_WIDTH\0" "BAR_HEIGHT\0"
+               "BAR_LEFT\0" "BAR_TOP\0"
+               "BAR_R\0" "BAR_G\0" "BAR_B\0"
+#if DEBUG
+               "DEBUG\0"
+#endif
+               ;
+       char *token[2];
+       parser_t *parser = config_open2(cfg_filename, xfopen_stdin);
+       while (config_read(parser, token, 2, 2, "#=",
+                                   (PARSE_NORMAL | PARSE_MIN_DIE) & ~(PARSE_TRIM | PARSE_COLLAPSE))) {
+               unsigned val = xatoi_u(token[1]);
+               int i = index_in_strings(param_names, token[0]);
+               if (i < 0)
+                       bb_error_msg_and_die("syntax error: %s", token[0]);
+               if (i >= 0 && i < 7)
+                       G.ns[i] = val;
+#if DEBUG
+               if (i == 7) {
+                       G.bdebug_messages = val;
+                       if (G.bdebug_messages)
+                               G.logfile_fd = xfopen_for_write("/tmp/fbsplash.log");
+               }
+#endif
+       }
+       config_close(parser);
+}
+
+
+int fbsplash_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int fbsplash_main(int argc UNUSED_PARAM, char **argv)
+{
+       const char *fb_device, *cfg_filename, *fifo_filename;
+       FILE *fp = fp; // for compiler
+       char *num_buf;
+       unsigned num;
+       bool bCursorOff;
+
+       INIT_G();
+
+       // parse command line options
+       fb_device = "/dev/fb0";
+       cfg_filename = NULL;
+       fifo_filename = NULL;
+       bCursorOff = 1 & getopt32(argv, "cs:d:i:f:",
+                       &G.image_filename, &fb_device, &cfg_filename, &fifo_filename);
+
+       // parse configuration file
+       if (cfg_filename)
+               init(cfg_filename);
+
+       // We must have -s IMG
+       if (!G.image_filename)
+               bb_show_usage();
+
+       fb_open(fb_device);
+
+       if (fifo_filename && bCursorOff) {
+               // hide cursor (BEFORE any fb ops)
+               full_write(STDOUT_FILENO, "\x1b" "[?25l", 6);
+       }
+
+       fb_drawimage();
+
+       if (!fifo_filename)
+               return EXIT_SUCCESS;
+
+       fp = xfopen_stdin(fifo_filename);
+       if (fp != stdin) {
+               // For named pipes, we want to support this:
+               //  mkfifo cmd_pipe
+               //  fbsplash -f cmd_pipe .... &
+               //  ...
+               //  echo 33 >cmd_pipe
+               //  ...
+               //  echo 66 >cmd_pipe
+               // This means that we don't want fbsplash to get EOF
+               // when last writer closes input end.
+               // The simplest way is to open fifo for writing too
+               // and become an additional writer :)
+               open(fifo_filename, O_WRONLY); // errors are ignored
+       }
+
+       fb_drawprogressbar(0);
+       // Block on read, waiting for some input.
+       // Use of <stdio.h> style I/O allows to correctly
+       // handle a case when we have many buffered lines
+       // already in the pipe
+       while ((num_buf = xmalloc_fgetline(fp)) != NULL) {
+               if (strncmp(num_buf, "exit", 4) == 0) {
+                       DEBUG_MESSAGE("exit");
+                       break;
+               }
+               num = atoi(num_buf);
+               if (isdigit(num_buf[0]) && (num <= 100)) {
+#if DEBUG
+                       char strVal[10];
+                       sprintf(strVal, "%d", num);
+                       DEBUG_MESSAGE(strVal);
+#endif
+                       fb_drawprogressbar(num);
+               }
+               free(num_buf);
+       }
+
+       if (bCursorOff) // restore cursor
+               full_write(STDOUT_FILENO, "\x1b" "[?25h", 6);
+
+       return EXIT_SUCCESS;
+}
diff --git a/miscutils/fbsplash.cfg b/miscutils/fbsplash.cfg
new file mode 100644 (file)
index 0000000..b6cf607
--- /dev/null
@@ -0,0 +1,9 @@
+# progress bar position
+BAR_LEFT=170
+BAR_TOP=300
+BAR_WIDTH=300
+BAR_HEIGHT=20
+# progress bar color
+BAR_R=80
+BAR_G=80
+BAR_B=130
diff --git a/miscutils/flash_eraseall.c b/miscutils/flash_eraseall.c
new file mode 100644 (file)
index 0000000..3e0c06f
--- /dev/null
@@ -0,0 +1,194 @@
+/* vi: set sw=4 ts=4: */
+/* eraseall.c -- erase the whole of a MTD device
+ *
+ * Ported to busybox from mtd-utils.
+ *
+ * Copyright (C) 2000 Arcom Control System Ltd
+ *
+ * Renamed to flash_eraseall.c
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.h"
+#include <mtd/mtd-user.h>
+#include <mtd/jffs2-user.h>
+
+#define OPTION_J       (1 << 0)
+#define OPTION_Q       (1 << 1)
+#define IS_NAND                (1 << 2)
+#define BBTEST         (1 << 3)
+
+struct globals {
+       /* This is used in the cpu_to_je/je_to_cpu macros in jffs2_user.h */
+       int tgt_endian;
+};
+#define G (*(struct globals*)&bb_common_bufsiz1)
+#define target_endian  (G.tgt_endian)
+#define INIT_G() do { \
+       target_endian = __BYTE_ORDER; \
+} while (0)
+
+static uint32_t crc32(uint32_t val, const void *ss, int len,
+               uint32_t *crc32_table)
+{
+       const unsigned char *s = ss;
+       while (--len >= 0)
+               val = crc32_table[(val ^ *s++) & 0xff] ^ (val >> 8);
+       return val;
+}
+
+static void show_progress(mtd_info_t *meminfo, erase_info_t *erase)
+{
+       printf("\rErasing %d Kibyte @ %x -- %2llu %% complete.",
+               (unsigned)meminfo->erasesize / 1024, erase->start,
+               (unsigned long long) erase->start * 100 / meminfo->size);
+       fflush(stdout);
+}
+
+int flash_eraseall_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int flash_eraseall_main(int argc UNUSED_PARAM, char **argv)
+{
+       struct jffs2_unknown_node cleanmarker;
+       mtd_info_t meminfo;
+       int fd, clmpos, clmlen;
+       erase_info_t erase;
+       struct stat st;
+       unsigned int flags;
+       char *mtd_name;
+
+       INIT_G();
+       opt_complementary = "=1";
+       flags = BBTEST | getopt32(argv, "jq");
+
+       mtd_name = argv[optind];
+       xstat(mtd_name, &st);
+       if (!S_ISCHR(st.st_mode))
+               bb_error_msg_and_die("%s: not a char device", mtd_name);
+
+       fd = xopen(mtd_name, O_RDWR);
+
+       xioctl(fd, MEMGETINFO, &meminfo);
+       erase.length = meminfo.erasesize;
+       if (meminfo.type == MTD_NANDFLASH)
+               flags |= IS_NAND;
+
+       clmpos = 0;
+       clmlen = 8;
+       if (flags & OPTION_J) {
+               uint32_t *crc32_table;
+
+               crc32_table = crc32_filltable(NULL, 0);
+
+               cleanmarker.magic = cpu_to_je16(JFFS2_MAGIC_BITMASK);
+               cleanmarker.nodetype = cpu_to_je16(JFFS2_NODETYPE_CLEANMARKER);
+               if (!(flags & IS_NAND))
+                       cleanmarker.totlen = cpu_to_je32(sizeof(struct jffs2_unknown_node));
+               else {
+                       struct nand_oobinfo oobinfo;
+
+                       xioctl(fd, MEMGETOOBSEL, &oobinfo);
+
+                       /* Check for autoplacement */
+                       if (oobinfo.useecc == MTD_NANDECC_AUTOPLACE) {
+                               /* Get the position of the free bytes */
+                               clmpos = oobinfo.oobfree[0][0];
+                               clmlen = oobinfo.oobfree[0][1];
+                               if (clmlen > 8)
+                                       clmlen = 8;
+                               if (clmlen == 0)
+                                       bb_error_msg_and_die("Autoplacement selected and no empty space in oob");
+                       } else {
+                               /* Legacy mode */
+                               switch (meminfo.oobsize) {
+                               case 8:
+                                       clmpos = 6;
+                                       clmlen = 2;
+                                       break;
+                               case 16:
+                                       clmpos = 8;
+                                       /*clmlen = 8;*/
+                                       break;
+                               case 64:
+                                       clmpos = 16;
+                                       /*clmlen = 8;*/
+                                       break;
+                               }
+                       }
+                       cleanmarker.totlen = cpu_to_je32(8);
+               }
+
+               cleanmarker.hdr_crc = cpu_to_je32(crc32(0, &cleanmarker, sizeof(struct jffs2_unknown_node) - 4,
+                                       crc32_table));
+       }
+
+       /* Don't want to destroy progress indicator by bb_error_msg's */
+       applet_name = xasprintf("\n%s: %s", applet_name, mtd_name);
+
+       for (erase.start = 0; erase.start < meminfo.size;
+            erase.start += meminfo.erasesize) {
+               if (flags & BBTEST) {
+                       int ret;
+                       loff_t offset = erase.start;
+
+                       ret = ioctl(fd, MEMGETBADBLOCK, &offset);
+                       if (ret > 0) {
+                               if (!(flags & OPTION_Q))
+                                       bb_info_msg("\nSkipping bad block at 0x%08x", erase.start);
+                               continue;
+                       }
+                       if (ret < 0) {
+                               /* Black block table is not available on certain flash
+                                * types e.g. NOR
+                                */
+                               if (errno == EOPNOTSUPP) {
+                                       flags &= ~BBTEST;
+                                       if (flags & IS_NAND)
+                                               bb_error_msg_and_die("bad block check not available");
+                               } else {
+                                       bb_perror_msg_and_die("MEMGETBADBLOCK error");
+                               }
+                       }
+               }
+
+               if (!(flags & OPTION_Q))
+                       show_progress(&meminfo, &erase);
+
+               xioctl(fd, MEMERASE, &erase);
+
+               /* format for JFFS2 ? */
+               if (!(flags & OPTION_J))
+                       continue;
+
+               /* write cleanmarker */
+               if (flags & IS_NAND) {
+                       struct mtd_oob_buf oob;
+
+                       oob.ptr = (unsigned char *) &cleanmarker;
+                       oob.start = erase.start + clmpos;
+                       oob.length = clmlen;
+                       xioctl(fd, MEMWRITEOOB, &oob);
+               } else {
+                       xlseek(fd, erase.start, SEEK_SET);
+                       /* if (lseek(fd, erase.start, SEEK_SET) < 0) {
+                               bb_perror_msg("MTD %s failure", "seek");
+                               continue;
+                       } */
+                       xwrite(fd, &cleanmarker, sizeof(cleanmarker));
+                       /* if (write(fd, &cleanmarker, sizeof(cleanmarker)) != sizeof(cleanmarker)) {
+                               bb_perror_msg("MTD %s failure", "write");
+                               continue;
+                       } */
+               }
+               if (!(flags & OPTION_Q))
+                       printf(" Cleanmarker written at %x.", erase.start);
+       }
+       if (!(flags & OPTION_Q)) {
+               show_progress(&meminfo, &erase);
+               bb_putchar('\n');
+       }
+
+       if (ENABLE_FEATURE_CLEAN_UP)
+               close(fd);
+       return EXIT_SUCCESS;
+}
diff --git a/miscutils/hdparm.c b/miscutils/hdparm.c
new file mode 100644 (file)
index 0000000..de5d68a
--- /dev/null
@@ -0,0 +1,2063 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * hdparm implementation for busybox
+ *
+ * Copyright (C) [2003] by [Matteo Croce] <3297627799@wind.it>
+ * Hacked by Tito <farmatito@tiscali.it> for size optimization.
+ *
+ * Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
+ *
+ * This program is based on the source code of hdparm: see below...
+ * hdparm.c - Command line interface to get/set hard disk parameters
+ *          - by Mark Lord (C) 1994-2002 -- freely distributable
+ */
+
+#include "libbb.h"
+#include <linux/hdreg.h>
+
+/* device types */
+/* ------------ */
+#define NO_DEV                  0xffff
+#define ATA_DEV                 0x0000
+#define ATAPI_DEV               0x0001
+
+/* word definitions */
+/* ---------------- */
+#define GEN_CONFIG             0   /* general configuration */
+#define LCYLS                  1   /* number of logical cylinders */
+#define CONFIG                 2   /* specific configuration */
+#define LHEADS                 3   /* number of logical heads */
+#define TRACK_BYTES            4   /* number of bytes/track (ATA-1) */
+#define SECT_BYTES             5   /* number of bytes/sector (ATA-1) */
+#define LSECTS                 6   /* number of logical sectors/track */
+#define START_SERIAL            10  /* ASCII serial number */
+#define LENGTH_SERIAL           10  /* 10 words (20 bytes or characters) */
+#define BUF_TYPE               20  /* buffer type (ATA-1) */
+#define BUFFER__SIZE           21  /* buffer size (ATA-1) */
+#define RW_LONG                        22  /* extra bytes in R/W LONG cmd ( < ATA-4)*/
+#define START_FW_REV            23  /* ASCII firmware revision */
+#define LENGTH_FW_REV           4  /*  4 words (8 bytes or characters) */
+#define START_MODEL            27  /* ASCII model number */
+#define LENGTH_MODEL           20  /* 20 words (40 bytes or characters) */
+#define SECTOR_XFER_MAX                47  /* r/w multiple: max sectors xfered */
+#define DWORD_IO               48  /* can do double-word IO (ATA-1 only) */
+#define CAPAB_0                        49  /* capabilities */
+#define CAPAB_1                        50
+#define PIO_MODE               51  /* max PIO mode supported (obsolete)*/
+#define DMA_MODE               52  /* max Singleword DMA mode supported (obs)*/
+#define WHATS_VALID            53  /* what fields are valid */
+#define LCYLS_CUR              54  /* current logical cylinders */
+#define LHEADS_CUR             55  /* current logical heads */
+#define LSECTS_CUR             56  /* current logical sectors/track */
+#define CAPACITY_LSB           57  /* current capacity in sectors */
+#define CAPACITY_MSB           58
+#define SECTOR_XFER_CUR                59  /* r/w multiple: current sectors xfered */
+#define LBA_SECTS_LSB          60  /* LBA: total number of user */
+#define LBA_SECTS_MSB          61  /*      addressable sectors */
+#define SINGLE_DMA             62  /* singleword DMA modes */
+#define MULTI_DMA              63  /* multiword DMA modes */
+#define ADV_PIO_MODES          64  /* advanced PIO modes supported */
+                                   /* multiword DMA xfer cycle time: */
+#define DMA_TIME_MIN           65  /*   - minimum */
+#define DMA_TIME_NORM          66  /*   - manufacturer's recommended */
+                                   /* minimum PIO xfer cycle time: */
+#define PIO_NO_FLOW            67  /*   - without flow control */
+#define PIO_FLOW               68  /*   - with IORDY flow control */
+#define PKT_REL                        71  /* typical #ns from PKT cmd to bus rel */
+#define SVC_NBSY               72  /* typical #ns from SERVICE cmd to !BSY */
+#define CDR_MAJOR              73  /* CD ROM: major version number */
+#define CDR_MINOR              74  /* CD ROM: minor version number */
+#define QUEUE_DEPTH            75  /* queue depth */
+#define MAJOR                  80  /* major version number */
+#define MINOR                  81  /* minor version number */
+#define CMDS_SUPP_0            82  /* command/feature set(s) supported */
+#define CMDS_SUPP_1            83
+#define CMDS_SUPP_2            84
+#define CMDS_EN_0              85  /* command/feature set(s) enabled */
+#define CMDS_EN_1              86
+#define CMDS_EN_2              87
+#define ULTRA_DMA              88  /* ultra DMA modes */
+                                   /* time to complete security erase */
+#define ERASE_TIME             89  /*   - ordinary */
+#define ENH_ERASE_TIME         90  /*   - enhanced */
+#define ADV_PWR                        91  /* current advanced power management level
+                                      in low byte, 0x40 in high byte. */
+#define PSWD_CODE              92  /* master password revision code */
+#define HWRST_RSLT             93  /* hardware reset result */
+#define ACOUSTIC               94  /* acoustic mgmt values ( >= ATA-6) */
+#define LBA_LSB                        100 /* LBA: maximum.  Currently only 48 */
+#define LBA_MID                        101 /*      bits are used, but addr 103 */
+#define LBA_48_MSB             102 /*      has been reserved for LBA in */
+#define LBA_64_MSB             103 /*      the future. */
+#define RM_STAT                        127 /* removable media status notification feature set support */
+#define SECU_STATUS            128 /* security status */
+#define CFA_PWR_MODE           160 /* CFA power mode 1 */
+#define START_MEDIA             176 /* media serial number */
+#define LENGTH_MEDIA            20  /* 20 words (40 bytes or characters)*/
+#define START_MANUF             196 /* media manufacturer I.D. */
+#define LENGTH_MANUF            10  /* 10 words (20 bytes or characters) */
+#define INTEGRITY              255 /* integrity word */
+
+/* bit definitions within the words */
+/* -------------------------------- */
+
+/* many words are considered valid if bit 15 is 0 and bit 14 is 1 */
+#define VALID                  0xc000
+#define VALID_VAL              0x4000
+/* many words are considered invalid if they are either all-0 or all-1 */
+#define NOVAL_0                        0x0000
+#define NOVAL_1                        0xffff
+
+/* word 0: gen_config */
+#define NOT_ATA                        0x8000
+#define NOT_ATAPI              0x4000  /* (check only if bit 15 == 1) */
+#define MEDIA_REMOVABLE                0x0080
+#define DRIVE_NOT_REMOVABLE    0x0040  /* bit obsoleted in ATA 6 */
+#define INCOMPLETE             0x0004
+#define CFA_SUPPORT_VAL                0x848a  /* 848a=CFA feature set support */
+#define DRQ_RESPONSE_TIME      0x0060
+#define DRQ_3MS_VAL            0x0000
+#define DRQ_INTR_VAL           0x0020
+#define DRQ_50US_VAL           0x0040
+#define PKT_SIZE_SUPPORTED     0x0003
+#define PKT_SIZE_12_VAL                0x0000
+#define PKT_SIZE_16_VAL                0x0001
+#define EQPT_TYPE              0x1f00
+#define SHIFT_EQPT             8
+
+#define CDROM 0x0005
+
+/* word 1: number of logical cylinders */
+#define LCYLS_MAX              0x3fff /* maximum allowable value */
+
+/* word 2: specific configuration
+ * (a) require SET FEATURES to spin-up
+ * (b) require spin-up to fully reply to IDENTIFY DEVICE
+ */
+#define STBY_NID_VAL           0x37c8  /*     (a) and     (b) */
+#define STBY_ID_VAL            0x738c  /*     (a) and not (b) */
+#define PWRD_NID_VAL           0x8c73  /* not (a) and     (b) */
+#define PWRD_ID_VAL            0xc837  /* not (a) and not (b) */
+
+/* words 47 & 59: sector_xfer_max & sector_xfer_cur */
+#define SECTOR_XFER            0x00ff  /* sectors xfered on r/w multiple cmds*/
+#define MULTIPLE_SETTING_VALID  0x0100  /* 1=multiple sector setting is valid */
+
+/* word 49: capabilities 0 */
+#define STD_STBY               0x2000  /* 1=standard values supported (ATA); 0=vendor specific values */
+#define IORDY_SUP              0x0800  /* 1=support; 0=may be supported */
+#define IORDY_OFF              0x0400  /* 1=may be disabled */
+#define LBA_SUP                        0x0200  /* 1=Logical Block Address support */
+#define DMA_SUP                        0x0100  /* 1=Direct Memory Access support */
+#define DMA_IL_SUP             0x8000  /* 1=interleaved DMA support (ATAPI) */
+#define CMD_Q_SUP              0x4000  /* 1=command queuing support (ATAPI) */
+#define OVLP_SUP               0x2000  /* 1=overlap operation support (ATAPI) */
+#define SWRST_REQ              0x1000  /* 1=ATA SW reset required (ATAPI, obsolete */
+
+/* word 50: capabilities 1 */
+#define MIN_STANDBY_TIMER      0x0001  /* 1=device specific standby timer value minimum */
+
+/* words 51 & 52: PIO & DMA cycle times */
+#define MODE                   0xff00  /* the mode is in the MSBs */
+
+/* word 53: whats_valid */
+#define OK_W88                 0x0004  /* the ultra_dma info is valid */
+#define OK_W64_70              0x0002  /* see above for word descriptions */
+#define OK_W54_58              0x0001  /* current cyl, head, sector, cap. info valid */
+
+/*word 63,88: dma_mode, ultra_dma_mode*/
+#define MODE_MAX               7       /* bit definitions force udma <=7 (when
+                                        * udma >=8 comes out it'll have to be
+                                        * defined in a new dma_mode word!) */
+
+/* word 64: PIO transfer modes */
+#define PIO_SUP                        0x00ff  /* only bits 0 & 1 are used so far,  */
+#define PIO_MODE_MAX           8       /* but all 8 bits are defined        */
+
+/* word 75: queue_depth */
+#define DEPTH_BITS             0x001f  /* bits used for queue depth */
+
+/* words 80-81: version numbers */
+/* NOVAL_0 or  NOVAL_1 means device does not report version */
+
+/* word 81: minor version number */
+#define MINOR_MAX              0x22
+/* words 82-84: cmds/feats supported */
+#define CMDS_W82               0x77ff  /* word 82: defined command locations*/
+#define CMDS_W83               0x3fff  /* word 83: defined command locations*/
+#define CMDS_W84               0x002f  /* word 83: defined command locations*/
+#define SUPPORT_48_BIT         0x0400
+#define NUM_CMD_FEAT_STR       48
+
+/* words 85-87: cmds/feats enabled */
+/* use cmd_feat_str[] to display what commands and features have
+ * been enabled with words 85-87
+ */
+
+/* words 89, 90, SECU ERASE TIME */
+#define ERASE_BITS      0x00ff
+
+/* word 92: master password revision */
+/* NOVAL_0 or  NOVAL_1 means no support for master password revision */
+
+/* word 93: hw reset result */
+#define CBLID           0x2000  /* CBLID status */
+#define RST0            0x0001  /* 1=reset to device #0 */
+#define DEV_DET         0x0006  /* how device num determined */
+#define JUMPER_VAL      0x0002  /* device num determined by jumper */
+#define CSEL_VAL        0x0004  /* device num determined by CSEL_VAL */
+
+/* word 127: removable media status notification feature set support */
+#define RM_STAT_BITS    0x0003
+#define RM_STAT_SUP     0x0001
+
+/* word 128: security */
+#define SECU_ENABLED    0x0002
+#define SECU_LEVEL      0x0010
+#define NUM_SECU_STR    6
+
+/* word 160: CFA power mode */
+#define VALID_W160              0x8000  /* 1=word valid */
+#define PWR_MODE_REQ            0x2000  /* 1=CFA power mode req'd by some cmds*/
+#define PWR_MODE_OFF            0x1000  /* 1=CFA power moded disabled */
+#define MAX_AMPS                0x0fff  /* value = max current in ma */
+
+/* word 255: integrity */
+#define SIG                     0x00ff  /* signature location */
+#define SIG_VAL                 0x00a5  /* signature value */
+
+#define TIMING_BUF_MB           1
+#define TIMING_BUF_BYTES        (TIMING_BUF_MB * 1024 * 1024)
+
+#undef DO_FLUSHCACHE            /* under construction: force cache flush on -W0 */
+
+
+enum { fd = 3 };
+
+
+struct globals {
+       smallint get_identity, get_geom;
+       smallint do_flush;
+       smallint do_ctimings, do_timings;
+       smallint reread_partn;
+       smallint set_piomode, noisy_piomode;
+       smallint set_readahead, get_readahead;
+       smallint set_readonly, get_readonly;
+       smallint set_unmask, get_unmask;
+       smallint set_mult, get_mult;
+#ifdef HDIO_GET_QDMA
+       smallint get_dma_q;
+#ifdef HDIO_SET_QDMA
+       smallint set_dma_q;
+#endif
+#endif
+       smallint set_nowerr, get_nowerr;
+       smallint set_keep, get_keep;
+       smallint set_io32bit, get_io32bit;
+       int piomode;
+       unsigned long Xreadahead;
+       unsigned long readonly;
+       unsigned long unmask;
+       unsigned long mult;
+#ifdef HDIO_SET_QDMA
+       unsigned long dma_q;
+#endif
+       unsigned long nowerr;
+       unsigned long keep;
+       unsigned long io32bit;
+#if ENABLE_FEATURE_HDPARM_HDIO_GETSET_DMA
+       unsigned long dma;
+       smallint set_dma, get_dma;
+#endif
+#ifdef HDIO_DRIVE_CMD
+       smallint set_xfermode, get_xfermode;
+       smallint set_dkeep, get_dkeep;
+       smallint set_standby, get_standby;
+       smallint set_lookahead, get_lookahead;
+       smallint set_prefetch, get_prefetch;
+       smallint set_defects, get_defects;
+       smallint set_wcache, get_wcache;
+       smallint set_doorlock, get_doorlock;
+       smallint set_seagate, get_seagate;
+       smallint set_standbynow, get_standbynow;
+       smallint set_sleepnow, get_sleepnow;
+       smallint get_powermode;
+       smallint set_apmmode, get_apmmode;
+       int xfermode_requested;
+       unsigned long dkeep;
+       unsigned long standby_requested; /* 0..255 */
+       unsigned long lookahead;
+       unsigned long prefetch;
+       unsigned long defects;
+       unsigned long wcache;
+       unsigned long doorlock;
+       unsigned long apmmode;
+#endif
+       USE_FEATURE_HDPARM_GET_IDENTITY(        smallint get_IDentity;)
+       USE_FEATURE_HDPARM_HDIO_TRISTATE_HWIF(  smallint set_busstate, get_busstate;)
+       USE_FEATURE_HDPARM_HDIO_DRIVE_RESET(    smallint perform_reset;)
+       USE_FEATURE_HDPARM_HDIO_TRISTATE_HWIF(  smallint perform_tristate;)
+       USE_FEATURE_HDPARM_HDIO_UNREGISTER_HWIF(smallint unregister_hwif;)
+       USE_FEATURE_HDPARM_HDIO_SCAN_HWIF(      smallint scan_hwif;)
+       USE_FEATURE_HDPARM_HDIO_TRISTATE_HWIF(  unsigned long busstate;)
+       USE_FEATURE_HDPARM_HDIO_TRISTATE_HWIF(  unsigned long tristate;)
+       USE_FEATURE_HDPARM_HDIO_UNREGISTER_HWIF(unsigned long hwif;)
+#if ENABLE_FEATURE_HDPARM_HDIO_SCAN_HWIF
+       unsigned long hwif_data;
+       unsigned long hwif_ctrl;
+       unsigned long hwif_irq;
+#endif
+#ifdef DO_FLUSHCACHE
+       unsigned char flushcache[4] = { WIN_FLUSHCACHE, 0, 0, 0 };
+#endif
+};
+#define G (*(struct globals*)&bb_common_bufsiz1)
+struct BUG_G_too_big {
+       char BUG_G_too_big[sizeof(G) <= COMMON_BUFSIZE ? 1 : -1];
+};
+#define get_identity       (G.get_identity           )
+#define get_geom           (G.get_geom               )
+#define do_flush           (G.do_flush               )
+#define do_ctimings        (G.do_ctimings            )
+#define do_timings         (G.do_timings             )
+#define reread_partn       (G.reread_partn           )
+#define set_piomode        (G.set_piomode            )
+#define noisy_piomode      (G.noisy_piomode          )
+#define set_readahead      (G.set_readahead          )
+#define get_readahead      (G.get_readahead          )
+#define set_readonly       (G.set_readonly           )
+#define get_readonly       (G.get_readonly           )
+#define set_unmask         (G.set_unmask             )
+#define get_unmask         (G.get_unmask             )
+#define set_mult           (G.set_mult               )
+#define get_mult           (G.get_mult               )
+#define set_dma_q          (G.set_dma_q              )
+#define get_dma_q          (G.get_dma_q              )
+#define set_nowerr         (G.set_nowerr             )
+#define get_nowerr         (G.get_nowerr             )
+#define set_keep           (G.set_keep               )
+#define get_keep           (G.get_keep               )
+#define set_io32bit        (G.set_io32bit            )
+#define get_io32bit        (G.get_io32bit            )
+#define piomode            (G.piomode                )
+#define Xreadahead         (G.Xreadahead             )
+#define readonly           (G.readonly               )
+#define unmask             (G.unmask                 )
+#define mult               (G.mult                   )
+#define dma_q              (G.dma_q                  )
+#define nowerr             (G.nowerr                 )
+#define keep               (G.keep                   )
+#define io32bit            (G.io32bit                )
+#define dma                (G.dma                    )
+#define set_dma            (G.set_dma                )
+#define get_dma            (G.get_dma                )
+#define set_xfermode       (G.set_xfermode           )
+#define get_xfermode       (G.get_xfermode           )
+#define set_dkeep          (G.set_dkeep              )
+#define get_dkeep          (G.get_dkeep              )
+#define set_standby        (G.set_standby            )
+#define get_standby        (G.get_standby            )
+#define set_lookahead      (G.set_lookahead          )
+#define get_lookahead      (G.get_lookahead          )
+#define set_prefetch       (G.set_prefetch           )
+#define get_prefetch       (G.get_prefetch           )
+#define set_defects        (G.set_defects            )
+#define get_defects        (G.get_defects            )
+#define set_wcache         (G.set_wcache             )
+#define get_wcache         (G.get_wcache             )
+#define set_doorlock       (G.set_doorlock           )
+#define get_doorlock       (G.get_doorlock           )
+#define set_seagate        (G.set_seagate            )
+#define get_seagate        (G.get_seagate            )
+#define set_standbynow     (G.set_standbynow         )
+#define get_standbynow     (G.get_standbynow         )
+#define set_sleepnow       (G.set_sleepnow           )
+#define get_sleepnow       (G.get_sleepnow           )
+#define get_powermode      (G.get_powermode          )
+#define set_apmmode        (G.set_apmmode            )
+#define get_apmmode        (G.get_apmmode            )
+#define xfermode_requested (G.xfermode_requested     )
+#define dkeep              (G.dkeep                  )
+#define standby_requested  (G.standby_requested      )
+#define lookahead          (G.lookahead              )
+#define prefetch           (G.prefetch               )
+#define defects            (G.defects                )
+#define wcache             (G.wcache                 )
+#define doorlock           (G.doorlock               )
+#define apmmode            (G.apmmode                )
+#define get_IDentity       (G.get_IDentity           )
+#define set_busstate       (G.set_busstate           )
+#define get_busstate       (G.get_busstate           )
+#define perform_reset      (G.perform_reset          )
+#define perform_tristate   (G.perform_tristate       )
+#define unregister_hwif    (G.unregister_hwif        )
+#define scan_hwif          (G.scan_hwif              )
+#define busstate           (G.busstate               )
+#define tristate           (G.tristate               )
+#define hwif               (G.hwif                   )
+#define hwif_data          (G.hwif_data              )
+#define hwif_ctrl          (G.hwif_ctrl              )
+#define hwif_irq           (G.hwif_irq               )
+
+
+/* Busybox messages and functions */
+#if ENABLE_IOCTL_HEX2STR_ERROR
+static int ioctl_alt_func(/*int fd,*/ int cmd, unsigned char *args, int alt, const char *string)
+{
+       if (!ioctl(fd, cmd, args))
+               return 0;
+       args[0] = alt;
+       return bb_ioctl_or_warn(fd, cmd, args, string);
+}
+#define ioctl_alt_or_warn(cmd,args,alt) ioctl_alt_func(cmd,args,alt,#cmd)
+#else
+static int ioctl_alt_func(/*int fd,*/ int cmd, unsigned char *args, int alt)
+{
+       if (!ioctl(fd, cmd, args))
+               return 0;
+       args[0] = alt;
+       return bb_ioctl_or_warn(fd, cmd, args);
+}
+#define ioctl_alt_or_warn(cmd,args,alt) ioctl_alt_func(cmd,args,alt)
+#endif
+
+static void on_off(int value)
+{
+       puts(value ? " (on)" : " (off)");
+}
+
+static void print_flag_on_off(int get_arg, const char *s, unsigned long arg)
+{
+       if (get_arg) {
+               printf(" setting %s to %ld", s, arg);
+               on_off(arg);
+       }
+}
+
+static void print_value_on_off(const char *str, unsigned long argp)
+{
+       printf(" %s\t= %2ld", str, argp);
+       on_off(argp != 0);
+}
+
+#if ENABLE_FEATURE_HDPARM_GET_IDENTITY
+static void print_ascii(const char *p, int length)
+{
+#if BB_BIG_ENDIAN
+#define LE_ONLY(x)
+       enum { ofs = 0 };
+#else
+#define LE_ONLY(x) x
+       /* every 16bit word is big-endian (i.e. inverted) */
+       /* accessing bytes in 1,0, 3,2, 5,4... sequence */
+       int ofs = 1;
+#endif
+
+       length *= 2;
+       /* find first non-space & print it */
+       while (length && p[ofs] != ' ') {
+               p++;
+               LE_ONLY(ofs = -ofs;)
+               length--;
+       }
+       while (length && p[ofs]) {
+               bb_putchar(p[ofs]);
+               p++;
+               LE_ONLY(ofs = -ofs;)
+               length--;
+       }
+       bb_putchar('\n');
+#undef LE_ONLY
+}
+
+static void xprint_ascii(uint16_t *val, int i, const char *string, int n)
+{
+       if (val[i]) {
+               printf("\t%-20s", string);
+               print_ascii((void*)&val[i], n);
+       }
+}
+
+static uint8_t mode_loop(uint16_t mode_sup, uint16_t mode_sel, int cc, uint8_t *have_mode)
+{
+       uint16_t ii;
+       uint8_t err_dma = 0;
+
+       for (ii = 0; ii <= MODE_MAX; ii++) {
+               if (mode_sel & 0x0001) {
+                       printf("*%cdma%u ", cc, ii);
+                       if (*have_mode)
+                               err_dma = 1;
+                       *have_mode = 1;
+               } else if (mode_sup & 0x0001)
+                       printf("%cdma%u ", cc, ii);
+
+               mode_sup >>= 1;
+               mode_sel >>= 1;
+       }
+       return err_dma;
+}
+
+static const char pkt_str[] ALIGN1 =
+       "Direct-access device" "\0"             /* word 0, bits 12-8 = 00 */
+       "Sequential-access device" "\0"         /* word 0, bits 12-8 = 01 */
+       "Printer" "\0"                          /* word 0, bits 12-8 = 02 */
+       "Processor" "\0"                        /* word 0, bits 12-8 = 03 */
+       "Write-once device" "\0"                /* word 0, bits 12-8 = 04 */
+       "CD-ROM" "\0"                           /* word 0, bits 12-8 = 05 */
+       "Scanner" "\0"                          /* word 0, bits 12-8 = 06 */
+       "Optical memory" "\0"                   /* word 0, bits 12-8 = 07 */
+       "Medium changer" "\0"                   /* word 0, bits 12-8 = 08 */
+       "Communications device" "\0"            /* word 0, bits 12-8 = 09 */
+       "ACS-IT8 device" "\0"                   /* word 0, bits 12-8 = 0a */
+       "ACS-IT8 device" "\0"                   /* word 0, bits 12-8 = 0b */
+       "Array controller" "\0"                 /* word 0, bits 12-8 = 0c */
+       "Enclosure services" "\0"               /* word 0, bits 12-8 = 0d */
+       "Reduced block command device" "\0"     /* word 0, bits 12-8 = 0e */
+       "Optical card reader/writer" "\0"       /* word 0, bits 12-8 = 0f */
+;
+
+static const char ata1_cfg_str[] ALIGN1 =       /* word 0 in ATA-1 mode */
+       "reserved" "\0"                         /* bit 0 */
+       "hard sectored" "\0"                    /* bit 1 */
+       "soft sectored" "\0"                    /* bit 2 */
+       "not MFM encoded " "\0"                 /* bit 3 */
+       "head switch time > 15us" "\0"          /* bit 4 */
+       "spindle motor control option" "\0"     /* bit 5 */
+       "fixed drive" "\0"                      /* bit 6 */
+       "removable drive" "\0"                  /* bit 7 */
+       "disk xfer rate <= 5Mbs" "\0"           /* bit 8 */
+       "disk xfer rate > 5Mbs, <= 10Mbs" "\0"  /* bit 9 */
+       "disk xfer rate > 5Mbs" "\0"            /* bit 10 */
+       "rotational speed tol." "\0"            /* bit 11 */
+       "data strobe offset option" "\0"        /* bit 12 */
+       "track offset option" "\0"              /* bit 13 */
+       "format speed tolerance gap reqd" "\0"  /* bit 14 */
+       "ATAPI"                                 /* bit 14 */
+;
+
+static const char minor_str[] ALIGN1 =
+       /* word 81 value: */
+       "Unspecified" "\0"                                  /* 0x0000 */
+       "ATA-1 X3T9.2 781D prior to rev.4" "\0"             /* 0x0001 */
+       "ATA-1 published, ANSI X3.221-1994" "\0"            /* 0x0002 */
+       "ATA-1 X3T9.2 781D rev.4" "\0"                      /* 0x0003 */
+       "ATA-2 published, ANSI X3.279-1996" "\0"            /* 0x0004 */
+       "ATA-2 X3T10 948D prior to rev.2k" "\0"             /* 0x0005 */
+       "ATA-3 X3T10 2008D rev.1" "\0"                      /* 0x0006 */
+       "ATA-2 X3T10 948D rev.2k" "\0"                      /* 0x0007 */
+       "ATA-3 X3T10 2008D rev.0" "\0"                      /* 0x0008 */
+       "ATA-2 X3T10 948D rev.3" "\0"                       /* 0x0009 */
+       "ATA-3 published, ANSI X3.298-199x" "\0"            /* 0x000a */
+       "ATA-3 X3T10 2008D rev.6" "\0"                      /* 0x000b */
+       "ATA-3 X3T13 2008D rev.7 and 7a" "\0"               /* 0x000c */
+       "ATA/ATAPI-4 X3T13 1153D rev.6" "\0"                /* 0x000d */
+       "ATA/ATAPI-4 T13 1153D rev.13" "\0"                 /* 0x000e */
+       "ATA/ATAPI-4 X3T13 1153D rev.7" "\0"                /* 0x000f */
+       "ATA/ATAPI-4 T13 1153D rev.18" "\0"                 /* 0x0010 */
+       "ATA/ATAPI-4 T13 1153D rev.15" "\0"                 /* 0x0011 */
+       "ATA/ATAPI-4 published, ANSI INCITS 317-1998" "\0"  /* 0x0012 */
+       "ATA/ATAPI-5 T13 1321D rev.3" "\0"                  /* 0x0013 */
+       "ATA/ATAPI-4 T13 1153D rev.14" "\0"                 /* 0x0014 */
+       "ATA/ATAPI-5 T13 1321D rev.1" "\0"                  /* 0x0015 */
+       "ATA/ATAPI-5 published, ANSI INCITS 340-2000" "\0"  /* 0x0016 */
+       "ATA/ATAPI-4 T13 1153D rev.17" "\0"                 /* 0x0017 */
+       "ATA/ATAPI-6 T13 1410D rev.0" "\0"                  /* 0x0018 */
+       "ATA/ATAPI-6 T13 1410D rev.3a" "\0"                 /* 0x0019 */
+       "ATA/ATAPI-7 T13 1532D rev.1" "\0"                  /* 0x001a */
+       "ATA/ATAPI-6 T13 1410D rev.2" "\0"                  /* 0x001b */
+       "ATA/ATAPI-6 T13 1410D rev.1" "\0"                  /* 0x001c */
+       "ATA/ATAPI-7 published, ANSI INCITS 397-2005" "\0"  /* 0x001d */
+       "ATA/ATAPI-7 T13 1532D rev.0" "\0"                  /* 0x001e */
+       "reserved" "\0"                                     /* 0x001f */
+       "reserved" "\0"                                     /* 0x0020 */
+       "ATA/ATAPI-7 T13 1532D rev.4a" "\0"                 /* 0x0021 */
+       "ATA/ATAPI-6 published, ANSI INCITS 361-2002" "\0"  /* 0x0022 */
+       "reserved"                                          /* 0x0023-0xfffe */
+;
+static const char actual_ver[MINOR_MAX + 2] ALIGN1 = {
+          /* word 81 value: */
+       0, /* 0x0000 WARNING: actual_ver[] array */
+       1, /* 0x0001 WARNING: corresponds        */
+       1, /* 0x0002 WARNING: *exactly*          */
+       1, /* 0x0003 WARNING: to the ATA/        */
+       2, /* 0x0004 WARNING: ATAPI version      */
+       2, /* 0x0005 WARNING: listed in          */
+       3, /* 0x0006 WARNING: the                */
+       2, /* 0x0007 WARNING: minor_str          */
+       3, /* 0x0008 WARNING: array              */
+       2, /* 0x0009 WARNING: above.             */
+       3, /* 0x000a WARNING:                    */
+       3, /* 0x000b WARNING: If you change      */
+       3, /* 0x000c WARNING: that one,          */
+       4, /* 0x000d WARNING: change this one    */
+       4, /* 0x000e WARNING: too!!!             */
+       4, /* 0x000f */
+       4, /* 0x0010 */
+       4, /* 0x0011 */
+       4, /* 0x0012 */
+       5, /* 0x0013 */
+       4, /* 0x0014 */
+       5, /* 0x0015 */
+       5, /* 0x0016 */
+       4, /* 0x0017 */
+       6, /* 0x0018 */
+       6, /* 0x0019 */
+       7, /* 0x001a */
+       6, /* 0x001b */
+       6, /* 0x001c */
+       7, /* 0x001d */
+       7, /* 0x001e */
+       0, /* 0x001f */
+       0, /* 0x0020 */
+       7, /* 0x0021 */
+       6, /* 0x0022 */
+       0  /* 0x0023-0xfffe */
+};
+
+static const char cmd_feat_str[] ALIGN1 =
+       "" "\0"                                     /* word 82 bit 15: obsolete  */
+       "NOP cmd" "\0"                              /* word 82 bit 14 */
+       "READ BUFFER cmd" "\0"                      /* word 82 bit 13 */
+       "WRITE BUFFER cmd" "\0"                     /* word 82 bit 12 */
+       "" "\0"                                     /* word 82 bit 11: obsolete  */
+       "Host Protected Area feature set" "\0"      /* word 82 bit 10 */
+       "DEVICE RESET cmd" "\0"                     /* word 82 bit  9 */
+       "SERVICE interrupt" "\0"                    /* word 82 bit  8 */
+       "Release interrupt" "\0"                    /* word 82 bit  7 */
+       "Look-ahead" "\0"                           /* word 82 bit  6 */
+       "Write cache" "\0"                          /* word 82 bit  5 */
+       "PACKET command feature set" "\0"           /* word 82 bit  4 */
+       "Power Management feature set" "\0"         /* word 82 bit  3 */
+       "Removable Media feature set" "\0"          /* word 82 bit  2 */
+       "Security Mode feature set" "\0"            /* word 82 bit  1 */
+       "SMART feature set" "\0"                    /* word 82 bit  0 */
+                                                   /* -------------- */
+       "" "\0"                                     /* word 83 bit 15: !valid bit */
+       "" "\0"                                     /* word 83 bit 14:  valid bit */
+       "FLUSH CACHE EXT cmd" "\0"                  /* word 83 bit 13 */
+       "Mandatory FLUSH CACHE cmd " "\0"           /* word 83 bit 12 */
+       "Device Configuration Overlay feature set " "\0"
+       "48-bit Address feature set " "\0"          /* word 83 bit 10 */
+       "" "\0"
+       "SET MAX security extension" "\0"           /* word 83 bit  8 */
+       "Address Offset Reserved Area Boot" "\0"    /* word 83 bit  7 */
+       "SET FEATURES subcommand required to spinup after power up" "\0"
+       "Power-Up In Standby feature set" "\0"      /* word 83 bit  5 */
+       "Removable Media Status Notification feature set" "\0"
+       "Adv. Power Management feature set" "\0"    /* word 83 bit  3 */
+       "CFA feature set" "\0"                      /* word 83 bit  2 */
+       "READ/WRITE DMA QUEUED" "\0"                /* word 83 bit  1 */
+       "DOWNLOAD MICROCODE cmd" "\0"               /* word 83 bit  0 */
+                                                   /* -------------- */
+       "" "\0"                                     /* word 84 bit 15: !valid bit */
+       "" "\0"                                     /* word 84 bit 14:  valid bit */
+       "" "\0"                                     /* word 84 bit 13:  reserved */
+       "" "\0"                                     /* word 84 bit 12:  reserved */
+       "" "\0"                                     /* word 84 bit 11:  reserved */
+       "" "\0"                                     /* word 84 bit 10:  reserved */
+       "" "\0"                                     /* word 84 bit  9:  reserved */
+       "" "\0"                                     /* word 84 bit  8:  reserved */
+       "" "\0"                                     /* word 84 bit  7:  reserved */
+       "" "\0"                                     /* word 84 bit  6:  reserved */
+       "General Purpose Logging feature set" "\0"  /* word 84 bit  5 */
+       "" "\0"                                     /* word 84 bit  4:  reserved */
+       "Media Card Pass Through Command feature set " "\0"
+       "Media serial number " "\0"                 /* word 84 bit  2 */
+       "SMART self-test " "\0"                     /* word 84 bit  1 */
+       "SMART error logging "                      /* word 84 bit  0 */
+;
+
+static const char secu_str[] ALIGN1 =
+       "supported" "\0"                /* word 128, bit 0 */
+       "enabled" "\0"                  /* word 128, bit 1 */
+       "locked" "\0"                   /* word 128, bit 2 */
+       "frozen" "\0"                   /* word 128, bit 3 */
+       "expired: security count" "\0"  /* word 128, bit 4 */
+       "supported: enhanced erase"     /* word 128, bit 5 */
+;
+
+// Parse 512 byte disk identification block and print much crap.
+static void identify(uint16_t *val) NORETURN;
+static void identify(uint16_t *val)
+{
+       uint16_t ii, jj, kk;
+       uint16_t like_std = 1, std = 0, min_std = 0xffff;
+       uint16_t dev = NO_DEV, eqpt = NO_DEV;
+       uint8_t  have_mode = 0, err_dma = 0;
+       uint8_t  chksum = 0;
+       uint32_t ll, mm, nn, oo;
+       uint64_t bbbig; /* (:) */
+       const char *strng;
+#if BB_BIG_ENDIAN
+       uint16_t buf[256];
+
+       // Adjust for endianness
+       swab(val, buf, sizeof(buf));
+       val = buf;
+#endif
+       /* check if we recognise the device type */
+       bb_putchar('\n');
+       if (!(val[GEN_CONFIG] & NOT_ATA)) {
+               dev = ATA_DEV;
+               printf("ATA device, with ");
+       } else if (val[GEN_CONFIG]==CFA_SUPPORT_VAL) {
+               dev = ATA_DEV;
+               like_std = 4;
+               printf("CompactFlash ATA device, with ");
+       } else if (!(val[GEN_CONFIG] & NOT_ATAPI)) {
+               dev = ATAPI_DEV;
+               eqpt = (val[GEN_CONFIG] & EQPT_TYPE) >> SHIFT_EQPT;
+               printf("ATAPI %s, with ", eqpt <= 0xf ? nth_string(pkt_str, eqpt) : "unknown");
+               like_std = 3;
+       } else
+               /* "Unknown device type:\n\tbits 15&14 of general configuration word 0 both set to 1.\n" */
+               bb_error_msg_and_die("unknown device type");
+
+       printf("%sremovable media\n", !(val[GEN_CONFIG] & MEDIA_REMOVABLE) ? "non-" : "");
+       /* Info from the specific configuration word says whether or not the
+        * ID command completed correctly.  It is only defined, however in
+        * ATA/ATAPI-5 & 6; it is reserved (value theoretically 0) in prior
+        * standards.  Since the values allowed for this word are extremely
+        * specific, it should be safe to check it now, even though we don't
+        * know yet what standard this device is using.
+        */
+       if ((val[CONFIG]==STBY_NID_VAL) || (val[CONFIG]==STBY_ID_VAL)
+        || (val[CONFIG]==PWRD_NID_VAL) || (val[CONFIG]==PWRD_ID_VAL)
+       ) {
+               like_std = 5;
+               if ((val[CONFIG]==STBY_NID_VAL) || (val[CONFIG]==STBY_ID_VAL))
+                       printf("powers-up in standby; SET FEATURES subcmd spins-up.\n");
+               if (((val[CONFIG]==STBY_NID_VAL) || (val[CONFIG]==PWRD_NID_VAL)) && (val[GEN_CONFIG] & INCOMPLETE))
+                       printf("\n\tWARNING: ID response incomplete.\n\tFollowing data may be incorrect.\n\n");
+       }
+
+       /* output the model and serial numbers and the fw revision */
+       xprint_ascii(val, START_MODEL,  "Model Number:",        LENGTH_MODEL);
+       xprint_ascii(val, START_SERIAL, "Serial Number:",       LENGTH_SERIAL);
+       xprint_ascii(val, START_FW_REV, "Firmware Revision:",   LENGTH_FW_REV);
+       xprint_ascii(val, START_MEDIA,  "Media Serial Num:",    LENGTH_MEDIA);
+       xprint_ascii(val, START_MANUF,  "Media Manufacturer:",  LENGTH_MANUF);
+
+       /* major & minor standards version number (Note: these words were not
+        * defined until ATA-3 & the CDROM std uses different words.) */
+       printf("Standards:");
+       if (eqpt != CDROM) {
+               if (val[MINOR] && (val[MINOR] <= MINOR_MAX)) {
+                       if (like_std < 3) like_std = 3;
+                       std = actual_ver[val[MINOR]];
+                       if (std) printf("\n\tUsed: %s ", nth_string(minor_str, val[MINOR]));
+
+               }
+               /* looks like when they up-issue the std, they obsolete one;
+                * thus, only the newest 4 issues need be supported. (That's
+                * what "kk" and "min_std" are all about.) */
+               if (val[MAJOR] && (val[MAJOR] != NOVAL_1)) {
+                       printf("\n\tSupported: ");
+                       jj = val[MAJOR] << 1;
+                       kk = like_std >4 ? like_std-4: 0;
+                       for (ii = 14; (ii >0)&&(ii>kk); ii--) {
+                               if (jj & 0x8000) {
+                                       printf("%u ", ii);
+                                       if (like_std < ii) {
+                                               like_std = ii;
+                                               kk = like_std >4 ? like_std-4: 0;
+                                       }
+                                       if (min_std > ii) min_std = ii;
+                               }
+                               jj <<= 1;
+                       }
+                       if (like_std < 3) like_std = 3;
+               }
+               /* Figure out what standard the device is using if it hasn't told
+                * us.  If we know the std, check if the device is using any of
+                * the words from the next level up.  It happens.
+                */
+               if (like_std < std) like_std = std;
+
+               if (((std == 5) || (!std && (like_std < 6))) &&
+                       ((((val[CMDS_SUPP_1] & VALID) == VALID_VAL) &&
+                       ((      val[CMDS_SUPP_1] & CMDS_W83) > 0x00ff)) ||
+                       (((     val[CMDS_SUPP_2] & VALID) == VALID_VAL) &&
+                       (       val[CMDS_SUPP_2] & CMDS_W84) ) )
+               ) {
+                       like_std = 6;
+               } else if (((std == 4) || (!std && (like_std < 5))) &&
+                       ((((val[INTEGRITY]      & SIG) == SIG_VAL) && !chksum) ||
+                       ((      val[HWRST_RSLT] & VALID) == VALID_VAL) ||
+                       (((     val[CMDS_SUPP_1] & VALID) == VALID_VAL) &&
+                       ((      val[CMDS_SUPP_1] & CMDS_W83) > 0x001f)) ) )
+               {
+                       like_std = 5;
+               } else if (((std == 3) || (!std && (like_std < 4))) &&
+                               ((((val[CMDS_SUPP_1] & VALID) == VALID_VAL) &&
+                               (((     val[CMDS_SUPP_1] & CMDS_W83) > 0x0000) ||
+                               ((      val[CMDS_SUPP_0] & CMDS_W82) > 0x000f))) ||
+                               ((      val[CAPAB_1] & VALID) == VALID_VAL) ||
+                               ((      val[WHATS_VALID] & OK_W88) && val[ULTRA_DMA]) ||
+                               ((      val[RM_STAT] & RM_STAT_BITS) == RM_STAT_SUP) )
+               ) {
+                       like_std = 4;
+               } else if (((std == 2) || (!std && (like_std < 3)))
+                && ((val[CMDS_SUPP_1] & VALID) == VALID_VAL)
+               ) {
+                       like_std = 3;
+               } else if (((std == 1) || (!std && (like_std < 2))) &&
+                               ((val[CAPAB_0] & (IORDY_SUP | IORDY_OFF)) ||
+                               (val[WHATS_VALID] & OK_W64_70)) )
+               {
+                       like_std = 2;
+               }
+
+               if (!std)
+                       printf("\n\tLikely used: %u\n", like_std);
+               else if (like_std > std)
+                       printf("& some of %u\n", like_std);
+               else
+                       bb_putchar('\n');
+       } else {
+               /* TBD: do CDROM stuff more thoroughly.  For now... */
+               kk = 0;
+               if (val[CDR_MINOR] == 9) {
+                       kk = 1;
+                       printf("\n\tUsed: ATAPI for CD-ROMs, SFF-8020i, r2.5");
+               }
+               if (val[CDR_MAJOR] && (val[CDR_MAJOR] !=NOVAL_1)) {
+                       kk = 1;
+                       printf("\n\tSupported: CD-ROM ATAPI");
+                       jj = val[CDR_MAJOR] >> 1;
+                       for (ii = 1; ii < 15; ii++) {
+                               if (jj & 0x0001) printf("-%u ", ii);
+                               jj >>= 1;
+                       }
+               }
+               puts(kk ? "" : "\n\tLikely used CD-ROM ATAPI-1");
+               /* the cdrom stuff is more like ATA-2 than anything else, so: */
+               like_std = 2;
+       }
+
+       if (min_std == 0xffff)
+               min_std = like_std > 4 ? like_std - 3 : 1;
+
+       printf("Configuration:\n");
+       /* more info from the general configuration word */
+       if ((eqpt != CDROM) && (like_std == 1)) {
+               jj = val[GEN_CONFIG] >> 1;
+               for (ii = 1; ii < 15; ii++) {
+                       if (jj & 0x0001)
+                               printf("\t%s\n", nth_string(ata1_cfg_str, ii));
+                       jj >>=1;
+               }
+       }
+       if (dev == ATAPI_DEV) {
+               if ((val[GEN_CONFIG] & DRQ_RESPONSE_TIME) ==  DRQ_3MS_VAL)
+                       strng = "3ms";
+               else if ((val[GEN_CONFIG] & DRQ_RESPONSE_TIME) ==  DRQ_INTR_VAL)
+                       strng = "<=10ms with INTRQ";
+               else if ((val[GEN_CONFIG] & DRQ_RESPONSE_TIME) ==  DRQ_50US_VAL)
+                       strng ="50us";
+               else
+                       strng = "unknown";
+               printf("\tDRQ response: %s\n\tPacket size: ", strng); /* Data Request (DRQ) */
+
+               if ((val[GEN_CONFIG] & PKT_SIZE_SUPPORTED) == PKT_SIZE_12_VAL)
+                       strng = "12 bytes";
+               else if ((val[GEN_CONFIG] & PKT_SIZE_SUPPORTED) == PKT_SIZE_16_VAL)
+                       strng = "16 bytes";
+               else
+                       strng = "unknown";
+               puts(strng);
+       } else {
+               /* addressing...CHS? See section 6.2 of ATA specs 4 or 5 */
+               ll = (uint32_t)val[LBA_SECTS_MSB] << 16 | val[LBA_SECTS_LSB];
+               mm = 0; bbbig = 0;
+               if ((ll > 0x00FBFC10) && (!val[LCYLS]))
+                       printf("\tCHS addressing not supported\n");
+               else {
+                       jj = val[WHATS_VALID] & OK_W54_58;
+                       printf("\tLogical\t\tmax\tcurrent\n\tcylinders\t%u\t%u\n\theads\t\t%u\t%u\n\tsectors/track\t%u\t%u\n\t--\n",
+                                       val[LCYLS],jj?val[LCYLS_CUR]:0, val[LHEADS],jj?val[LHEADS_CUR]:0, val[LSECTS],jj?val[LSECTS_CUR]:0);
+
+                       if ((min_std == 1) && (val[TRACK_BYTES] || val[SECT_BYTES]))
+                               printf("\tbytes/track: %u\tbytes/sector: %u\n", val[TRACK_BYTES], val[SECT_BYTES]);
+
+                       if (jj) {
+                               mm = (uint32_t)val[CAPACITY_MSB] << 16 | val[CAPACITY_LSB];
+                               if (like_std < 3) {
+                                       /* check Endian of capacity bytes */
+                                       nn = val[LCYLS_CUR] * val[LHEADS_CUR] * val[LSECTS_CUR];
+                                       oo = (uint32_t)val[CAPACITY_LSB] << 16 | val[CAPACITY_MSB];
+                                       if (abs(mm - nn) > abs(oo - nn))
+                                               mm = oo;
+                               }
+                               printf("\tCHS current addressable sectors:%11u\n", mm);
+                       }
+               }
+               /* LBA addressing */
+               printf("\tLBA    user addressable sectors:%11u\n", ll);
+               if (((val[CMDS_SUPP_1] & VALID) == VALID_VAL)
+                && (val[CMDS_SUPP_1] & SUPPORT_48_BIT)
+               ) {
+                       bbbig = (uint64_t)val[LBA_64_MSB] << 48 |
+                               (uint64_t)val[LBA_48_MSB] << 32 |
+                               (uint64_t)val[LBA_MID] << 16 |
+                                       val[LBA_LSB];
+                       printf("\tLBA48  user addressable sectors:%11"PRIu64"\n", bbbig);
+               }
+
+               if (!bbbig)
+                       bbbig = (uint64_t)(ll>mm ? ll : mm); /* # 512 byte blocks */
+               printf("\tdevice size with M = 1024*1024: %11"PRIu64" MBytes\n", bbbig>>11);
+               bbbig = (bbbig << 9) / 1000000;
+               printf("\tdevice size with M = 1000*1000: %11"PRIu64" MBytes ", bbbig);
+
+               if (bbbig > 1000)
+                       printf("(%"PRIu64" GB)\n", bbbig/1000);
+               else
+                       bb_putchar('\n');
+       }
+
+       /* hw support of commands (capabilities) */
+       printf("Capabilities:\n\t");
+
+       if (dev == ATAPI_DEV) {
+               if (eqpt != CDROM && (val[CAPAB_0] & CMD_Q_SUP)) printf("Cmd queuing, ");
+               if (val[CAPAB_0] & OVLP_SUP) printf("Cmd overlap, ");
+       }
+       if (val[CAPAB_0] & LBA_SUP) printf("LBA, ");
+
+       if (like_std != 1) {
+               printf("IORDY%s(can%s be disabled)\n",
+                               !(val[CAPAB_0] & IORDY_SUP) ? "(may be)" : "",
+                               (val[CAPAB_0] & IORDY_OFF) ? "" :"not");
+       } else
+               printf("no IORDY\n");
+
+       if ((like_std == 1) && val[BUF_TYPE]) {
+               printf("\tBuffer type: %04x: %s%s\n", val[BUF_TYPE],
+                               (val[BUF_TYPE] < 2) ? "single port, single-sector" : "dual port, multi-sector",
+                               (val[BUF_TYPE] > 2) ? " with read caching ability" : "");
+       }
+
+       if ((min_std == 1) && (val[BUFFER__SIZE] && (val[BUFFER__SIZE] != NOVAL_1))) {
+               printf("\tBuffer size: %.1fkB\n", (float)val[BUFFER__SIZE]/2);
+       }
+       if ((min_std < 4) && (val[RW_LONG])) {
+               printf("\tbytes avail on r/w long: %u\n", val[RW_LONG]);
+       }
+       if ((eqpt != CDROM) && (like_std > 3)) {
+               printf("\tQueue depth: %u\n", (val[QUEUE_DEPTH] & DEPTH_BITS) + 1);
+       }
+
+       if (dev == ATA_DEV) {
+               if (like_std == 1)
+                       printf("\tCan%s perform double-word IO\n", (!val[DWORD_IO]) ? "not" : "");
+               else {
+                       printf("\tStandby timer values: spec'd by %s", (val[CAPAB_0] & STD_STBY) ? "Standard" : "Vendor");
+                       if ((like_std > 3) && ((val[CAPAB_1] & VALID) == VALID_VAL))
+                               printf(", %s device specific minimum\n", (val[CAPAB_1] & MIN_STANDBY_TIMER) ? "with" : "no");
+                       else
+                               bb_putchar('\n');
+               }
+               printf("\tR/W multiple sector transfer: ");
+               if ((like_std < 3) && !(val[SECTOR_XFER_MAX] & SECTOR_XFER))
+                       printf("not supported\n");
+               else {
+                       printf("Max = %u\tCurrent = ", val[SECTOR_XFER_MAX] & SECTOR_XFER);
+                       if (val[SECTOR_XFER_CUR] & MULTIPLE_SETTING_VALID)
+                               printf("%u\n", val[SECTOR_XFER_CUR] & SECTOR_XFER);
+                       else
+                               printf("?\n");
+               }
+               if ((like_std > 3) && (val[CMDS_SUPP_1] & 0x0008)) {
+                       /* We print out elsewhere whether the APM feature is enabled or
+                          not.  If it's not enabled, let's not repeat the info; just print
+                          nothing here. */
+                       printf("\tAdvancedPM level: ");
+                       if ((val[ADV_PWR] & 0xFF00) == 0x4000) {
+                               uint8_t apm_level = val[ADV_PWR] & 0x00FF;
+                               printf("%u (0x%x)\n", apm_level, apm_level);
+                       }
+                       else
+                               printf("unknown setting (0x%04x)\n", val[ADV_PWR]);
+               }
+               if (like_std > 5 && val[ACOUSTIC]) {
+                       printf("\tRecommended acoustic management value: %u, current value: %u\n",
+                                       (val[ACOUSTIC] >> 8) & 0x00ff, val[ACOUSTIC] & 0x00ff);
+               }
+       } else {
+                /* ATAPI */
+               if (eqpt != CDROM && (val[CAPAB_0] & SWRST_REQ))
+                       printf("\tATA sw reset required\n");
+
+               if (val[PKT_REL] || val[SVC_NBSY]) {
+                       printf("\tOverlap support:");
+                       if (val[PKT_REL]) printf(" %uus to release bus.", val[PKT_REL]);
+                       if (val[SVC_NBSY]) printf(" %uus to clear BSY after SERVICE cmd.", val[SVC_NBSY]);
+                       bb_putchar('\n');
+               }
+       }
+
+       /* DMA stuff. Check that only one DMA mode is selected. */
+       printf("\tDMA: ");
+       if (!(val[CAPAB_0] & DMA_SUP))
+               printf("not supported\n");
+       else {
+               if (val[DMA_MODE] && !val[SINGLE_DMA] && !val[MULTI_DMA])
+                       printf(" sdma%u\n", (val[DMA_MODE] & MODE) >> 8);
+               if (val[SINGLE_DMA]) {
+                       jj = val[SINGLE_DMA];
+                       kk = val[SINGLE_DMA] >> 8;
+                       err_dma += mode_loop(jj, kk, 's', &have_mode);
+               }
+               if (val[MULTI_DMA]) {
+                       jj = val[MULTI_DMA];
+                       kk = val[MULTI_DMA] >> 8;
+                       err_dma += mode_loop(jj, kk, 'm', &have_mode);
+               }
+               if ((val[WHATS_VALID] & OK_W88) && val[ULTRA_DMA]) {
+                       jj = val[ULTRA_DMA];
+                       kk = val[ULTRA_DMA] >> 8;
+                       err_dma += mode_loop(jj, kk, 'u', &have_mode);
+               }
+               if (err_dma || !have_mode) printf("(?)");
+               bb_putchar('\n');
+
+               if ((dev == ATAPI_DEV) && (eqpt != CDROM) && (val[CAPAB_0] & DMA_IL_SUP))
+                       printf("\t\tInterleaved DMA support\n");
+
+               if ((val[WHATS_VALID] & OK_W64_70)
+                && (val[DMA_TIME_MIN] || val[DMA_TIME_NORM])
+               ) {
+                       printf("\t\tCycle time:");
+                       if (val[DMA_TIME_MIN]) printf(" min=%uns", val[DMA_TIME_MIN]);
+                       if (val[DMA_TIME_NORM]) printf(" recommended=%uns", val[DMA_TIME_NORM]);
+                       bb_putchar('\n');
+               }
+       }
+
+       /* Programmed IO stuff */
+       printf("\tPIO: ");
+       /* If a drive supports mode n (e.g. 3), it also supports all modes less
+        * than n (e.g. 3, 2, 1 and 0).  Print all the modes. */
+       if ((val[WHATS_VALID] & OK_W64_70) && (val[ADV_PIO_MODES] & PIO_SUP)) {
+               jj = ((val[ADV_PIO_MODES] & PIO_SUP) << 3) | 0x0007;
+               for (ii = 0; ii <= PIO_MODE_MAX; ii++) {
+                       if (jj & 0x0001) printf("pio%d ", ii);
+                       jj >>=1;
+               }
+               bb_putchar('\n');
+       } else if (((min_std < 5) || (eqpt == CDROM)) && (val[PIO_MODE] & MODE)) {
+               for (ii = 0; ii <= val[PIO_MODE]>>8; ii++)
+                       printf("pio%d ", ii);
+               bb_putchar('\n');
+       } else
+               puts("unknown");
+
+       if (val[WHATS_VALID] & OK_W64_70) {
+               if (val[PIO_NO_FLOW] || val[PIO_FLOW]) {
+                       printf("\t\tCycle time:");
+                       if (val[PIO_NO_FLOW]) printf(" no flow control=%uns", val[PIO_NO_FLOW]);
+                       if (val[PIO_FLOW]) printf("  IORDY flow control=%uns", val[PIO_FLOW]);
+                       bb_putchar('\n');
+               }
+       }
+
+       if ((val[CMDS_SUPP_1] & VALID) == VALID_VAL) {
+               printf("Commands/features:\n\tEnabled\tSupported:\n");
+               jj = val[CMDS_SUPP_0];
+               kk = val[CMDS_EN_0];
+               for (ii = 0; ii < NUM_CMD_FEAT_STR; ii++) {
+                       const char *feat_str = nth_string(cmd_feat_str, ii);
+                       if ((jj & 0x8000) && (*feat_str != '\0')) {
+                               printf("\t%s\t%s\n", (kk & 0x8000) ? "   *" : "", feat_str);
+                       }
+                       jj <<= 1;
+                       kk <<= 1;
+                       if (ii % 16 == 15) {
+                               jj = val[CMDS_SUPP_0+1+(ii/16)];
+                               kk = val[CMDS_EN_0+1+(ii/16)];
+                       }
+                       if (ii == 31) {
+                               if ((val[CMDS_SUPP_2] & VALID) != VALID_VAL)
+                                       ii +=16;
+                       }
+               }
+       }
+       /* Removable Media Status Notification feature set */
+       if ((val[RM_STAT] & RM_STAT_BITS) == RM_STAT_SUP)
+               printf("\t%s supported\n", nth_string(cmd_feat_str, 27));
+
+       /* security */
+       if ((eqpt != CDROM) && (like_std > 3)
+        && (val[SECU_STATUS] || val[ERASE_TIME] || val[ENH_ERASE_TIME])
+       ) {
+               printf("Security:\n");
+               if (val[PSWD_CODE] && (val[PSWD_CODE] != NOVAL_1))
+                       printf("\tMaster password revision code = %u\n", val[PSWD_CODE]);
+               jj = val[SECU_STATUS];
+               if (jj) {
+                       for (ii = 0; ii < NUM_SECU_STR; ii++) {
+                               printf("\t%s\t%s\n", (!(jj & 0x0001)) ? "not" : "", nth_string(secu_str, ii));
+                               jj >>=1;
+                       }
+                       if (val[SECU_STATUS] & SECU_ENABLED) {
+                               printf("\tSecurity level %s\n", (val[SECU_STATUS] & SECU_LEVEL) ? "maximum" : "high");
+                       }
+               }
+               jj =  val[ERASE_TIME]     & ERASE_BITS;
+               kk =  val[ENH_ERASE_TIME] & ERASE_BITS;
+               if (jj || kk) {
+                       bb_putchar('\t');
+                       if (jj) printf("%umin for %sSECURITY ERASE UNIT. ", jj==ERASE_BITS ? 508 : jj<<1, "");
+                       if (kk) printf("%umin for %sSECURITY ERASE UNIT. ", kk==ERASE_BITS ? 508 : kk<<1, "ENHANCED ");
+                       bb_putchar('\n');
+               }
+       }
+
+       /* reset result */
+       jj = val[HWRST_RSLT];
+       if ((jj & VALID) == VALID_VAL) {
+               oo = (jj & RST0);
+               if (!oo)
+                       jj >>= 8;
+               if ((jj & DEV_DET) == JUMPER_VAL)
+                       strng = " determined by the jumper";
+               else if ((jj & DEV_DET) == CSEL_VAL)
+                       strng = " determined by CSEL";
+               else
+                       strng = "";
+               printf("HW reset results:\n\tCBLID- %s Vih\n\tDevice num = %i%s\n",
+                               (val[HWRST_RSLT] & CBLID) ? "above" : "below", !(oo), strng);
+       }
+
+       /* more stuff from std 5 */
+       if ((like_std > 4) && (eqpt != CDROM)) {
+               if (val[CFA_PWR_MODE] & VALID_W160) {
+                       printf("CFA power mode 1:\n\t%s%s\n", (val[CFA_PWR_MODE] & PWR_MODE_OFF) ? "disabled" : "enabled",
+                                       (val[CFA_PWR_MODE] & PWR_MODE_REQ) ? " and required by some commands" : "");
+
+                       if (val[CFA_PWR_MODE] & MAX_AMPS)
+                               printf("\tMaximum current = %uma\n", val[CFA_PWR_MODE] & MAX_AMPS);
+               }
+               if ((val[INTEGRITY] & SIG) == SIG_VAL) {
+                       printf("Checksum: %scorrect\n", chksum ? "in" : "");
+               }
+       }
+
+       exit(EXIT_SUCCESS);
+}
+#endif
+
+// Historically, if there was no HDIO_OBSOLETE_IDENTITY, then
+// then the HDIO_GET_IDENTITY only returned 142 bytes.
+// Otherwise, HDIO_OBSOLETE_IDENTITY returns 142 bytes,
+// and HDIO_GET_IDENTITY returns 512 bytes.  But the latest
+// 2.5.xx kernels no longer define HDIO_OBSOLETE_IDENTITY
+// (which they should, but they should just return -EINVAL).
+//
+// So.. we must now assume that HDIO_GET_IDENTITY returns 512 bytes.
+// On a really old system, it will not, and we will be confused.
+// Too bad, really.
+
+#if ENABLE_FEATURE_HDPARM_GET_IDENTITY
+static const char cfg_str[] ALIGN1 =
+       """\0"            "HardSect""\0"   "SoftSect""\0"  "NotMFM""\0"
+       "HdSw>15uSec""\0" "SpinMotCtl""\0" "Fixed""\0"     "Removeable""\0"
+       "DTR<=5Mbs""\0"   "DTR>5Mbs""\0"   "DTR>10Mbs""\0" "RotSpdTol>.5%""\0"
+       "dStbOff""\0"     "TrkOff""\0"     "FmtGapReq""\0" "nonMagnetic"
+;
+
+static const char BuffType[] ALIGN1 =
+       "unknown""\0"     "1Sect""\0"      "DualPort""\0"  "DualPortCache"
+;
+
+static void dump_identity(const struct hd_driveid *id)
+{
+       int i;
+       const unsigned short *id_regs = (const void*) id;
+
+       printf("\n Model=%.40s, FwRev=%.8s, SerialNo=%.20s\n Config={",
+                               id->model, id->fw_rev, id->serial_no);
+       for (i = 0; i <= 15; i++) {
+               if (id->config & (1<<i))
+                       printf(" %s", nth_string(cfg_str, i));
+       }
+       printf(" }\n RawCHS=%u/%u/%u, TrkSize=%u, SectSize=%u, ECCbytes=%u\n"
+                       " BuffType=(%u) %s, BuffSize=%ukB, MaxMultSect=%u",
+                               id->cyls, id->heads, id->sectors, id->track_bytes,
+                               id->sector_bytes, id->ecc_bytes,
+                               id->buf_type, nth_string(BuffType, (id->buf_type > 3) ? 0 : id->buf_type),
+                               id->buf_size/2, id->max_multsect);
+       if (id->max_multsect) {
+               printf(", MultSect=");
+               if (!(id->multsect_valid & 1))
+                       printf("?%u?", id->multsect);
+               else if (id->multsect)
+                       printf("%u", id->multsect);
+               else
+                       printf("off");
+       }
+       bb_putchar('\n');
+
+       if (!(id->field_valid & 1))
+               printf(" (maybe):");
+
+       printf(" CurCHS=%u/%u/%u, CurSects=%lu, LBA=%s", id->cur_cyls, id->cur_heads,
+               id->cur_sectors,
+               (BB_BIG_ENDIAN) ?
+                       (unsigned long)(id->cur_capacity0 << 16) | id->cur_capacity1 :
+                       (unsigned long)(id->cur_capacity1 << 16) | id->cur_capacity0,
+                       ((id->capability&2) == 0) ? "no" : "yes");
+
+       if (id->capability & 2)
+               printf(", LBAsects=%u", id->lba_capacity);
+
+       printf("\n IORDY=%s", (id->capability & 8) ? (id->capability & 4) ?  "on/off" : "yes" : "no");
+
+       if (((id->capability & 8) || (id->field_valid & 2)) && (id->field_valid & 2))
+               printf(", tPIO={min:%u,w/IORDY:%u}", id->eide_pio, id->eide_pio_iordy);
+
+       if ((id->capability & 1) && (id->field_valid & 2))
+               printf(", tDMA={min:%u,rec:%u}", id->eide_dma_min, id->eide_dma_time);
+
+       printf("\n PIO modes:  ");
+       if (id->tPIO <= 5) {
+               printf("pio0 ");
+               if (id->tPIO >= 1) printf("pio1 ");
+               if (id->tPIO >= 2) printf("pio2 ");
+       }
+       if (id->field_valid & 2) {
+               static const masks_labels_t pio_modes = {
+                       .masks = { 1, 2, ~3 },
+                       .labels = "pio3 \0""pio4 \0""pio? \0",
+               };
+               print_flags(&pio_modes, id->eide_pio_modes);
+       }
+       if (id->capability & 1) {
+               if (id->dma_1word | id->dma_mword) {
+                       static const int dma_wmode_masks[] = { 0x100, 1, 0x200, 2, 0x400, 4, 0xf800, 0xf8 };
+                       printf("\n DMA modes:  ");
+                       print_flags_separated(dma_wmode_masks,
+                               "*\0""sdma0 \0""*\0""sdma1 \0""*\0""sdma2 \0""*\0""sdma? \0",
+                               id->dma_1word, NULL);
+                       print_flags_separated(dma_wmode_masks,
+                               "*\0""mdma0\0""*\0""mdma1\0""*\0""mdma2\0""*\0""mdma?\0",
+                               id->dma_mword, NULL);
+               }
+       }
+       if (((id->capability & 8) || (id->field_valid & 2)) && id->field_valid & 4) {
+               static const masks_labels_t ultra_modes1 = {
+                       .masks = { 0x100, 0x001, 0x200, 0x002, 0x400, 0x004 },
+                       .labels = "*\0""udma0 \0""*\0""udma1 \0""*\0""udma2 \0",
+               };
+
+               printf("\n UDMA modes: ");
+               print_flags(&ultra_modes1, id->dma_ultra);
+#ifdef __NEW_HD_DRIVE_ID
+               if (id->hw_config & 0x2000) {
+#else /* !__NEW_HD_DRIVE_ID */
+               if (id->word93 & 0x2000) {
+#endif /* __NEW_HD_DRIVE_ID */
+                       static const masks_labels_t ultra_modes2 = {
+                               .masks = { 0x0800, 0x0008, 0x1000, 0x0010,
+                                       0x2000, 0x0020, 0x4000, 0x0040,
+                                       0x8000, 0x0080 },
+                               .labels = "*\0""udma3 \0""*\0""udma4 \0"
+                                       "*\0""udma5 \0""*\0""udma6 \0"
+                                       "*\0""udma7 \0"
+                       };
+                       print_flags(&ultra_modes2, id->dma_ultra);
+               }
+       }
+       printf("\n AdvancedPM=%s", (!(id_regs[83] & 8)) ? "no" : "yes");
+       if (id_regs[83] & 8) {
+               if (!(id_regs[86] & 8))
+                       printf(": disabled (255)");
+               else if ((id_regs[91] & 0xFF00) != 0x4000)
+                       printf(": unknown setting");
+               else
+                       printf(": mode=0x%02X (%u)", id_regs[91] & 0xFF, id_regs[91] & 0xFF);
+       }
+       if (id_regs[82] & 0x20)
+               printf(" WriteCache=%s", (id_regs[85] & 0x20) ? "enabled" : "disabled");
+#ifdef __NEW_HD_DRIVE_ID
+       if ((id->minor_rev_num && id->minor_rev_num <= 31)
+        || (id->major_rev_num && id->minor_rev_num <= 31)
+       ) {
+               printf("\n Drive conforms to: %s: ", (id->minor_rev_num <= 31) ? nth_string(minor_str, id->minor_rev_num) : "unknown");
+               if (id->major_rev_num != 0x0000 &&  /* NOVAL_0 */
+                   id->major_rev_num != 0xFFFF) {  /* NOVAL_1 */
+                       for (i = 0; i <= 15; i++) {
+                               if (id->major_rev_num & (1<<i))
+                                               printf(" ATA/ATAPI-%u", i);
+                       }
+               }
+       }
+#endif /* __NEW_HD_DRIVE_ID */
+       printf("\n\n * current active mode\n\n");
+}
+#endif
+
+static void flush_buffer_cache(/*int fd*/ void)
+{
+       fsync(fd);                              /* flush buffers */
+       ioctl_or_warn(fd, BLKFLSBUF, NULL); /* do it again, big time */
+#ifdef HDIO_DRIVE_CMD
+       sleep(1);
+       if (ioctl(fd, HDIO_DRIVE_CMD, NULL) && errno != EINVAL) {       /* await completion */
+               if (ENABLE_IOCTL_HEX2STR_ERROR) /* To be coherent with ioctl_or_warn */
+                       bb_perror_msg("HDIO_DRIVE_CMD");
+               else
+                       bb_perror_msg("ioctl %#x failed", HDIO_DRIVE_CMD);
+       }
+#endif
+}
+
+static void seek_to_zero(/*int fd*/ void)
+{
+       xlseek(fd, (off_t) 0, SEEK_SET);
+}
+
+static void read_big_block(/*int fd,*/ char *buf)
+{
+       int i;
+
+       xread(fd, buf, TIMING_BUF_BYTES);
+       /* access all sectors of buf to ensure the read fully completed */
+       for (i = 0; i < TIMING_BUF_BYTES; i += 512)
+               buf[i] &= 1;
+}
+
+static unsigned dev_size_mb(/*int fd*/ void)
+{
+       union {
+               unsigned long long blksize64;
+               unsigned blksize32;
+       } u;
+
+       if (0 == ioctl(fd, BLKGETSIZE64, &u.blksize64)) { // bytes
+               u.blksize64 /= (1024 * 1024);
+       } else {
+               xioctl(fd, BLKGETSIZE, &u.blksize32); // sectors
+               u.blksize64 = u.blksize32 / (2 * 1024);
+       }
+       if (u.blksize64 > UINT_MAX)
+               return UINT_MAX;
+       return u.blksize64;
+}
+
+static void print_timing(unsigned m, unsigned elapsed_us)
+{
+       unsigned sec = elapsed_us / 1000000;
+       unsigned hs = (elapsed_us % 1000000) / 10000;
+
+       printf("%5u MB in %u.%02u seconds = %u kB/s\n",
+               m, sec, hs,
+               /* "| 1" prevents div-by-0 */
+               (unsigned) ((unsigned long long)m * (1024 * 1000000) / (elapsed_us | 1))
+               // ~= (m * 1024) / (elapsed_us / 1000000)
+               // = kb / elapsed_sec
+       );
+}
+
+static void do_time(int cache /*,int fd*/)
+/* cache=1: time cache: repeatedly read N MB at offset 0
+ * cache=0: time device: linear read, starting at offset 0
+ */
+{
+       unsigned max_iterations, iterations;
+       unsigned start; /* doesn't need to be long long */
+       unsigned elapsed, elapsed2;
+       unsigned total_MB;
+       char *buf = xmalloc(TIMING_BUF_BYTES);
+
+       if (mlock(buf, TIMING_BUF_BYTES))
+               bb_perror_msg_and_die("mlock");
+
+       /* Clear out the device request queues & give them time to complete.
+        * NB: *small* delay. User is expected to have a clue and to not run
+        * heavy io in parallel with measurements. */
+       sync();
+       sleep(1);
+       if (cache) { /* Time cache */
+               seek_to_zero();
+               read_big_block(buf);
+               printf("Timing buffer-cache reads: ");
+       } else { /* Time device */
+               printf("Timing buffered disk reads:");
+       }
+       fflush(stdout);
+
+       /* Now do the timing */
+       iterations = 0;
+       /* Max time to run (small for cache, avoids getting
+        * huge total_MB which can overlow unsigned type) */
+       elapsed2 = 510000; /* cache */
+       max_iterations = UINT_MAX;
+       if (!cache) {
+               elapsed2 = 3000000; /* not cache */
+               /* Don't want to read past the end! */
+               max_iterations = dev_size_mb() / TIMING_BUF_MB;
+       }
+       start = monotonic_us();
+       do {
+               if (cache)
+                       seek_to_zero();
+               read_big_block(buf);
+               elapsed = (unsigned)monotonic_us() - start;
+               ++iterations;
+       } while (elapsed < elapsed2 && iterations < max_iterations);
+       total_MB = iterations * TIMING_BUF_MB;
+       //printf(" elapsed:%u iterations:%u ", elapsed, iterations);
+       if (cache) {
+               /* Cache: remove lseek() and monotonic_us() overheads
+                * from elapsed */
+               start = monotonic_us();
+               do {
+                       seek_to_zero();
+                       elapsed2 = (unsigned)monotonic_us() - start;
+               } while (--iterations);
+               //printf(" elapsed2:%u ", elapsed2);
+               elapsed -= elapsed2;
+               total_MB *= 2; // BUFCACHE_FACTOR (why?)
+               flush_buffer_cache();
+       }
+       print_timing(total_MB, elapsed);
+       munlock(buf, TIMING_BUF_BYTES);
+       free(buf);
+}
+
+#if ENABLE_FEATURE_HDPARM_HDIO_TRISTATE_HWIF
+static void bus_state_value(unsigned value)
+{
+       if (value == BUSSTATE_ON)
+               on_off(1);
+       else if (value == BUSSTATE_OFF)
+               on_off(0);
+       else if (value == BUSSTATE_TRISTATE)
+               printf(" (tristate)\n");
+       else
+               printf(" (unknown: %d)\n", value);
+}
+#endif
+
+#ifdef HDIO_DRIVE_CMD
+static void interpret_standby(uint8_t standby)
+{
+       printf(" (");
+       if (standby == 0) {
+               printf("off");
+       } else if (standby <= 240 || standby == 252 || standby == 255) {
+               /* standby is in 5 sec units */
+               printf("%u minutes %u seconds", standby / 12, (standby*5) % 60);
+       } else if (standby <= 251) {
+               unsigned t = (standby - 240); /* t is in 30 min units */;
+               printf("%u.%c hours", t / 2, (t & 1) ? '0' : '5');
+       }
+       if (standby == 253)
+               printf("vendor-specific");
+       if (standby == 254)
+               printf("reserved");
+       printf(")\n");
+}
+
+static const uint8_t xfermode_val[] ALIGN1 = {
+        8,      9,     10,     11,     12,     13,     14,     15,
+       16,     17,     18,     19,     20,     21,     22,     23,
+       32,     33,     34,     35,     36,     37,     38,     39,
+       64,     65,     66,     67,     68,     69,     70,     71
+};
+/* NB: we save size by _not_ storing terninating NUL! */
+static const char xfermode_name[][5] ALIGN1 = {
+       "pio0", "pio1", "pio2", "pio3", "pio4", "pio5", "pio6", "pio7",
+       "sdma0","sdma1","sdma2","sdma3","sdma4","sdma5","sdma6","sdma7",
+       "mdma0","mdma1","mdma2","mdma3","mdma4","mdma5","mdma6","mdma7",
+       "udma0","udma1","udma2","udma3","udma4","udma5","udma6","udma7"
+};
+
+static int translate_xfermode(const char *name)
+{
+       int val;
+       unsigned i;
+
+       for (i = 0; i < ARRAY_SIZE(xfermode_val); i++) {
+               if (!strncmp(name, xfermode_name[i], 5))
+                       if (strlen(name) <= 5)
+                               return xfermode_val[i];
+       }
+       /* Negative numbers are invalid and are caught later */
+       val = bb_strtoi(name, NULL, 10);
+       if (!errno)
+               return val;
+       return -1;
+}
+
+static void interpret_xfermode(unsigned xfermode)
+{
+       printf(" (");
+       if (xfermode == 0)
+               printf("default PIO mode");
+       else if (xfermode == 1)
+               printf("default PIO mode, disable IORDY");
+       else if (xfermode >= 8 && xfermode <= 15)
+               printf("PIO flow control mode%u", xfermode - 8);
+       else if (xfermode >= 16 && xfermode <= 23)
+               printf("singleword DMA mode%u", xfermode - 16);
+       else if (xfermode >= 32 && xfermode <= 39)
+               printf("multiword DMA mode%u", xfermode - 32);
+       else if (xfermode >= 64 && xfermode <= 71)
+               printf("UltraDMA mode%u", xfermode - 64);
+       else
+               printf("unknown");
+       printf(")\n");
+}
+#endif /* HDIO_DRIVE_CMD */
+
+static void print_flag(int flag, const char *s, unsigned long value)
+{
+       if (flag)
+               printf(" setting %s to %ld\n", s, value);
+}
+
+static void process_dev(char *devname)
+{
+       /*int fd;*/
+       long parm, multcount;
+#ifndef HDIO_DRIVE_CMD
+       int force_operation = 0;
+#endif
+       /* Please restore args[n] to these values after each ioctl
+          except for args[2] */
+       unsigned char args[4] = { WIN_SETFEATURES, 0, 0, 0 };
+       const char *fmt = " %s\t= %2ld";
+
+       /*fd = xopen(devname, O_RDONLY | O_NONBLOCK);*/
+       xmove_fd(xopen(devname, O_RDONLY | O_NONBLOCK), fd);
+       printf("\n%s:\n", devname);
+
+       if (set_readahead) {
+               print_flag(get_readahead, "fs readahead", Xreadahead);
+               ioctl_or_warn(fd, BLKRASET, (int *)Xreadahead);
+       }
+#if ENABLE_FEATURE_HDPARM_HDIO_UNREGISTER_HWIF
+       if (unregister_hwif) {
+               printf(" attempting to unregister hwif#%lu\n", hwif);
+               ioctl_or_warn(fd, HDIO_UNREGISTER_HWIF, (int *)(unsigned long)hwif);
+       }
+#endif
+#if ENABLE_FEATURE_HDPARM_HDIO_SCAN_HWIF
+       if (scan_hwif) {
+               printf(" attempting to scan hwif (0x%lx, 0x%lx, %lu)\n", hwif_data, hwif_ctrl, hwif_irq);
+               args[0] = hwif_data;
+               args[1] = hwif_ctrl;
+               args[2] = hwif_irq;
+               ioctl_or_warn(fd, HDIO_SCAN_HWIF, args);
+               args[0] = WIN_SETFEATURES;
+               args[1] = 0;
+       }
+#endif
+       if (set_piomode) {
+               if (noisy_piomode) {
+                       printf(" attempting to ");
+                       if (piomode == 255)
+                               printf("auto-tune PIO mode\n");
+                       else if (piomode < 100)
+                               printf("set PIO mode to %d\n", piomode);
+                       else if (piomode < 200)
+                               printf("set MDMA mode to %d\n", (piomode-100));
+                       else
+                               printf("set UDMA mode to %d\n", (piomode-200));
+               }
+               ioctl_or_warn(fd, HDIO_SET_PIO_MODE, (int *)(unsigned long)piomode);
+       }
+       if (set_io32bit) {
+               print_flag(get_io32bit, "32-bit IO_support flag", io32bit);
+               ioctl_or_warn(fd, HDIO_SET_32BIT, (int *)io32bit);
+       }
+       if (set_mult) {
+               print_flag(get_mult, "multcount", mult);
+#ifdef HDIO_DRIVE_CMD
+               ioctl_or_warn(fd, HDIO_SET_MULTCOUNT, (void *)mult);
+#else
+               force_operation |= (!ioctl_or_warn(fd, HDIO_SET_MULTCOUNT, (void *)mult));
+#endif
+       }
+       if (set_readonly) {
+               print_flag_on_off(get_readonly, "readonly", readonly);
+               ioctl_or_warn(fd, BLKROSET, &readonly);
+       }
+       if (set_unmask) {
+               print_flag_on_off(get_unmask, "unmaskirq", unmask);
+               ioctl_or_warn(fd, HDIO_SET_UNMASKINTR, (int *)unmask);
+       }
+#if ENABLE_FEATURE_HDPARM_HDIO_GETSET_DMA
+       if (set_dma) {
+               print_flag_on_off(get_dma, "using_dma", dma);
+               ioctl_or_warn(fd, HDIO_SET_DMA, (int *)dma);
+       }
+#endif /* FEATURE_HDPARM_HDIO_GETSET_DMA */
+#ifdef HDIO_SET_QDMA
+       if (set_dma_q) {
+               print_flag_on_off(get_dma_q, "DMA queue_depth", dma_q);
+               ioctl_or_warn(fd, HDIO_SET_QDMA, (int *)dma_q);
+       }
+#endif
+       if (set_nowerr) {
+               print_flag_on_off(get_nowerr, "nowerr", nowerr);
+               ioctl_or_warn(fd, HDIO_SET_NOWERR, (int *)nowerr);
+       }
+       if (set_keep) {
+               print_flag_on_off(get_keep, "keep_settings", keep);
+               ioctl_or_warn(fd, HDIO_SET_KEEPSETTINGS, (int *)keep);
+       }
+#ifdef HDIO_DRIVE_CMD
+       if (set_doorlock) {
+               args[0] = doorlock ? WIN_DOORLOCK : WIN_DOORUNLOCK;
+               args[2] = 0;
+               print_flag_on_off(get_doorlock, "drive doorlock", doorlock);
+               ioctl_or_warn(fd, HDIO_DRIVE_CMD, &args);
+               args[0] = WIN_SETFEATURES;
+       }
+       if (set_dkeep) {
+               /* lock/unlock the drive's "feature" settings */
+               print_flag_on_off(get_dkeep, "drive keep features", dkeep);
+               args[2] = dkeep ? 0x66 : 0xcc;
+               ioctl_or_warn(fd, HDIO_DRIVE_CMD, &args);
+       }
+       if (set_defects) {
+               args[2] = defects ? 0x04 : 0x84;
+               print_flag(get_defects, "drive defect-mgmt", defects);
+               ioctl_or_warn(fd, HDIO_DRIVE_CMD, &args);
+       }
+       if (set_prefetch) {
+               args[1] = prefetch;
+               args[2] = 0xab;
+               print_flag(get_prefetch, "drive prefetch", prefetch);
+               ioctl_or_warn(fd, HDIO_DRIVE_CMD, &args);
+               args[1] = 0;
+       }
+       if (set_xfermode) {
+               args[1] = xfermode_requested;
+               args[2] = 3;
+               if (get_xfermode) {
+                       print_flag(1, "xfermode", xfermode_requested);
+                       interpret_xfermode(xfermode_requested);
+               }
+               ioctl_or_warn(fd, HDIO_DRIVE_CMD, &args);
+               args[1] = 0;
+       }
+       if (set_lookahead) {
+               args[2] = lookahead ? 0xaa : 0x55;
+               print_flag_on_off(get_lookahead, "drive read-lookahead", lookahead);
+               ioctl_or_warn(fd, HDIO_DRIVE_CMD, &args);
+       }
+       if (set_apmmode) {
+               args[2] = (apmmode == 255) ? 0x85 /* disable */ : 0x05 /* set */; /* feature register */
+               args[1] = apmmode; /* sector count register 1-255 */
+               if (get_apmmode)
+                       printf(" setting APM level to %s 0x%02lX (%ld)\n", (apmmode == 255) ? "disabled" : "", apmmode, apmmode);
+               ioctl_or_warn(fd, HDIO_DRIVE_CMD, &args);
+               args[1] = 0;
+       }
+       if (set_wcache) {
+#ifdef DO_FLUSHCACHE
+#ifndef WIN_FLUSHCACHE
+#define WIN_FLUSHCACHE 0xe7
+#endif
+#endif /* DO_FLUSHCACHE */
+               args[2] = wcache ? 0x02 : 0x82;
+               print_flag_on_off(get_wcache, "drive write-caching", wcache);
+#ifdef DO_FLUSHCACHE
+               if (!wcache)
+                       ioctl_or_warn(fd, HDIO_DRIVE_CMD, &flushcache);
+#endif /* DO_FLUSHCACHE */
+               ioctl_or_warn(fd, HDIO_DRIVE_CMD, &args);
+#ifdef DO_FLUSHCACHE
+               if (!wcache)
+                       ioctl_or_warn(fd, HDIO_DRIVE_CMD, &flushcache);
+#endif /* DO_FLUSHCACHE */
+       }
+
+       /* In code below, we do not preserve args[0], but the rest
+          is preserved, including args[2] */
+       args[2] = 0;
+
+       if (set_standbynow) {
+#ifndef WIN_STANDBYNOW1
+#define WIN_STANDBYNOW1 0xE0
+#endif
+#ifndef WIN_STANDBYNOW2
+#define WIN_STANDBYNOW2 0x94
+#endif
+               if (get_standbynow) printf(" issuing standby command\n");
+               args[0] = WIN_STANDBYNOW1;
+               ioctl_alt_or_warn(HDIO_DRIVE_CMD, args, WIN_STANDBYNOW2);
+       }
+       if (set_sleepnow) {
+#ifndef WIN_SLEEPNOW1
+#define WIN_SLEEPNOW1 0xE6
+#endif
+#ifndef WIN_SLEEPNOW2
+#define WIN_SLEEPNOW2 0x99
+#endif
+               if (get_sleepnow) printf(" issuing sleep command\n");
+               args[0] = WIN_SLEEPNOW1;
+               ioctl_alt_or_warn(HDIO_DRIVE_CMD, args, WIN_SLEEPNOW2);
+       }
+       if (set_seagate) {
+               args[0] = 0xfb;
+               if (get_seagate) printf(" disabling Seagate auto powersaving mode\n");
+               ioctl_or_warn(fd, HDIO_DRIVE_CMD, &args);
+       }
+       if (set_standby) {
+               args[0] = WIN_SETIDLE1;
+               args[1] = standby_requested;
+               if (get_standby) {
+                       print_flag(1, "standby", standby_requested);
+                       interpret_standby(standby_requested);
+               }
+               ioctl_or_warn(fd, HDIO_DRIVE_CMD, &args);
+               args[1] = 0;
+       }
+#else  /* HDIO_DRIVE_CMD */
+       if (force_operation) {
+               char buf[512];
+               flush_buffer_cache();
+               if (-1 == read(fd, buf, sizeof(buf)))
+                       bb_perror_msg("read(%d bytes) failed (rc=-1)", sizeof(buf));
+       }
+#endif /* HDIO_DRIVE_CMD */
+
+       if (get_mult || get_identity) {
+               multcount = -1;
+               if (ioctl(fd, HDIO_GET_MULTCOUNT, &multcount)) {
+                       if (get_mult && ENABLE_IOCTL_HEX2STR_ERROR) /* To be coherent with ioctl_or_warn. */
+                               bb_perror_msg("HDIO_GET_MULTCOUNT");
+                       else
+                               bb_perror_msg("ioctl %#x failed", HDIO_GET_MULTCOUNT);
+               } else if (get_mult) {
+                       printf(fmt, "multcount", multcount);
+                       on_off(multcount != 0);
+               }
+       }
+       if (get_io32bit) {
+               if (!ioctl_or_warn(fd, HDIO_GET_32BIT, &parm)) {
+                       printf(" IO_support\t=%3ld (", parm);
+                       if (parm == 0)
+                               printf("default 16-bit)\n");
+                       else if (parm == 2)
+                               printf("16-bit)\n");
+                       else if (parm == 1)
+                               printf("32-bit)\n");
+                       else if (parm == 3)
+                               printf("32-bit w/sync)\n");
+                       else if (parm == 8)
+                               printf("Request-Queue-Bypass)\n");
+                       else
+                               printf("\?\?\?)\n");
+               }
+       }
+       if (get_unmask) {
+               if (!ioctl_or_warn(fd, HDIO_GET_UNMASKINTR, &parm))
+                       print_value_on_off("unmaskirq", parm);
+       }
+
+
+#if ENABLE_FEATURE_HDPARM_HDIO_GETSET_DMA
+       if (get_dma) {
+               if (!ioctl_or_warn(fd, HDIO_GET_DMA, &parm)) {
+                       printf(fmt, "using_dma", parm);
+                       if (parm == 8)
+                               printf(" (DMA-Assisted-PIO)\n");
+                       else
+                               on_off(parm != 0);
+               }
+       }
+#endif
+#ifdef HDIO_GET_QDMA
+       if (get_dma_q) {
+               if (!ioctl_or_warn(fd, HDIO_GET_QDMA, &parm))
+                       print_value_on_off("queue_depth", parm);
+       }
+#endif
+       if (get_keep) {
+               if (!ioctl_or_warn(fd, HDIO_GET_KEEPSETTINGS, &parm))
+                       print_value_on_off("keepsettings", parm);
+       }
+
+       if (get_nowerr) {
+               if (!ioctl_or_warn(fd, HDIO_GET_NOWERR, &parm))
+                       print_value_on_off("nowerr", parm);
+       }
+       if (get_readonly) {
+               if (!ioctl_or_warn(fd, BLKROGET, &parm))
+                       print_value_on_off("readonly", parm);
+       }
+       if (get_readahead) {
+               if (!ioctl_or_warn(fd, BLKRAGET, &parm))
+                       print_value_on_off("readahead", parm);
+       }
+       if (get_geom) {
+               if (!ioctl_or_warn(fd, BLKGETSIZE, &parm)) {
+                       struct hd_geometry g;
+
+                       if (!ioctl_or_warn(fd, HDIO_GETGEO, &g))
+                               printf(" geometry\t= %u/%u/%u, sectors = %ld, start = %ld\n",
+                                               g.cylinders, g.heads, g.sectors, parm, g.start);
+               }
+       }
+#ifdef HDIO_DRIVE_CMD
+       if (get_powermode) {
+#ifndef WIN_CHECKPOWERMODE1
+#define WIN_CHECKPOWERMODE1 0xE5
+#endif
+#ifndef WIN_CHECKPOWERMODE2
+#define WIN_CHECKPOWERMODE2 0x98
+#endif
+               const char *state;
+
+               args[0] = WIN_CHECKPOWERMODE1;
+               if (ioctl_alt_or_warn(HDIO_DRIVE_CMD, args, WIN_CHECKPOWERMODE2)) {
+                       if (errno != EIO || args[0] != 0 || args[1] != 0)
+                               state = "unknown";
+                       else
+                               state = "sleeping";
+               } else
+                       state = (args[2] == 255) ? "active/idle" : "standby";
+               args[1] = args[2] = 0;
+
+               printf(" drive state is:  %s\n", state);
+       }
+#endif
+#if ENABLE_FEATURE_HDPARM_HDIO_DRIVE_RESET
+       if (perform_reset) {
+               ioctl_or_warn(fd, HDIO_DRIVE_RESET, NULL);
+       }
+#endif /* FEATURE_HDPARM_HDIO_DRIVE_RESET */
+#if ENABLE_FEATURE_HDPARM_HDIO_TRISTATE_HWIF
+       if (perform_tristate) {
+               args[0] = 0;
+               args[1] = tristate;
+               ioctl_or_warn(fd, HDIO_TRISTATE_HWIF, &args);
+       }
+#endif /* FEATURE_HDPARM_HDIO_TRISTATE_HWIF */
+#if ENABLE_FEATURE_HDPARM_GET_IDENTITY
+       if (get_identity) {
+               struct hd_driveid id;
+
+               if (!ioctl(fd, HDIO_GET_IDENTITY, &id)) {
+                       if (multcount != -1) {
+                               id.multsect = multcount;
+                               id.multsect_valid |= 1;
+                       } else
+                               id.multsect_valid &= ~1;
+                       dump_identity(&id);
+               } else if (errno == -ENOMSG)
+                       printf(" no identification info available\n");
+               else if (ENABLE_IOCTL_HEX2STR_ERROR)  /* To be coherent with ioctl_or_warn */
+                       bb_perror_msg("HDIO_GET_IDENTITY");
+               else
+                       bb_perror_msg("ioctl %#x failed", HDIO_GET_IDENTITY);
+       }
+
+       if (get_IDentity) {
+               unsigned char args1[4+512]; /* = { ... } will eat 0.5k of rodata! */
+
+               memset(args1, 0, sizeof(args1));
+               args1[0] = WIN_IDENTIFY;
+               args1[3] = 1;
+               if (!ioctl_alt_or_warn(HDIO_DRIVE_CMD, args1, WIN_PIDENTIFY))
+                       identify((void *)(args1 + 4));
+       }
+#endif
+#if ENABLE_FEATURE_HDPARM_HDIO_TRISTATE_HWIF
+       if (set_busstate) {
+               if (get_busstate) {
+                       print_flag(1, "bus state", busstate);
+                       bus_state_value(busstate);
+               }
+               ioctl_or_warn(fd, HDIO_SET_BUSSTATE, (int *)(unsigned long)busstate);
+       }
+       if (get_busstate) {
+               if (!ioctl_or_warn(fd, HDIO_GET_BUSSTATE, &parm)) {
+                       printf(fmt, "bus state", parm);
+                       bus_state_value(parm);
+               }
+       }
+#endif
+       if (reread_partn)
+               ioctl_or_warn(fd, BLKRRPART, NULL);
+
+       if (do_ctimings)
+               do_time(1 /*,fd*/); /* time cache */
+       if (do_timings)
+               do_time(0 /*,fd*/); /* time device */
+       if (do_flush)
+               flush_buffer_cache();
+       close(fd);
+}
+
+#if ENABLE_FEATURE_HDPARM_GET_IDENTITY
+static int fromhex(unsigned char c)
+{
+       if (isdigit(c))
+               return (c - '0');
+       if (c >= 'a' && c <= 'f')
+               return (c - ('a' - 10));
+       bb_error_msg_and_die("bad char: '%c' 0x%02x", c, c);
+}
+
+static void identify_from_stdin(void) NORETURN;
+static void identify_from_stdin(void)
+{
+       uint16_t sbuf[256];
+       unsigned char buf[1280];
+       unsigned char *b = (unsigned char *)buf;
+       int i;
+
+       xread(STDIN_FILENO, buf, 1280);
+
+       // Convert the newline-separated hex data into an identify block.
+
+       for (i = 0; i < 256; i++) {
+               int j;
+               for (j = 0; j < 4; j++)
+                       sbuf[i] = (sbuf[i] << 4) + fromhex(*(b++));
+       }
+
+       // Parse the data.
+
+       identify(sbuf);
+}
+#else
+void identify_from_stdin(void);
+#endif
+
+/* busybox specific stuff */
+static void parse_opts(smallint *get, smallint *set, unsigned long *value, int min, int max)
+{
+       if (get) {
+               *get = 1;
+       }
+       if (optarg) {
+               *set = 1;
+               *value = xatol_range(optarg, min, max);
+       }
+}
+
+static void parse_xfermode(int flag, smallint *get, smallint *set, int *value)
+{
+       if (flag) {
+               *get = 1;
+               if (optarg) {
+                       *value = translate_xfermode(optarg);
+                       *set = (*value > -1);
+               }
+       }
+}
+
+/*------- getopt short options --------*/
+static const char hdparm_options[] ALIGN1 =
+       "gfu::n::p:r::m::c::k::a::B:tT"
+       USE_FEATURE_HDPARM_GET_IDENTITY("iI")
+       USE_FEATURE_HDPARM_HDIO_GETSET_DMA("d::")
+#ifdef HDIO_DRIVE_CMD
+       "S:D:P:X:K:A:L:W:CyYzZ"
+#endif
+       USE_FEATURE_HDPARM_HDIO_UNREGISTER_HWIF("U:")
+#ifdef HDIO_GET_QDMA
+#ifdef HDIO_SET_QDMA
+       "Q:"
+#else
+       "Q"
+#endif
+#endif
+       USE_FEATURE_HDPARM_HDIO_DRIVE_RESET("w")
+       USE_FEATURE_HDPARM_HDIO_TRISTATE_HWIF("x::b:")
+       USE_FEATURE_HDPARM_HDIO_SCAN_HWIF("R:");
+/*-------------------------------------*/
+
+/* our main() routine: */
+int hdparm_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int hdparm_main(int argc, char **argv)
+{
+       int c;
+       int flagcount = 0;
+
+       while ((c = getopt(argc, argv, hdparm_options)) >= 0) {
+               flagcount++;
+               USE_FEATURE_HDPARM_GET_IDENTITY(get_IDentity |= (c == 'I'));
+               USE_FEATURE_HDPARM_GET_IDENTITY(get_identity |= (c == 'i'));
+               get_geom |= (c == 'g');
+               do_flush |= (c == 'f');
+               if (c == 'u') parse_opts(&get_unmask, &set_unmask, &unmask, 0, 1);
+               USE_FEATURE_HDPARM_HDIO_GETSET_DMA(if (c == 'd') parse_opts(&get_dma, &set_dma, &dma, 0, 9));
+               if (c == 'n') parse_opts(&get_nowerr, &set_nowerr, &nowerr, 0, 1);
+               parse_xfermode((c == 'p'), &noisy_piomode, &set_piomode, &piomode);
+               if (c == 'r') parse_opts(&get_readonly, &set_readonly, &readonly, 0, 1);
+               if (c == 'm') parse_opts(&get_mult, &set_mult, &mult, 0, INT_MAX /*32*/);
+               if (c == 'c') parse_opts(&get_io32bit, &set_io32bit, &io32bit, 0, INT_MAX /*8*/);
+               if (c == 'k') parse_opts(&get_keep, &set_keep, &keep, 0, 1);
+               if (c == 'a') parse_opts(&get_readahead, &set_readahead, &Xreadahead, 0, INT_MAX);
+               if (c == 'B') parse_opts(&get_apmmode, &set_apmmode, &apmmode, 1, 255);
+               do_flush |= do_timings |= (c == 't');
+               do_flush |= do_ctimings |= (c == 'T');
+#ifdef HDIO_DRIVE_CMD
+               if (c == 'S') parse_opts(&get_standby, &set_standby, &standby_requested, 0, 255);
+               if (c == 'D') parse_opts(&get_defects, &set_defects, &defects, 0, INT_MAX);
+               if (c == 'P') parse_opts(&get_prefetch, &set_prefetch, &prefetch, 0, INT_MAX);
+               parse_xfermode((c == 'X'), &get_xfermode, &set_xfermode, &xfermode_requested);
+               if (c == 'K') parse_opts(&get_dkeep, &set_dkeep, &prefetch, 0, 1);
+               if (c == 'A') parse_opts(&get_lookahead, &set_lookahead, &lookahead, 0, 1);
+               if (c == 'L') parse_opts(&get_doorlock, &set_doorlock, &doorlock, 0, 1);
+               if (c == 'W') parse_opts(&get_wcache, &set_wcache, &wcache, 0, 1);
+               get_powermode |= (c == 'C');
+               get_standbynow = set_standbynow |= (c == 'y');
+               get_sleepnow = set_sleepnow |= (c == 'Y');
+               reread_partn |= (c == 'z');
+               get_seagate = set_seagate |= (c == 'Z');
+#endif
+               USE_FEATURE_HDPARM_HDIO_UNREGISTER_HWIF(if (c == 'U') parse_opts(NULL, &unregister_hwif, &hwif, 0, INT_MAX));
+#ifdef HDIO_GET_QDMA
+               if (c == 'Q') {
+#ifdef HDIO_SET_QDMA
+                       parse_opts(&get_dma_q, &set_dma_q, &dma_q, 0, INT_MAX);
+#else
+                       parse_opts(&get_dma_q, NULL, NULL, 0, 0);
+#endif
+               }
+#endif
+               USE_FEATURE_HDPARM_HDIO_DRIVE_RESET(perform_reset = (c == 'r'));
+               USE_FEATURE_HDPARM_HDIO_TRISTATE_HWIF(if (c == 'x') parse_opts(NULL, &perform_tristate, &tristate, 0, 1));
+               USE_FEATURE_HDPARM_HDIO_TRISTATE_HWIF(if (c == 'b') parse_opts(&get_busstate, &set_busstate, &busstate, 0, 2));
+#if ENABLE_FEATURE_HDPARM_HDIO_SCAN_HWIF
+               if (c == 'R') {
+                       parse_opts(NULL, &scan_hwif, &hwif_data, 0, INT_MAX);
+                       hwif_ctrl = xatoi_u((argv[optind]) ? argv[optind] : "");
+                       hwif_irq  = xatoi_u((argv[optind+1]) ? argv[optind+1] : "");
+                       /* Move past the 2 additional arguments */
+                       argv += 2;
+                       argc -= 2;
+               }
+#endif
+       }
+       /* When no flags are given (flagcount = 0), -acdgkmnru is assumed. */
+       if (!flagcount) {
+               get_mult = get_io32bit = get_unmask = get_keep = get_readonly = get_readahead = get_geom = 1;
+               USE_FEATURE_HDPARM_HDIO_GETSET_DMA(get_dma = 1);
+       }
+       argv += optind;
+
+       if (!*argv) {
+               if (ENABLE_FEATURE_HDPARM_GET_IDENTITY && !isatty(STDIN_FILENO))
+                       identify_from_stdin(); /* EXIT */
+               bb_show_usage();
+       }
+
+       do {
+               process_dev(*argv++);
+       } while (*argv);
+
+       return EXIT_SUCCESS;
+}
diff --git a/miscutils/inotifyd.c b/miscutils/inotifyd.c
new file mode 100644 (file)
index 0000000..d6b5d24
--- /dev/null
@@ -0,0 +1,176 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * simple inotify daemon
+ * reports filesystem changes via userspace agent
+ *
+ * Copyright (C) 2008 by Vladimir Dronnikov <dronnikov@gmail.com>
+ *
+ * Licensed under GPLv2, see file LICENSE in this tarball for details.
+ */
+
+/*
+ * Use as follows:
+ * # inotifyd /user/space/agent dir/or/file/being/watched[:mask] ...
+ *
+ * When a filesystem event matching the specified mask is occured on specified file (or directory)
+ * a userspace agent is spawned and given the following parameters:
+ * $1. actual event(s)
+ * $2. file (or directory) name
+ * $3. name of subfile (if any), in case of watching a directory
+ *
+ * E.g. inotifyd ./dev-watcher /dev:n
+ *
+ * ./dev-watcher can be, say:
+ * #!/bin/sh
+ * echo "We have new device in here! Hello, $3!"
+ *
+ * See below for mask names explanation.
+ */
+
+#include "libbb.h"
+#include <sys/inotify.h>
+
+static const char mask_names[] ALIGN1 =
+       "a"     // 0x00000001   File was accessed
+       "c"     // 0x00000002   File was modified
+       "e"     // 0x00000004   Metadata changed
+       "w"     // 0x00000008   Writtable file was closed
+       "0"     // 0x00000010   Unwrittable file closed
+       "r"     // 0x00000020   File was opened
+       "m"     // 0x00000040   File was moved from X
+       "y"     // 0x00000080   File was moved to Y
+       "n"     // 0x00000100   Subfile was created
+       "d"     // 0x00000200   Subfile was deleted
+       "D"     // 0x00000400   Self was deleted
+       "M"     // 0x00000800   Self was moved
+       "\0"    // 0x00001000   (unused)
+       // Kernel events, always reported:
+       "u"     // 0x00002000   Backing fs was unmounted
+       "o"     // 0x00004000   Event queued overflowed
+       "x"     // 0x00008000   File is no longer watched (usually deleted)
+;
+enum {
+       MASK_BITS = sizeof(mask_names) - 1
+};
+
+int inotifyd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int inotifyd_main(int argc, char **argv)
+{
+       int n;
+       unsigned mask;
+       struct pollfd pfd;
+       char **watches; // names of files being watched
+       const char *args[5];
+
+       // sanity check: agent and at least one watch must be given
+       if (!argv[1] || !argv[2])
+               bb_show_usage();
+
+       argv++;
+       // inotify_add_watch will number watched files
+       // starting from 1, thus watches[0] is unimportant,
+       // and 1st file name is watches[1].
+       watches = argv;
+       args[0] = *argv;
+       args[4] = NULL;
+       argc -= 2; // number of files we watch
+
+       // open inotify
+       pfd.fd = inotify_init();
+       if (pfd.fd < 0)
+               bb_perror_msg_and_die("no kernel support");
+
+       // setup watches
+       while (*++argv) {
+               char *path = *argv;
+               char *masks = strchr(path, ':');
+
+               mask = 0x0fff; // assuming we want all non-kernel events
+               // if mask is specified ->
+               if (masks) {
+                       *masks = '\0'; // split path and mask
+                       // convert mask names to mask bitset
+                       mask = 0;
+                       while (*++masks) {
+                               const char *found;
+                               found = memchr(mask_names, *masks, MASK_BITS);
+                               if (found)
+                                       mask |= (1 << (found - mask_names));
+                       }
+               }
+               // add watch
+               n = inotify_add_watch(pfd.fd, path, mask);
+               if (n < 0)
+                       bb_perror_msg_and_die("add watch (%s) failed", path);
+               //bb_error_msg("added %d [%s]:%4X", n, path, mask);
+       }
+
+       // setup signals
+       bb_signals(BB_FATAL_SIGS, record_signo);
+
+       // do watch
+       pfd.events = POLLIN;
+       while (1) {
+               int len;
+               void *buf;
+               struct inotify_event *ie;
+ again:
+               if (bb_got_signal)
+                       break;
+               n = poll(&pfd, 1, -1);
+               // Signal interrupted us?
+               if (n < 0 && errno == EINTR)
+                       goto again;
+               // Under Linux, above if() is not necessary.
+               // Non-fatal signals, e.g. SIGCHLD, when set to SIG_DFL,
+               // are not interrupting poll().
+               // Thus we can just break if n <= 0 (see below),
+               // because EINTR will happen only on SIGTERM et al.
+               // But this might be not true under other Unixes,
+               // and is generally way too subtle to depend on.
+               if (n <= 0) // strange error?
+                       break;
+
+               // read out all pending events
+               // (NB: len must be int, not ssize_t or long!)
+               xioctl(pfd.fd, FIONREAD, &len);
+#define eventbuf bb_common_bufsiz1
+               ie = buf = (len <= sizeof(eventbuf)) ? eventbuf : xmalloc(len);
+               len = full_read(pfd.fd, buf, len);
+               // process events. N.B. events may vary in length
+               while (len > 0) {
+                       int i;
+                       // cache relevant events mask
+                       unsigned m = ie->mask & ((1 << MASK_BITS) - 1);
+                       if (m) {
+                               char events[MASK_BITS + 1];
+                               char *s = events;
+                               for (i = 0; i < MASK_BITS; ++i, m >>= 1) {
+                                       if ((m & 1) && (mask_names[i] != '\0'))
+                                               *s++ = mask_names[i];
+                               }
+                               *s = '\0';
+//                             bb_error_msg("exec %s %08X\t%s\t%s\t%s", args[0],
+//                                     ie->mask, events, watches[ie->wd], ie->len ? ie->name : "");
+                               args[1] = events;
+                               args[2] = watches[ie->wd];
+                               args[3] = ie->len ? ie->name : NULL;
+                               wait4pid(xspawn((char **)args));
+                               // we are done if all files got final x event
+                               if (ie->mask & 0x8000) {
+                                       if (--argc <= 0)
+                                               goto done;
+                                       inotify_rm_watch(pfd.fd, ie->wd);
+                               }
+                       }
+                       // next event
+                       i = sizeof(struct inotify_event) + ie->len;
+                       len -= i;
+                       ie = (void*)((char*)ie + i);
+               }
+               if (eventbuf != buf)
+                       free(buf);
+       } // while (1)
+ done:
+       return bb_got_signal;
+}
diff --git a/miscutils/ionice.c b/miscutils/ionice.c
new file mode 100644 (file)
index 0000000..361c141
--- /dev/null
@@ -0,0 +1,99 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * ionice implementation for busybox based on linux-utils-ng 2.14
+ *
+ * Copyright (C) 2008 by  <u173034@informatik.uni-oldenburg.de>
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+#include <sys/syscall.h>
+#include <asm/unistd.h>
+#include "libbb.h"
+
+static int ioprio_set(int which, int who, int ioprio)
+{
+       return syscall(SYS_ioprio_set, which, who, ioprio);
+}
+
+static int ioprio_get(int which, int who)
+{
+       return syscall(SYS_ioprio_get, which, who);
+}
+
+enum {
+       IOPRIO_WHO_PROCESS = 1,
+       IOPRIO_WHO_PGRP,
+       IOPRIO_WHO_USER
+};
+
+enum {
+       IOPRIO_CLASS_NONE,
+       IOPRIO_CLASS_RT,
+       IOPRIO_CLASS_BE,
+       IOPRIO_CLASS_IDLE
+};
+
+static const char to_prio[] = "none\0realtime\0best-effort\0idle";
+
+#define IOPRIO_CLASS_SHIFT      13
+
+int ionice_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int ionice_main(int argc UNUSED_PARAM, char **argv)
+{
+       /* Defaults */
+       int ioclass = 0;
+       int pri = 0;
+       int pid = 0; /* affect own porcess */
+       int opt;
+       enum {
+               OPT_n = 1,
+               OPT_c = 2,
+               OPT_p = 4,
+       };
+
+       /* Numeric params */
+       opt_complementary = "n+:c+:p+";
+       /* '+': stop at first non-option */
+       opt = getopt32(argv, "+n:c:p:", &pri, &ioclass, &pid);
+       argv += optind;
+
+       if (opt & OPT_c) {
+               if (ioclass > 3)
+                       bb_error_msg_and_die("bad class %d", ioclass);
+// Do we need this (compat?)?
+//             if (ioclass == IOPRIO_CLASS_NONE)
+//                     ioclass = IOPRIO_CLASS_BE;
+//             if (ioclass == IOPRIO_CLASS_IDLE) {
+//                     //if (opt & OPT_n)
+//                     //      bb_error_msg("ignoring priority for idle class");
+//                     pri = 7;
+//             }
+       }
+
+       if (!(opt & (OPT_n|OPT_c))) {
+               if (!(opt & OPT_p) && *argv)
+                       pid = xatoi_u(*argv);
+
+               pri = ioprio_get(IOPRIO_WHO_PROCESS, pid);
+               if (pri == -1)
+                       bb_perror_msg_and_die("ioprio_%cet", 'g');
+
+               ioclass = (pri >> IOPRIO_CLASS_SHIFT) & 0x3;
+               pri &= 0xff;
+               printf((ioclass == IOPRIO_CLASS_IDLE) ? "%s\n" : "%s: prio %d\n",
+                               nth_string(to_prio, ioclass), pri);
+       } else {
+//printf("pri=%d class=%d val=%x\n",
+//pri, ioclass, pri | (ioclass << IOPRIO_CLASS_SHIFT));
+               pri |= (ioclass << IOPRIO_CLASS_SHIFT);
+               if (ioprio_set(IOPRIO_WHO_PROCESS, pid, pri) == -1)
+                       bb_perror_msg_and_die("ioprio_%cet", 's');
+               if (*argv) {
+                       BB_EXECVP(*argv, argv);
+                       bb_simple_perror_msg_and_die(*argv);
+               }
+       }
+
+       return EXIT_SUCCESS;
+}
diff --git a/miscutils/last.c b/miscutils/last.c
new file mode 100644 (file)
index 0000000..f8c3013
--- /dev/null
@@ -0,0 +1,133 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * last implementation for busybox
+ *
+ * Copyright (C) 2003-2004 by Erik Andersen <andersen@codepoet.org>
+ *
+ * Licensed under the GPL version 2, see the file LICENSE in this tarball.
+ */
+
+#include "libbb.h"
+#include <utmp.h>
+
+/* NB: ut_name and ut_user are the same field, use only one name (ut_user)
+ * to reduce confusion */
+
+#ifndef SHUTDOWN_TIME
+#  define SHUTDOWN_TIME 254
+#endif
+
+/* Grr... utmp char[] members do not have to be nul-terminated.
+ * Do what we can while still keeping this reasonably small.
+ * Note: We are assuming the ut_id[] size is fixed at 4. */
+
+#if defined UT_LINESIZE \
+       && ((UT_LINESIZE != 32) || (UT_NAMESIZE != 32) || (UT_HOSTSIZE != 256))
+#error struct utmp member char[] size(s) have changed!
+#elif defined __UT_LINESIZE \
+       && ((__UT_LINESIZE != 32) || (__UT_NAMESIZE != 64) || (__UT_HOSTSIZE != 256))
+#error struct utmp member char[] size(s) have changed!
+#endif
+
+#if EMPTY != 0 || RUN_LVL != 1 || BOOT_TIME != 2 || NEW_TIME != 3 || \
+       OLD_TIME != 4
+#error Values for the ut_type field of struct utmp changed
+#endif
+
+int last_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int last_main(int argc, char **argv UNUSED_PARAM)
+{
+       struct utmp ut;
+       int n, file = STDIN_FILENO;
+       time_t t_tmp;
+       off_t pos;
+       static const char _ut_usr[] ALIGN1 =
+                       "runlevel\0" "reboot\0" "shutdown\0";
+       static const char _ut_lin[] ALIGN1 =
+                       "~\0" "{\0" "|\0" /* "LOGIN\0" "date\0" */;
+       enum {
+               TYPE_RUN_LVL = RUN_LVL,         /* 1 */
+               TYPE_BOOT_TIME = BOOT_TIME,     /* 2 */
+               TYPE_SHUTDOWN_TIME = SHUTDOWN_TIME
+       };
+       enum {
+               _TILDE = EMPTY,                         /* 0 */
+               TYPE_NEW_TIME,  /* NEW_TIME, 3 */
+               TYPE_OLD_TIME   /* OLD_TIME, 4 */
+       };
+
+       if (argc > 1) {
+               bb_show_usage();
+       }
+       file = xopen(bb_path_wtmp_file, O_RDONLY);
+
+       printf("%-10s %-14s %-18s %-12.12s %s\n",
+              "USER", "TTY", "HOST", "LOGIN", "TIME");
+       /* yikes. We reverse over the file and that is a not too elegant way */
+       pos = xlseek(file, 0, SEEK_END);
+       pos = lseek(file, pos - sizeof(ut), SEEK_SET);
+       while ((n = full_read(file, &ut, sizeof(ut))) > 0) {
+               if (n != sizeof(ut)) {
+                       bb_perror_msg_and_die("short read");
+               }
+               n = index_in_strings(_ut_lin, ut.ut_line);
+               if (n == _TILDE) { /* '~' */
+#if 1
+/* do we really need to be cautious here? */
+                       n = index_in_strings(_ut_usr, ut.ut_user);
+                       if (++n > 0)
+                               ut.ut_type = n != 3 ? n : SHUTDOWN_TIME;
+#else
+                       if (strncmp(ut.ut_user, "shutdown", 8) == 0)
+                               ut.ut_type = SHUTDOWN_TIME;
+                       else if (strncmp(ut.ut_user, "reboot", 6) == 0)
+                               ut.ut_type = BOOT_TIME;
+                       else if (strncmp(ut.ut_user, "runlevel", 8) == 0)
+                               ut.ut_type = RUN_LVL;
+#endif
+               } else {
+                       if (ut.ut_user[0] == '\0' || strcmp(ut.ut_user, "LOGIN") == 0) {
+                               /* Don't bother.  This means we can't find how long
+                                * someone was logged in for.  Oh well. */
+                               goto next;
+                       }
+                       if (ut.ut_type != DEAD_PROCESS
+                        && ut.ut_user[0] && ut.ut_line[0]
+                       ) {
+                               ut.ut_type = USER_PROCESS;
+                       }
+                       if (strcmp(ut.ut_user, "date") == 0) {
+                               if (n == TYPE_OLD_TIME) { /* '|' */
+                                       ut.ut_type = OLD_TIME;
+                               }
+                               if (n == TYPE_NEW_TIME) { /* '{' */
+                                       ut.ut_type = NEW_TIME;
+                               }
+                       }
+               }
+
+               if (ut.ut_type != USER_PROCESS) {
+                       switch (ut.ut_type) {
+                               case OLD_TIME:
+                               case NEW_TIME:
+                               case RUN_LVL:
+                               case SHUTDOWN_TIME:
+                                       goto next;
+                               case BOOT_TIME:
+                                       strcpy(ut.ut_line, "system boot");
+                       }
+               }
+               /* manpages say ut_tv.tv_sec *is* time_t,
+                * but some systems have it wrong */
+               t_tmp = (time_t)ut.ut_tv.tv_sec;
+               printf("%-10s %-14s %-18s %-12.12s\n",
+                      ut.ut_user, ut.ut_line, ut.ut_host, ctime(&t_tmp) + 4);
+ next:
+               pos -= sizeof(ut);
+               if (pos <= 0)
+                       break; /* done. */
+               xlseek(file, pos, SEEK_SET);
+       }
+
+       fflush_stdout_and_exit(EXIT_SUCCESS);
+}
diff --git a/miscutils/last_fancy.c b/miscutils/last_fancy.c
new file mode 100644 (file)
index 0000000..f3ea037
--- /dev/null
@@ -0,0 +1,297 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * (sysvinit like) last implementation
+ *
+ * Copyright (C) 2008 by Patricia Muscalu <patricia.muscalu@axis.com>
+ *
+ * Licensed under the GPLv2 or later, see the file LICENSE in this tarball.
+ */
+
+#include "libbb.h"
+#include <utmp.h>
+
+/* NB: ut_name and ut_user are the same field, use only one name (ut_user)
+ * to reduce confusion */
+
+#ifndef SHUTDOWN_TIME
+#  define SHUTDOWN_TIME 254
+#endif
+
+#define HEADER_FORMAT     "%-8.8s %-12.12s %-*.*s %-16.16s %-7.7s %s\n"
+#define HEADER_LINE       "USER", "TTY", \
+       INET_ADDRSTRLEN, INET_ADDRSTRLEN, "HOST", "LOGIN", "  TIME", ""
+#define HEADER_LINE_WIDE  "USER", "TTY", \
+       INET6_ADDRSTRLEN, INET6_ADDRSTRLEN, "HOST", "LOGIN", "  TIME", ""
+
+enum {
+       NORMAL,
+       LOGGED,
+       DOWN,
+       REBOOT,
+       CRASH,
+       GONE
+};
+
+enum {
+       LAST_OPT_W = (1 << 0),  /* -W wide            */
+       LAST_OPT_f = (1 << 1),  /* -f input file      */
+       LAST_OPT_H = (1 << 2),  /* -H header          */
+};
+
+#define show_wide (option_mask32 & LAST_OPT_W)
+
+static void show_entry(struct utmp *ut, int state, time_t dur_secs)
+{
+       unsigned days, hours, mins;
+       char duration[32];
+       char login_time[17];
+       char logout_time[8];
+       const char *logout_str;
+       const char *duration_str;
+       time_t tmp;
+
+       /* manpages say ut_tv.tv_sec *is* time_t,
+        * but some systems have it wrong */
+       tmp = ut->ut_tv.tv_sec;
+       safe_strncpy(login_time, ctime(&tmp), 17);
+       snprintf(logout_time, 8, "- %s", ctime(&dur_secs) + 11);
+
+       dur_secs = MAX(dur_secs - (time_t)ut->ut_tv.tv_sec, (time_t)0);
+       /* unsigned int is easier to divide than time_t (which may be signed long) */
+       mins = dur_secs / 60;
+       days = mins / (24*60);
+       mins = mins % (24*60);
+       hours = mins / 60;
+       mins = mins % 60;
+
+//     if (days) {
+               sprintf(duration, "(%u+%02u:%02u)", days, hours, mins);
+//     } else {
+//             sprintf(duration, " (%02u:%02u)", hours, mins);
+//     }
+
+       logout_str = logout_time;
+       duration_str = duration;
+       switch (state) {
+       case NORMAL:
+               break;
+       case LOGGED:
+               logout_str = "  still";
+               duration_str = "logged in";
+               break;
+       case DOWN:
+               logout_str = "- down ";
+               break;
+       case REBOOT:
+               break;
+       case CRASH:
+               logout_str = "- crash";
+               break;
+       case GONE:
+               logout_str = "   gone";
+               duration_str = "- no logout";
+               break;
+       }
+
+       printf(HEADER_FORMAT,
+                  ut->ut_user,
+                  ut->ut_line,
+                  show_wide ? INET6_ADDRSTRLEN : INET_ADDRSTRLEN,
+                  show_wide ? INET6_ADDRSTRLEN : INET_ADDRSTRLEN,
+                  ut->ut_host,
+                  login_time,
+                  logout_str,
+                  duration_str);
+}
+
+static int get_ut_type(struct utmp *ut)
+{
+       if (ut->ut_line[0] == '~') {
+               if (strcmp(ut->ut_user, "shutdown") == 0) {
+                       return SHUTDOWN_TIME;
+               }
+               if (strcmp(ut->ut_user, "reboot") == 0) {
+                       return BOOT_TIME;
+               }
+               if (strcmp(ut->ut_user, "runlevel") == 0) {
+                       return RUN_LVL;
+               }
+               return ut->ut_type;
+       }
+
+       if (ut->ut_user[0] == 0) {
+               return DEAD_PROCESS;
+       }
+
+       if ((ut->ut_type != DEAD_PROCESS)
+        && (strcmp(ut->ut_user, "LOGIN") != 0)
+        && ut->ut_user[0]
+        && ut->ut_line[0]
+       ) {
+               ut->ut_type = USER_PROCESS;
+       }
+
+       if (strcmp(ut->ut_user, "date") == 0) {
+               if (ut->ut_line[0] == '|') {
+                       return OLD_TIME;
+               }
+               if (ut->ut_line[0] == '{') {
+                       return NEW_TIME;
+               }
+       }
+       return ut->ut_type;
+}
+
+static int is_runlevel_shutdown(struct utmp *ut)
+{
+       if (((ut->ut_pid & 255) == '0') || ((ut->ut_pid & 255) == '6')) {
+               return 1;
+       }
+
+       return 0;
+}
+
+int last_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int last_main(int argc UNUSED_PARAM, char **argv)
+{
+       struct utmp ut;
+       const char *filename = _PATH_WTMP;
+       llist_t *zlist;
+       off_t pos;
+       time_t start_time;
+       time_t boot_time;
+       time_t down_time;
+       int file;
+       unsigned opt;
+       smallint going_down;
+       smallint boot_down;
+
+       opt = getopt32(argv, "Wf:" /* "H" */, &filename);
+#ifdef BUT_UTIL_LINUX_LAST_HAS_NO_SUCH_OPT
+       if (opt & LAST_OPT_H) {
+               /* Print header line */
+               if (opt & LAST_OPT_W) {
+                       printf(HEADER_FORMAT, HEADER_LINE_WIDE);
+               } else {
+                       printf(HEADER_FORMAT, HEADER_LINE);
+               }
+       }
+#endif
+
+       file = xopen(filename, O_RDONLY);
+       {
+               /* in case the file is empty... */
+               struct stat st;
+               fstat(file, &st);
+               start_time = st.st_ctime;
+       }
+
+       time(&down_time);
+       going_down = 0;
+       boot_down = NORMAL; /* 0 */
+       zlist = NULL;
+       boot_time = 0;
+       /* get file size, rounding down to last full record */
+       pos = xlseek(file, 0, SEEK_END) / sizeof(ut) * sizeof(ut);
+       for (;;) {
+               pos -= (off_t)sizeof(ut);
+               if (pos < 0) {
+                       /* Beyond the beginning of the file boundary =>
+                        * the whole file has been read. */
+                       break;
+               }
+               xlseek(file, pos, SEEK_SET);
+               xread(file, &ut, sizeof(ut));
+               /* rewritten by each record, eventially will have
+                * first record's ut_tv.tv_sec: */
+               start_time = ut.ut_tv.tv_sec;
+
+               switch (get_ut_type(&ut)) {
+               case SHUTDOWN_TIME:
+                       down_time = ut.ut_tv.tv_sec;
+                       boot_down = DOWN;
+                       going_down = 1;
+                       break;
+               case RUN_LVL:
+                       if (is_runlevel_shutdown(&ut)) {
+                               down_time = ut.ut_tv.tv_sec;
+                               going_down = 1;
+                               boot_down = DOWN;
+                       }
+                       break;
+               case BOOT_TIME:
+                       strcpy(ut.ut_line, "system boot");
+                       show_entry(&ut, REBOOT, down_time);
+                       boot_down = CRASH;
+                       going_down = 1;
+                       break;
+               case DEAD_PROCESS:
+                       if (!ut.ut_line[0]) {
+                               break;
+                       }
+                       /* add_entry */
+                       llist_add_to(&zlist, memcpy(xmalloc(sizeof(ut)), &ut, sizeof(ut)));
+                       break;
+               case USER_PROCESS: {
+                       int show;
+
+                       if (!ut.ut_line[0]) {
+                               break;
+                       }
+                       /* find_entry */
+                       show = 1;
+                       {
+                               llist_t *el, *next;
+                               for (el = zlist; el; el = next) {
+                                       struct utmp *up = (struct utmp *)el->data;
+                                       next = el->link;
+                                       if (strncmp(up->ut_line, ut.ut_line, UT_LINESIZE) == 0) {
+                                               if (show) {
+                                                       show_entry(&ut, NORMAL, up->ut_tv.tv_sec);
+                                                       show = 0;
+                                               }
+                                               llist_unlink(&zlist, el);
+                                               free(el->data);
+                                               free(el);
+                                       }
+                               }
+                       }
+
+                       if (show) {
+                               int state = boot_down;
+
+                               if (boot_time == 0) {
+                                       state = LOGGED;
+                                       /* Check if the process is alive */
+                                       if ((ut.ut_pid > 0)
+                                        && (kill(ut.ut_pid, 0) != 0)
+                                        && (errno == ESRCH)) {
+                                               state = GONE;
+                                       }
+                               }
+                               show_entry(&ut, state, boot_time);
+                       }
+                       /* add_entry */
+                       llist_add_to(&zlist, memcpy(xmalloc(sizeof(ut)), &ut, sizeof(ut)));
+                       break;
+               }
+               }
+
+               if (going_down) {
+                       boot_time = ut.ut_tv.tv_sec;
+                       llist_free(zlist, free);
+                       zlist = NULL;
+                       going_down = 0;
+               }
+       }
+
+       if (ENABLE_FEATURE_CLEAN_UP) {
+               llist_free(zlist, free);
+       }
+
+       printf("\nwtmp begins %s", ctime(&start_time));
+
+       if (ENABLE_FEATURE_CLEAN_UP)
+               close(file);
+       fflush_stdout_and_exit(EXIT_SUCCESS);
+}
diff --git a/miscutils/less.c b/miscutils/less.c
new file mode 100644 (file)
index 0000000..27855bb
--- /dev/null
@@ -0,0 +1,1799 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Mini less implementation for busybox
+ *
+ * Copyright (C) 2005 by Rob Sullivan <cogito.ergo.cogito@gmail.com>
+ *
+ * Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
+ */
+
+/*
+ * TODO:
+ * - Add more regular expression support - search modifiers, certain matches, etc.
+ * - Add more complex bracket searching - currently, nested brackets are
+ *   not considered.
+ * - Add support for "F" as an input. This causes less to act in
+ *   a similar way to tail -f.
+ * - Allow horizontal scrolling.
+ *
+ * Notes:
+ * - the inp file pointer is used so that keyboard input works after
+ *   redirected input has been read from stdin
+ */
+
+#include <sched.h>     /* sched_yield() */
+
+#include "libbb.h"
+#if ENABLE_FEATURE_LESS_REGEXP
+#include "xregex.h"
+#endif
+
+/* The escape codes for highlighted and normal text */
+#define HIGHLIGHT "\033[7m"
+#define NORMAL "\033[0m"
+/* The escape code to clear the screen */
+#define CLEAR "\033[H\033[J"
+/* The escape code to clear to end of line */
+#define CLEAR_2_EOL "\033[K"
+
+enum {
+/* Absolute max of lines eaten */
+       MAXLINES = CONFIG_FEATURE_LESS_MAXLINES,
+/* This many "after the end" lines we will show (at max) */
+       TILDES = 1,
+};
+
+/* Command line options */
+enum {
+       FLAG_E = 1 << 0,
+       FLAG_M = 1 << 1,
+       FLAG_m = 1 << 2,
+       FLAG_N = 1 << 3,
+       FLAG_TILDE = 1 << 4,
+       FLAG_I = 1 << 5,
+       FLAG_S = (1 << 6) * ENABLE_FEATURE_LESS_DASHCMD,
+/* hijack command line options variable for internal state vars */
+       LESS_STATE_MATCH_BACKWARDS = 1 << 15,
+};
+
+#if !ENABLE_FEATURE_LESS_REGEXP
+enum { pattern_valid = 0 };
+#endif
+
+struct globals {
+       int cur_fline; /* signed */
+       int kbd_fd;  /* fd to get input from */
+       int less_gets_pos;
+/* last position in last line, taking into account tabs */
+       size_t last_line_pos;
+       unsigned max_fline;
+       unsigned max_lineno; /* this one tracks linewrap */
+       unsigned max_displayed_line;
+       unsigned width;
+#if ENABLE_FEATURE_LESS_WINCH
+       unsigned winch_counter;
+#endif
+       ssize_t eof_error; /* eof if 0, error if < 0 */
+       ssize_t readpos;
+       ssize_t readeof; /* must be signed */
+       const char **buffer;
+       const char **flines;
+       const char *empty_line_marker;
+       unsigned num_files;
+       unsigned current_file;
+       char *filename;
+       char **files;
+#if ENABLE_FEATURE_LESS_MARKS
+       unsigned num_marks;
+       unsigned mark_lines[15][2];
+#endif
+#if ENABLE_FEATURE_LESS_REGEXP
+       unsigned *match_lines;
+       int match_pos; /* signed! */
+       int wanted_match; /* signed! */
+       int num_matches;
+       regex_t pattern;
+       smallint pattern_valid;
+#endif
+       smallint terminated;
+       smalluint kbd_input_size;
+       struct termios term_orig, term_less;
+       char kbd_input[KEYCODE_BUFFER_SIZE];
+};
+#define G (*ptr_to_globals)
+#define cur_fline           (G.cur_fline         )
+#define kbd_fd              (G.kbd_fd            )
+#define less_gets_pos       (G.less_gets_pos     )
+#define last_line_pos       (G.last_line_pos     )
+#define max_fline           (G.max_fline         )
+#define max_lineno          (G.max_lineno        )
+#define max_displayed_line  (G.max_displayed_line)
+#define width               (G.width             )
+#define winch_counter       (G.winch_counter     )
+/* This one is 100% not cached by compiler on read access */
+#define WINCH_COUNTER (*(volatile unsigned *)&winch_counter)
+#define eof_error           (G.eof_error         )
+#define readpos             (G.readpos           )
+#define readeof             (G.readeof           )
+#define buffer              (G.buffer            )
+#define flines              (G.flines            )
+#define empty_line_marker   (G.empty_line_marker )
+#define num_files           (G.num_files         )
+#define current_file        (G.current_file      )
+#define filename            (G.filename          )
+#define files               (G.files             )
+#define num_marks           (G.num_marks         )
+#define mark_lines          (G.mark_lines        )
+#if ENABLE_FEATURE_LESS_REGEXP
+#define match_lines         (G.match_lines       )
+#define match_pos           (G.match_pos         )
+#define num_matches         (G.num_matches       )
+#define wanted_match        (G.wanted_match      )
+#define pattern             (G.pattern           )
+#define pattern_valid       (G.pattern_valid     )
+#endif
+#define terminated          (G.terminated        )
+#define term_orig           (G.term_orig         )
+#define term_less           (G.term_less         )
+#define kbd_input_size      (G.kbd_input_size    )
+#define kbd_input           (G.kbd_input         )
+#define INIT_G() do { \
+       SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
+       less_gets_pos = -1; \
+       empty_line_marker = "~"; \
+       num_files = 1; \
+       current_file = 1; \
+       eof_error = 1; \
+       terminated = 1; \
+       USE_FEATURE_LESS_REGEXP(wanted_match = -1;) \
+} while (0)
+
+/* flines[] are lines read from stdin, each in malloc'ed buffer.
+ * Line numbers are stored as uint32_t prepended to each line.
+ * Pointer is adjusted so that flines[i] points directly past
+ * line number. Accesor: */
+#define MEMPTR(p) ((char*)(p) - 4)
+#define LINENO(p) (*(uint32_t*)((p) - 4))
+
+
+/* Reset terminal input to normal */
+static void set_tty_cooked(void)
+{
+       fflush(stdout);
+       tcsetattr(kbd_fd, TCSANOW, &term_orig);
+}
+
+/* Move the cursor to a position (x,y), where (0,0) is the
+   top-left corner of the console */
+static void move_cursor(int line, int row)
+{
+       printf("\033[%u;%uH", line, row);
+}
+
+static void clear_line(void)
+{
+       printf("\033[%u;0H" CLEAR_2_EOL, max_displayed_line + 2);
+}
+
+static void print_hilite(const char *str)
+{
+       printf(HIGHLIGHT"%s"NORMAL, str);
+}
+
+static void print_statusline(const char *str)
+{
+       clear_line();
+       printf(HIGHLIGHT"%.*s"NORMAL, width - 1, str);
+}
+
+/* Exit the program gracefully */
+static void less_exit(int code)
+{
+       set_tty_cooked();
+       clear_line();
+       if (code < 0)
+               kill_myself_with_sig(- code); /* does not return */
+       exit(code);
+}
+
+#if (ENABLE_FEATURE_LESS_DASHCMD && ENABLE_FEATURE_LESS_LINENUMS) \
+ || ENABLE_FEATURE_LESS_WINCH
+static void re_wrap(void)
+{
+       int w = width;
+       int new_line_pos;
+       int src_idx;
+       int dst_idx;
+       int new_cur_fline = 0;
+       uint32_t lineno;
+       char linebuf[w + 1];
+       const char **old_flines = flines;
+       const char *s;
+       char **new_flines = NULL;
+       char *d;
+
+       if (option_mask32 & FLAG_N)
+               w -= 8;
+
+       src_idx = 0;
+       dst_idx = 0;
+       s = old_flines[0];
+       lineno = LINENO(s);
+       d = linebuf;
+       new_line_pos = 0;
+       while (1) {
+               *d = *s;
+               if (*d != '\0') {
+                       new_line_pos++;
+                       if (*d == '\t') /* tab */
+                               new_line_pos += 7;
+                       s++;
+                       d++;
+                       if (new_line_pos >= w) {
+                               int sz;
+                               /* new line is full, create next one */
+                               *d = '\0';
+ next_new:
+                               sz = (d - linebuf) + 1; /* + 1: NUL */
+                               d = ((char*)xmalloc(sz + 4)) + 4;
+                               LINENO(d) = lineno;
+                               memcpy(d, linebuf, sz);
+                               new_flines = xrealloc_vector(new_flines, 8, dst_idx);
+                               new_flines[dst_idx] = d;
+                               dst_idx++;
+                               if (new_line_pos < w) {
+                                       /* if we came here thru "goto next_new" */
+                                       if (src_idx > max_fline)
+                                               break;
+                                       lineno = LINENO(s);
+                               }
+                               d = linebuf;
+                               new_line_pos = 0;
+                       }
+                       continue;
+               }
+               /* *d == NUL: old line ended, go to next old one */
+               free(MEMPTR(old_flines[src_idx]));
+               /* btw, convert cur_fline... */
+               if (cur_fline == src_idx)
+                       new_cur_fline = dst_idx;
+               src_idx++;
+               /* no more lines? finish last new line (and exit the loop) */
+               if (src_idx > max_fline)
+                       goto next_new;
+               s = old_flines[src_idx];
+               if (lineno != LINENO(s)) {
+                       /* this is not a continuation line!
+                        * create next _new_ line too */
+                       goto next_new;
+               }
+       }
+
+       free(old_flines);
+       flines = (const char **)new_flines;
+
+       max_fline = dst_idx - 1;
+       last_line_pos = new_line_pos;
+       cur_fline = new_cur_fline;
+       /* max_lineno is screen-size independent */
+#if ENABLE_FEATURE_LESS_REGEXP
+       pattern_valid = 0;
+#endif
+}
+#endif
+
+#if ENABLE_FEATURE_LESS_REGEXP
+static void fill_match_lines(unsigned pos);
+#else
+#define fill_match_lines(pos) ((void)0)
+#endif
+
+/* Devilishly complex routine.
+ *
+ * Has to deal with EOF and EPIPE on input,
+ * with line wrapping, with last line not ending in '\n'
+ * (possibly not ending YET!), with backspace and tabs.
+ * It reads input again if last time we got an EOF (thus supporting
+ * growing files) or EPIPE (watching output of slow process like make).
+ *
+ * Variables used:
+ * flines[] - array of lines already read. Linewrap may cause
+ *      one source file line to occupy several flines[n].
+ * flines[max_fline] - last line, possibly incomplete.
+ * terminated - 1 if flines[max_fline] is 'terminated'
+ *      (if there was '\n' [which isn't stored itself, we just remember
+ *      that it was seen])
+ * max_lineno - last line's number, this one doesn't increment
+ *      on line wrap, only on "real" new lines.
+ * readbuf[0..readeof-1] - small preliminary buffer.
+ * readbuf[readpos] - next character to add to current line.
+ * last_line_pos - screen line position of next char to be read
+ *      (takes into account tabs and backspaces)
+ * eof_error - < 0 error, == 0 EOF, > 0 not EOF/error
+ */
+static void read_lines(void)
+{
+#define readbuf bb_common_bufsiz1
+       char *current_line, *p;
+       int w = width;
+       char last_terminated = terminated;
+#if ENABLE_FEATURE_LESS_REGEXP
+       unsigned old_max_fline = max_fline;
+       time_t last_time = 0;
+       unsigned seconds_p1 = 3; /* seconds_to_loop + 1 */
+#endif
+
+       if (option_mask32 & FLAG_N)
+               w -= 8;
+
+ USE_FEATURE_LESS_REGEXP(again0:)
+
+       p = current_line = ((char*)xmalloc(w + 4)) + 4;
+       max_fline += last_terminated;
+       if (!last_terminated) {
+               const char *cp = flines[max_fline];
+               strcpy(p, cp);
+               p += strlen(current_line);
+               free(MEMPTR(flines[max_fline]));
+               /* last_line_pos is still valid from previous read_lines() */
+       } else {
+               last_line_pos = 0;
+       }
+
+       while (1) { /* read lines until we reach cur_fline or wanted_match */
+               *p = '\0';
+               terminated = 0;
+               while (1) { /* read chars until we have a line */
+                       char c;
+                       /* if no unprocessed chars left, eat more */
+                       if (readpos >= readeof) {
+                               ndelay_on(0);
+                               eof_error = safe_read(STDIN_FILENO, readbuf, sizeof(readbuf));
+                               ndelay_off(0);
+                               readpos = 0;
+                               readeof = eof_error;
+                               if (eof_error <= 0)
+                                       goto reached_eof;
+                       }
+                       c = readbuf[readpos];
+                       /* backspace? [needed for manpages] */
+                       /* <tab><bs> is (a) insane and */
+                       /* (b) harder to do correctly, so we refuse to do it */
+                       if (c == '\x8' && last_line_pos && p[-1] != '\t') {
+                               readpos++; /* eat it */
+                               last_line_pos--;
+                       /* was buggy (p could end up <= current_line)... */
+                               *--p = '\0';
+                               continue;
+                       }
+                       {
+                               size_t new_last_line_pos = last_line_pos + 1;
+                               if (c == '\t') {
+                                       new_last_line_pos += 7;
+                                       new_last_line_pos &= (~7);
+                               }
+                               if ((int)new_last_line_pos >= w)
+                                       break;
+                               last_line_pos = new_last_line_pos;
+                       }
+                       /* ok, we will eat this char */
+                       readpos++;
+                       if (c == '\n') {
+                               terminated = 1;
+                               last_line_pos = 0;
+                               break;
+                       }
+                       /* NUL is substituted by '\n'! */
+                       if (c == '\0') c = '\n';
+                       *p++ = c;
+                       *p = '\0';
+               } /* end of "read chars until we have a line" loop */
+               /* Corner case: linewrap with only "" wrapping to next line */
+               /* Looks ugly on screen, so we do not store this empty line */
+               if (!last_terminated && !current_line[0]) {
+                       last_terminated = 1;
+                       max_lineno++;
+                       continue;
+               }
+ reached_eof:
+               last_terminated = terminated;
+               flines = xrealloc_vector(flines, 8, max_fline);
+
+               flines[max_fline] = (char*)xrealloc(MEMPTR(current_line), strlen(current_line) + 1 + 4) + 4;
+               LINENO(flines[max_fline]) = max_lineno;
+               if (terminated)
+                       max_lineno++;
+
+               if (max_fline >= MAXLINES) {
+                       eof_error = 0; /* Pretend we saw EOF */
+                       break;
+               }
+               if (!(option_mask32 & FLAG_S)
+                 ? (max_fline > cur_fline + max_displayed_line)
+                 : (max_fline >= cur_fline
+                    && max_lineno > LINENO(flines[cur_fline]) + max_displayed_line)
+               ) {
+#if !ENABLE_FEATURE_LESS_REGEXP
+                       break;
+#else
+                       if (wanted_match >= num_matches) { /* goto_match called us */
+                               fill_match_lines(old_max_fline);
+                               old_max_fline = max_fline;
+                       }
+                       if (wanted_match < num_matches)
+                               break;
+#endif
+               }
+               if (eof_error <= 0) {
+                       if (eof_error < 0) {
+                               if (errno == EAGAIN) {
+                                       /* not yet eof or error, reset flag (or else
+                                        * we will hog CPU - select() will return
+                                        * immediately */
+                                       eof_error = 1;
+                               } else {
+                                       print_statusline("read error");
+                               }
+                       }
+#if !ENABLE_FEATURE_LESS_REGEXP
+                       break;
+#else
+                       if (wanted_match < num_matches) {
+                               break;
+                       } else { /* goto_match called us */
+                               time_t t = time(NULL);
+                               if (t != last_time) {
+                                       last_time = t;
+                                       if (--seconds_p1 == 0)
+                                               break;
+                               }
+                               sched_yield();
+                               goto again0; /* go loop again (max 2 seconds) */
+                       }
+#endif
+               }
+               max_fline++;
+               current_line = ((char*)xmalloc(w + 4)) + 4;
+               p = current_line;
+               last_line_pos = 0;
+       } /* end of "read lines until we reach cur_fline" loop */
+       fill_match_lines(old_max_fline);
+#if ENABLE_FEATURE_LESS_REGEXP
+       /* prevent us from being stuck in search for a match */
+       wanted_match = -1;
+#endif
+#undef readbuf
+}
+
+#if ENABLE_FEATURE_LESS_FLAGS
+/* Interestingly, writing calc_percent as a function saves around 32 bytes
+ * on my build. */
+static int calc_percent(void)
+{
+       unsigned p = (100 * (cur_fline+max_displayed_line+1) + max_fline/2) / (max_fline+1);
+       return p <= 100 ? p : 100;
+}
+
+/* Print a status line if -M was specified */
+static void m_status_print(void)
+{
+       int percentage;
+
+       if (less_gets_pos >= 0) /* don't touch statusline while input is done! */
+               return;
+
+       clear_line();
+       printf(HIGHLIGHT"%s", filename);
+       if (num_files > 1)
+               printf(" (file %i of %i)", current_file, num_files);
+       printf(" lines %i-%i/%i ",
+                       cur_fline + 1, cur_fline + max_displayed_line + 1,
+                       max_fline + 1);
+       if (cur_fline >= (int)(max_fline - max_displayed_line)) {
+               printf("(END)"NORMAL);
+               if (num_files > 1 && current_file != num_files)
+                       printf(HIGHLIGHT" - next: %s"NORMAL, files[current_file]);
+               return;
+       }
+       percentage = calc_percent();
+       printf("%i%%"NORMAL, percentage);
+}
+#endif
+
+/* Print the status line */
+static void status_print(void)
+{
+       const char *p;
+
+       if (less_gets_pos >= 0) /* don't touch statusline while input is done! */
+               return;
+
+       /* Change the status if flags have been set */
+#if ENABLE_FEATURE_LESS_FLAGS
+       if (option_mask32 & (FLAG_M|FLAG_m)) {
+               m_status_print();
+               return;
+       }
+       /* No flags set */
+#endif
+
+       clear_line();
+       if (cur_fline && cur_fline < (int)(max_fline - max_displayed_line)) {
+               bb_putchar(':');
+               return;
+       }
+       p = "(END)";
+       if (!cur_fline)
+               p = filename;
+       if (num_files > 1) {
+               printf(HIGHLIGHT"%s (file %i of %i)"NORMAL,
+                               p, current_file, num_files);
+               return;
+       }
+       print_hilite(p);
+}
+
+static void cap_cur_fline(int nlines)
+{
+       int diff;
+       if (cur_fline < 0)
+               cur_fline = 0;
+       if (cur_fline + max_displayed_line > max_fline + TILDES) {
+               cur_fline -= nlines;
+               if (cur_fline < 0)
+                       cur_fline = 0;
+               diff = max_fline - (cur_fline + max_displayed_line) + TILDES;
+               /* As the number of lines requested was too large, we just move
+               to the end of the file */
+               if (diff > 0)
+                       cur_fline += diff;
+       }
+}
+
+static const char controls[] ALIGN1 =
+       /* NUL: never encountered; TAB: not converted */
+       /**/"\x01\x02\x03\x04\x05\x06\x07\x08"  "\x0a\x0b\x0c\x0d\x0e\x0f"
+       "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f"
+       "\x7f\x9b"; /* DEL and infamous Meta-ESC :( */
+static const char ctrlconv[] ALIGN1 =
+       /* '\n': it's a former NUL - subst with '@', not 'J' */
+       "\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x40\x4b\x4c\x4d\x4e\x4f"
+       "\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f";
+
+static void lineno_str(char *nbuf9, const char *line)
+{
+       nbuf9[0] = '\0';
+       if (option_mask32 & FLAG_N) {
+               const char *fmt;
+               unsigned n;
+
+               if (line == empty_line_marker) {
+                       memset(nbuf9, ' ', 8);
+                       nbuf9[8] = '\0';
+                       return;
+               }
+               /* Width of 7 preserves tab spacing in the text */
+               fmt = "%7u ";
+               n = LINENO(line) + 1;
+               if (n > 9999999) {
+                       n %= 10000000;
+                       fmt = "%07u ";
+               }
+               sprintf(nbuf9, fmt, n);
+       }
+}
+
+
+#if ENABLE_FEATURE_LESS_REGEXP
+static void print_found(const char *line)
+{
+       int match_status;
+       int eflags;
+       char *growline;
+       regmatch_t match_structs;
+
+       char buf[width];
+       char nbuf9[9];
+       const char *str = line;
+       char *p = buf;
+       size_t n;
+
+       while (*str) {
+               n = strcspn(str, controls);
+               if (n) {
+                       if (!str[n]) break;
+                       memcpy(p, str, n);
+                       p += n;
+                       str += n;
+               }
+               n = strspn(str, controls);
+               memset(p, '.', n);
+               p += n;
+               str += n;
+       }
+       strcpy(p, str);
+
+       /* buf[] holds quarantined version of str */
+
+       /* Each part of the line that matches has the HIGHLIGHT
+          and NORMAL escape sequences placed around it.
+          NB: we regex against line, but insert text
+          from quarantined copy (buf[]) */
+       str = buf;
+       growline = NULL;
+       eflags = 0;
+       goto start;
+
+       while (match_status == 0) {
+               char *new = xasprintf("%s%.*s"HIGHLIGHT"%.*s"NORMAL,
+                               growline ? : "",
+                               match_structs.rm_so, str,
+                               match_structs.rm_eo - match_structs.rm_so,
+                                               str + match_structs.rm_so);
+               free(growline);
+               growline = new;
+               str += match_structs.rm_eo;
+               line += match_structs.rm_eo;
+               eflags = REG_NOTBOL;
+ start:
+               /* Most of the time doesn't find the regex, optimize for that */
+               match_status = regexec(&pattern, line, 1, &match_structs, eflags);
+               /* if even "" matches, treat it as "not a match" */
+               if (match_structs.rm_so >= match_structs.rm_eo)
+                       match_status = 1;
+       }
+
+       lineno_str(nbuf9, line);
+       if (!growline) {
+               printf(CLEAR_2_EOL"%s%s\n", nbuf9, str);
+               return;
+       }
+       printf(CLEAR_2_EOL"%s%s%s\n", nbuf9, growline, str);
+       free(growline);
+}
+#else
+void print_found(const char *line);
+#endif
+
+static void print_ascii(const char *str)
+{
+       char buf[width];
+       char nbuf9[9];
+       char *p;
+       size_t n;
+
+       lineno_str(nbuf9, str);
+       printf(CLEAR_2_EOL"%s", nbuf9);
+
+       while (*str) {
+               n = strcspn(str, controls);
+               if (n) {
+                       if (!str[n]) break;
+                       printf("%.*s", (int) n, str);
+                       str += n;
+               }
+               n = strspn(str, controls);
+               p = buf;
+               do {
+                       if (*str == 0x7f)
+                               *p++ = '?';
+                       else if (*str == (char)0x9b)
+                       /* VT100's CSI, aka Meta-ESC. Who's inventor? */
+                       /* I want to know who committed this sin */
+                               *p++ = '{';
+                       else
+                               *p++ = ctrlconv[(unsigned char)*str];
+                       str++;
+               } while (--n);
+               *p = '\0';
+               print_hilite(buf);
+       }
+       puts(str);
+}
+
+/* Print the buffer */
+static void buffer_print(void)
+{
+       unsigned i;
+
+       move_cursor(0, 0);
+       for (i = 0; i <= max_displayed_line; i++)
+               if (pattern_valid)
+                       print_found(buffer[i]);
+               else
+                       print_ascii(buffer[i]);
+       status_print();
+}
+
+static void buffer_fill_and_print(void)
+{
+       unsigned i;
+#if ENABLE_FEATURE_LESS_DASHCMD
+       int fpos = cur_fline;
+
+       if (option_mask32 & FLAG_S) {
+               /* Go back to the beginning of this line */
+               while (fpos && LINENO(flines[fpos]) == LINENO(flines[fpos-1]))
+                       fpos--;
+       }
+
+       i = 0;
+       while (i <= max_displayed_line && fpos <= max_fline) {
+               int lineno = LINENO(flines[fpos]);
+               buffer[i] = flines[fpos];
+               i++;
+               do {
+                       fpos++;
+               } while ((fpos <= max_fline)
+                     && (option_mask32 & FLAG_S)
+                     && lineno == LINENO(flines[fpos])
+               );
+       }
+#else
+       for (i = 0; i <= max_displayed_line && cur_fline + i <= max_fline; i++) {
+               buffer[i] = flines[cur_fline + i];
+       }
+#endif
+       for (; i <= max_displayed_line; i++) {
+               buffer[i] = empty_line_marker;
+       }
+       buffer_print();
+}
+
+/* Move the buffer up and down in the file in order to scroll */
+static void buffer_down(int nlines)
+{
+       cur_fline += nlines;
+       read_lines();
+       cap_cur_fline(nlines);
+       buffer_fill_and_print();
+}
+
+static void buffer_up(int nlines)
+{
+       cur_fline -= nlines;
+       if (cur_fline < 0) cur_fline = 0;
+       read_lines();
+       buffer_fill_and_print();
+}
+
+static void buffer_line(int linenum)
+{
+       if (linenum < 0)
+               linenum = 0;
+       cur_fline = linenum;
+       read_lines();
+       if (linenum + max_displayed_line > max_fline)
+               linenum = max_fline - max_displayed_line + TILDES;
+       if (linenum < 0)
+               linenum = 0;
+       cur_fline = linenum;
+       buffer_fill_and_print();
+}
+
+static void open_file_and_read_lines(void)
+{
+       if (filename) {
+               xmove_fd(xopen(filename, O_RDONLY), STDIN_FILENO);
+       } else {
+               /* "less" with no arguments in argv[] */
+               /* For status line only */
+               filename = xstrdup(bb_msg_standard_input);
+       }
+       readpos = 0;
+       readeof = 0;
+       last_line_pos = 0;
+       terminated = 1;
+       read_lines();
+}
+
+/* Reinitialize everything for a new file - free the memory and start over */
+static void reinitialize(void)
+{
+       unsigned i;
+
+       if (flines) {
+               for (i = 0; i <= max_fline; i++)
+                       free(MEMPTR(flines[i]));
+               free(flines);
+               flines = NULL;
+       }
+
+       max_fline = -1;
+       cur_fline = 0;
+       max_lineno = 0;
+       open_file_and_read_lines();
+       buffer_fill_and_print();
+}
+
+static ssize_t getch_nowait(void)
+{
+       int rd;
+       struct pollfd pfd[2];
+
+       pfd[0].fd = STDIN_FILENO;
+       pfd[0].events = POLLIN;
+       pfd[1].fd = kbd_fd;
+       pfd[1].events = POLLIN;
+ again:
+       tcsetattr(kbd_fd, TCSANOW, &term_less);
+       /* NB: select/poll returns whenever read will not block. Therefore:
+        * if eof is reached, select/poll will return immediately
+        * because read will immediately return 0 bytes.
+        * Even if select/poll says that input is available, read CAN block
+        * (switch fd into O_NONBLOCK'ed mode to avoid it)
+        */
+       rd = 1;
+       /* Are we interested in stdin? */
+//TODO: reuse code for determining this
+       if (!(option_mask32 & FLAG_S)
+          ? !(max_fline > cur_fline + max_displayed_line)
+          : !(max_fline >= cur_fline
+              && max_lineno > LINENO(flines[cur_fline]) + max_displayed_line)
+       ) {
+               if (eof_error > 0) /* did NOT reach eof yet */
+                       rd = 0; /* yes, we are interested in stdin */
+       }
+       /* Position cursor if line input is done */
+       if (less_gets_pos >= 0)
+               move_cursor(max_displayed_line + 2, less_gets_pos + 1);
+       fflush(stdout);
+
+       if (kbd_input_size == 0) {
+#if ENABLE_FEATURE_LESS_WINCH
+               while (1) {
+                       int r;
+                       /* NB: SIGWINCH interrupts poll() */
+                       r = poll(pfd + rd, 2 - rd, -1);
+                       if (/*r < 0 && errno == EINTR &&*/ winch_counter)
+                               return '\\'; /* anything which has no defined function */
+                       if (r) break;
+               }
+#else
+               safe_poll(pfd + rd, 2 - rd, -1);
+#endif
+       }
+
+       /* We have kbd_fd in O_NONBLOCK mode, read inside read_key()
+        * would not block even if there is no input available */
+       rd = read_key(kbd_fd, &kbd_input_size, kbd_input);
+       if (rd == -1) {
+               if (errno == EAGAIN) {
+                       /* No keyboard input available. Since poll() did return,
+                        * we should have input on stdin */
+                       read_lines();
+                       buffer_fill_and_print();
+                       goto again;
+               }
+               /* EOF/error (ssh session got killed etc) */
+               less_exit(0);
+       }
+       set_tty_cooked();
+       return rd;
+}
+
+/* Grab a character from input without requiring the return key. If the
+ * character is ASCII \033, get more characters and assign certain sequences
+ * special return codes. Note that this function works best with raw input. */
+static int less_getch(int pos)
+{
+       int i;
+
+ again:
+       less_gets_pos = pos;
+       i = getch_nowait();
+       less_gets_pos = -1;
+
+       /* Discard Ctrl-something chars */
+       if (i >= 0 && i < ' ' && i != 0x0d && i != 8)
+               goto again;
+       return i;
+}
+
+static char* less_gets(int sz)
+{
+       int c;
+       unsigned i = 0;
+       char *result = xzalloc(1);
+
+       while (1) {
+               c = '\0';
+               less_gets_pos = sz + i;
+               c = getch_nowait();
+               if (c == 0x0d) {
+                       result[i] = '\0';
+                       less_gets_pos = -1;
+                       return result;
+               }
+               if (c == 0x7f)
+                       c = 8;
+               if (c == 8 && i) {
+                       printf("\x8 \x8");
+                       i--;
+               }
+               if (c < ' ') /* filters out KEYCODE_xxx too (<0) */
+                       continue;
+               if (i >= width - sz - 1)
+                       continue; /* len limit */
+               bb_putchar(c);
+               result[i++] = c;
+               result = xrealloc(result, i+1);
+       }
+}
+
+static void examine_file(void)
+{
+       char *new_fname;
+
+       print_statusline("Examine: ");
+       new_fname = less_gets(sizeof("Examine: ") - 1);
+       if (!new_fname[0]) {
+               status_print();
+ err:
+               free(new_fname);
+               return;
+       }
+       if (access(new_fname, R_OK) != 0) {
+               print_statusline("Cannot read this file");
+               goto err;
+       }
+       free(filename);
+       filename = new_fname;
+       /* files start by = argv. why we assume that argv is infinitely long??
+       files[num_files] = filename;
+       current_file = num_files + 1;
+       num_files++; */
+       files[0] = filename;
+       num_files = current_file = 1;
+       reinitialize();
+}
+
+/* This function changes the file currently being paged. direction can be one of the following:
+ * -1: go back one file
+ *  0: go to the first file
+ *  1: go forward one file */
+static void change_file(int direction)
+{
+       if (current_file != ((direction > 0) ? num_files : 1)) {
+               current_file = direction ? current_file + direction : 1;
+               free(filename);
+               filename = xstrdup(files[current_file - 1]);
+               reinitialize();
+       } else {
+               print_statusline(direction > 0 ? "No next file" : "No previous file");
+       }
+}
+
+static void remove_current_file(void)
+{
+       unsigned i;
+
+       if (num_files < 2)
+               return;
+
+       if (current_file != 1) {
+               change_file(-1);
+               for (i = 3; i <= num_files; i++)
+                       files[i - 2] = files[i - 1];
+               num_files--;
+       } else {
+               change_file(1);
+               for (i = 2; i <= num_files; i++)
+                       files[i - 2] = files[i - 1];
+               num_files--;
+               current_file--;
+       }
+}
+
+static void colon_process(void)
+{
+       int keypress;
+
+       /* Clear the current line and print a prompt */
+       print_statusline(" :");
+
+       keypress = less_getch(2);
+       switch (keypress) {
+       case 'd':
+               remove_current_file();
+               break;
+       case 'e':
+               examine_file();
+               break;
+#if ENABLE_FEATURE_LESS_FLAGS
+       case 'f':
+               m_status_print();
+               break;
+#endif
+       case 'n':
+               change_file(1);
+               break;
+       case 'p':
+               change_file(-1);
+               break;
+       case 'q':
+               less_exit(EXIT_SUCCESS);
+               break;
+       case 'x':
+               change_file(0);
+               break;
+       }
+}
+
+#if ENABLE_FEATURE_LESS_REGEXP
+static void normalize_match_pos(int match)
+{
+       if (match >= num_matches)
+               match = num_matches - 1;
+       if (match < 0)
+               match = 0;
+       match_pos = match;
+}
+
+static void goto_match(int match)
+{
+       if (!pattern_valid)
+               return;
+       if (match < 0)
+               match = 0;
+       /* Try to find next match if eof isn't reached yet */
+       if (match >= num_matches && eof_error > 0) {
+               wanted_match = match; /* "I want to read until I see N'th match" */
+               read_lines();
+       }
+       if (num_matches) {
+               normalize_match_pos(match);
+               buffer_line(match_lines[match_pos]);
+       } else {
+               print_statusline("No matches found");
+       }
+}
+
+static void fill_match_lines(unsigned pos)
+{
+       if (!pattern_valid)
+               return;
+       /* Run the regex on each line of the current file */
+       while (pos <= max_fline) {
+               /* If this line matches */
+               if (regexec(&pattern, flines[pos], 0, NULL, 0) == 0
+               /* and we didn't match it last time */
+                && !(num_matches && match_lines[num_matches-1] == pos)
+               ) {
+                       match_lines = xrealloc_vector(match_lines, 4, num_matches);
+                       match_lines[num_matches++] = pos;
+               }
+               pos++;
+       }
+}
+
+static void regex_process(void)
+{
+       char *uncomp_regex, *err;
+
+       /* Reset variables */
+       free(match_lines);
+       match_lines = NULL;
+       match_pos = 0;
+       num_matches = 0;
+       if (pattern_valid) {
+               regfree(&pattern);
+               pattern_valid = 0;
+       }
+
+       /* Get the uncompiled regular expression from the user */
+       clear_line();
+       bb_putchar((option_mask32 & LESS_STATE_MATCH_BACKWARDS) ? '?' : '/');
+       uncomp_regex = less_gets(1);
+       if (!uncomp_regex[0]) {
+               free(uncomp_regex);
+               buffer_print();
+               return;
+       }
+
+       /* Compile the regex and check for errors */
+       err = regcomp_or_errmsg(&pattern, uncomp_regex,
+                               (option_mask32 & FLAG_I) ? REG_ICASE : 0);
+       free(uncomp_regex);
+       if (err) {
+               print_statusline(err);
+               free(err);
+               return;
+       }
+
+       pattern_valid = 1;
+       match_pos = 0;
+       fill_match_lines(0);
+       while (match_pos < num_matches) {
+               if ((int)match_lines[match_pos] > cur_fline)
+                       break;
+               match_pos++;
+       }
+       if (option_mask32 & LESS_STATE_MATCH_BACKWARDS)
+               match_pos--;
+
+       /* It's possible that no matches are found yet.
+        * goto_match() will read input looking for match,
+        * if needed */
+       goto_match(match_pos);
+}
+#endif
+
+static void number_process(int first_digit)
+{
+       unsigned i;
+       int num;
+       int keypress;
+       char num_input[sizeof(int)*4]; /* more than enough */
+
+       num_input[0] = first_digit;
+
+       /* Clear the current line, print a prompt, and then print the digit */
+       clear_line();
+       printf(":%c", first_digit);
+
+       /* Receive input until a letter is given */
+       i = 1;
+       while (i < sizeof(num_input)-1) {
+               keypress = less_getch(i + 1);
+               if ((unsigned)keypress > 255 || !isdigit(num_input[i]))
+                       break;
+               num_input[i] = keypress;
+               bb_putchar(keypress);
+               i++;
+       }
+
+       num_input[i] = '\0';
+       num = bb_strtou(num_input, NULL, 10);
+       /* on format error, num == -1 */
+       if (num < 1 || num > MAXLINES) {
+               buffer_print();
+               return;
+       }
+
+       /* We now know the number and the letter entered, so we process them */
+       switch (keypress) {
+       case KEYCODE_DOWN: case 'z': case 'd': case 'e': case ' ': case '\015':
+               buffer_down(num);
+               break;
+       case KEYCODE_UP: case 'b': case 'w': case 'y': case 'u':
+               buffer_up(num);
+               break;
+       case 'g': case '<': case 'G': case '>':
+               cur_fline = num + max_displayed_line;
+               read_lines();
+               buffer_line(num - 1);
+               break;
+       case 'p': case '%':
+               num = num * (max_fline / 100); /* + max_fline / 2; */
+               cur_fline = num + max_displayed_line;
+               read_lines();
+               buffer_line(num);
+               break;
+#if ENABLE_FEATURE_LESS_REGEXP
+       case 'n':
+               goto_match(match_pos + num);
+               break;
+       case '/':
+               option_mask32 &= ~LESS_STATE_MATCH_BACKWARDS;
+               regex_process();
+               break;
+       case '?':
+               option_mask32 |= LESS_STATE_MATCH_BACKWARDS;
+               regex_process();
+               break;
+#endif
+       }
+}
+
+#if ENABLE_FEATURE_LESS_DASHCMD
+static void flag_change(void)
+{
+       int keypress;
+
+       clear_line();
+       bb_putchar('-');
+       keypress = less_getch(1);
+
+       switch (keypress) {
+       case 'M':
+               option_mask32 ^= FLAG_M;
+               break;
+       case 'm':
+               option_mask32 ^= FLAG_m;
+               break;
+       case 'E':
+               option_mask32 ^= FLAG_E;
+               break;
+       case '~':
+               option_mask32 ^= FLAG_TILDE;
+               break;
+       case 'S':
+               option_mask32 ^= FLAG_S;
+               buffer_fill_and_print();
+               break;
+#if ENABLE_FEATURE_LESS_LINENUMS
+       case 'N':
+               option_mask32 ^= FLAG_N;
+               re_wrap();
+               buffer_fill_and_print();
+               break;
+#endif
+       }
+}
+
+#ifdef BLOAT
+static void show_flag_status(void)
+{
+       int keypress;
+       int flag_val;
+
+       clear_line();
+       bb_putchar('_');
+       keypress = less_getch(1);
+
+       switch (keypress) {
+       case 'M':
+               flag_val = option_mask32 & FLAG_M;
+               break;
+       case 'm':
+               flag_val = option_mask32 & FLAG_m;
+               break;
+       case '~':
+               flag_val = option_mask32 & FLAG_TILDE;
+               break;
+       case 'N':
+               flag_val = option_mask32 & FLAG_N;
+               break;
+       case 'E':
+               flag_val = option_mask32 & FLAG_E;
+               break;
+       default:
+               flag_val = 0;
+               break;
+       }
+
+       clear_line();
+       printf(HIGHLIGHT"The status of the flag is: %u"NORMAL, flag_val != 0);
+}
+#endif
+
+#endif /* ENABLE_FEATURE_LESS_DASHCMD */
+
+static void save_input_to_file(void)
+{
+       const char *msg = "";
+       char *current_line;
+       unsigned i;
+       FILE *fp;
+
+       print_statusline("Log file: ");
+       current_line = less_gets(sizeof("Log file: ")-1);
+       if (current_line[0]) {
+               fp = fopen_for_write(current_line);
+               if (!fp) {
+                       msg = "Error opening log file";
+                       goto ret;
+               }
+               for (i = 0; i <= max_fline; i++)
+                       fprintf(fp, "%s\n", flines[i]);
+               fclose(fp);
+               msg = "Done";
+       }
+ ret:
+       print_statusline(msg);
+       free(current_line);
+}
+
+#if ENABLE_FEATURE_LESS_MARKS
+static void add_mark(void)
+{
+       int letter;
+
+       print_statusline("Mark: ");
+       letter = less_getch(sizeof("Mark: ") - 1);
+
+       if (isalpha(letter)) {
+               /* If we exceed 15 marks, start overwriting previous ones */
+               if (num_marks == 14)
+                       num_marks = 0;
+
+               mark_lines[num_marks][0] = letter;
+               mark_lines[num_marks][1] = cur_fline;
+               num_marks++;
+       } else {
+               print_statusline("Invalid mark letter");
+       }
+}
+
+static void goto_mark(void)
+{
+       int letter;
+       int i;
+
+       print_statusline("Go to mark: ");
+       letter = less_getch(sizeof("Go to mark: ") - 1);
+       clear_line();
+
+       if (isalpha(letter)) {
+               for (i = 0; i <= num_marks; i++)
+                       if (letter == mark_lines[i][0]) {
+                               buffer_line(mark_lines[i][1]);
+                               break;
+                       }
+               if (num_marks == 14 && letter != mark_lines[14][0])
+                       print_statusline("Mark not set");
+       } else
+               print_statusline("Invalid mark letter");
+}
+#endif
+
+#if ENABLE_FEATURE_LESS_BRACKETS
+static char opp_bracket(char bracket)
+{
+       switch (bracket) {
+               case '{': case '[': /* '}' == '{' + 2. Same for '[' */
+                       bracket++;
+               case '(':           /* ')' == '(' + 1 */
+                       bracket++;
+                       break;
+               case '}': case ']':
+                       bracket--;
+               case ')':
+                       bracket--;
+                       break;
+       };
+       return bracket;
+}
+
+static void match_right_bracket(char bracket)
+{
+       unsigned i;
+
+       if (strchr(flines[cur_fline], bracket) == NULL) {
+               print_statusline("No bracket in top line");
+               return;
+       }
+       bracket = opp_bracket(bracket);
+       for (i = cur_fline + 1; i < max_fline; i++) {
+               if (strchr(flines[i], bracket) != NULL) {
+                       buffer_line(i);
+                       return;
+               }
+       }
+       print_statusline("No matching bracket found");
+}
+
+static void match_left_bracket(char bracket)
+{
+       int i;
+
+       if (strchr(flines[cur_fline + max_displayed_line], bracket) == NULL) {
+               print_statusline("No bracket in bottom line");
+               return;
+       }
+
+       bracket = opp_bracket(bracket);
+       for (i = cur_fline + max_displayed_line; i >= 0; i--) {
+               if (strchr(flines[i], bracket) != NULL) {
+                       buffer_line(i);
+                       return;
+               }
+       }
+       print_statusline("No matching bracket found");
+}
+#endif  /* FEATURE_LESS_BRACKETS */
+
+static void keypress_process(int keypress)
+{
+       switch (keypress) {
+       case KEYCODE_DOWN: case 'e': case 'j': case 0x0d:
+               buffer_down(1);
+               break;
+       case KEYCODE_UP: case 'y': case 'k':
+               buffer_up(1);
+               break;
+       case KEYCODE_PAGEDOWN: case ' ': case 'z': case 'f':
+               buffer_down(max_displayed_line + 1);
+               break;
+       case KEYCODE_PAGEUP: case 'w': case 'b':
+               buffer_up(max_displayed_line + 1);
+               break;
+       case 'd':
+               buffer_down((max_displayed_line + 1) / 2);
+               break;
+       case 'u':
+               buffer_up((max_displayed_line + 1) / 2);
+               break;
+       case KEYCODE_HOME: case 'g': case 'p': case '<': case '%':
+               buffer_line(0);
+               break;
+       case KEYCODE_END: case 'G': case '>':
+               cur_fline = MAXLINES;
+               read_lines();
+               buffer_line(cur_fline);
+               break;
+       case 'q': case 'Q':
+               less_exit(EXIT_SUCCESS);
+               break;
+#if ENABLE_FEATURE_LESS_MARKS
+       case 'm':
+               add_mark();
+               buffer_print();
+               break;
+       case '\'':
+               goto_mark();
+               buffer_print();
+               break;
+#endif
+       case 'r': case 'R':
+               buffer_print();
+               break;
+       /*case 'R':
+               full_repaint();
+               break;*/
+       case 's':
+               save_input_to_file();
+               break;
+       case 'E':
+               examine_file();
+               break;
+#if ENABLE_FEATURE_LESS_FLAGS
+       case '=':
+               m_status_print();
+               break;
+#endif
+#if ENABLE_FEATURE_LESS_REGEXP
+       case '/':
+               option_mask32 &= ~LESS_STATE_MATCH_BACKWARDS;
+               regex_process();
+               break;
+       case 'n':
+               goto_match(match_pos + 1);
+               break;
+       case 'N':
+               goto_match(match_pos - 1);
+               break;
+       case '?':
+               option_mask32 |= LESS_STATE_MATCH_BACKWARDS;
+               regex_process();
+               break;
+#endif
+#if ENABLE_FEATURE_LESS_DASHCMD
+       case '-':
+               flag_change();
+               buffer_print();
+               break;
+#ifdef BLOAT
+       case '_':
+               show_flag_status();
+               break;
+#endif
+#endif
+#if ENABLE_FEATURE_LESS_BRACKETS
+       case '{': case '(': case '[':
+               match_right_bracket(keypress);
+               break;
+       case '}': case ')': case ']':
+               match_left_bracket(keypress);
+               break;
+#endif
+       case ':':
+               colon_process();
+               break;
+       }
+
+       if (isdigit(keypress))
+               number_process(keypress);
+}
+
+static void sig_catcher(int sig)
+{
+       less_exit(- sig);
+}
+
+#if ENABLE_FEATURE_LESS_WINCH
+static void sigwinch_handler(int sig UNUSED_PARAM)
+{
+       winch_counter++;
+}
+#endif
+
+int less_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int less_main(int argc, char **argv)
+{
+       int keypress;
+
+       INIT_G();
+
+       /* TODO: -x: do not interpret backspace, -xx: tab also */
+       /* -xxx: newline also */
+       /* -w N: assume width N (-xxx -w 32: hex viewer of sorts) */
+       getopt32(argv, "EMmN~I" USE_FEATURE_LESS_DASHCMD("S"));
+       argc -= optind;
+       argv += optind;
+       num_files = argc;
+       files = argv;
+
+       /* Another popular pager, most, detects when stdout
+        * is not a tty and turns into cat. This makes sense. */
+       if (!isatty(STDOUT_FILENO))
+               return bb_cat(argv);
+
+       if (!num_files) {
+               if (isatty(STDIN_FILENO)) {
+                       /* Just "less"? No args and no redirection? */
+                       bb_error_msg("missing filename");
+                       bb_show_usage();
+               }
+       } else {
+               filename = xstrdup(files[0]);
+       }
+
+       if (option_mask32 & FLAG_TILDE)
+               empty_line_marker = "";
+
+       kbd_fd = open(CURRENT_TTY, O_RDONLY);
+       if (kbd_fd < 0)
+               return bb_cat(argv);
+       ndelay_on(kbd_fd);
+
+       tcgetattr(kbd_fd, &term_orig);
+       term_less = term_orig;
+       term_less.c_lflag &= ~(ICANON | ECHO);
+       term_less.c_iflag &= ~(IXON | ICRNL);
+       /*term_less.c_oflag &= ~ONLCR;*/
+       term_less.c_cc[VMIN] = 1;
+       term_less.c_cc[VTIME] = 0;
+
+       get_terminal_width_height(kbd_fd, &width, &max_displayed_line);
+       /* 20: two tabstops + 4 */
+       if (width < 20 || max_displayed_line < 3)
+               return bb_cat(argv);
+       max_displayed_line -= 2;
+
+       /* We want to restore term_orig on exit */
+       bb_signals(BB_FATAL_SIGS, sig_catcher);
+#if ENABLE_FEATURE_LESS_WINCH
+       signal(SIGWINCH, sigwinch_handler);
+#endif
+
+       buffer = xmalloc((max_displayed_line+1) * sizeof(char *));
+       reinitialize();
+       while (1) {
+#if ENABLE_FEATURE_LESS_WINCH
+               while (WINCH_COUNTER) {
+ again:
+                       winch_counter--;
+                       get_terminal_width_height(kbd_fd, &width, &max_displayed_line);
+                       /* 20: two tabstops + 4 */
+                       if (width < 20)
+                               width = 20;
+                       if (max_displayed_line < 3)
+                               max_displayed_line = 3;
+                       max_displayed_line -= 2;
+                       free(buffer);
+                       buffer = xmalloc((max_displayed_line+1) * sizeof(char *));
+                       /* Avoid re-wrap and/or redraw if we already know
+                        * we need to do it again. These ops are expensive */
+                       if (WINCH_COUNTER)
+                               goto again;
+                       re_wrap();
+                       if (WINCH_COUNTER)
+                               goto again;
+                       buffer_fill_and_print();
+                       /* This took some time. Loop back and check,
+                        * were there another SIGWINCH? */
+               }
+#endif
+               keypress = less_getch(-1); /* -1: do not position cursor */
+               keypress_process(keypress);
+       }
+}
+
+/*
+Help text of less version 418 is below.
+If you are implementing something, keeping
+key and/or command line switch compatibility is a good idea:
+
+
+                   SUMMARY OF LESS COMMANDS
+
+      Commands marked with * may be preceded by a number, N.
+      Notes in parentheses indicate the behavior if N is given.
+  h  H                 Display this help.
+  q  :q  Q  :Q  ZZ     Exit.
+ ---------------------------------------------------------------------------
+                           MOVING
+  e  ^E  j  ^N  CR  *  Forward  one line   (or N lines).
+  y  ^Y  k  ^K  ^P  *  Backward one line   (or N lines).
+  f  ^F  ^V  SPACE  *  Forward  one window (or N lines).
+  b  ^B  ESC-v      *  Backward one window (or N lines).
+  z                 *  Forward  one window (and set window to N).
+  w                 *  Backward one window (and set window to N).
+  ESC-SPACE         *  Forward  one window, but don't stop at end-of-file.
+  d  ^D             *  Forward  one half-window (and set half-window to N).
+  u  ^U             *  Backward one half-window (and set half-window to N).
+  ESC-)  RightArrow *  Left  one half screen width (or N positions).
+  ESC-(  LeftArrow  *  Right one half screen width (or N positions).
+  F                    Forward forever; like "tail -f".
+  r  ^R  ^L            Repaint screen.
+  R                    Repaint screen, discarding buffered input.
+        ---------------------------------------------------
+        Default "window" is the screen height.
+        Default "half-window" is half of the screen height.
+ ---------------------------------------------------------------------------
+                          SEARCHING
+  /pattern          *  Search forward for (N-th) matching line.
+  ?pattern          *  Search backward for (N-th) matching line.
+  n                 *  Repeat previous search (for N-th occurrence).
+  N                 *  Repeat previous search in reverse direction.
+  ESC-n             *  Repeat previous search, spanning files.
+  ESC-N             *  Repeat previous search, reverse dir. & spanning files.
+  ESC-u                Undo (toggle) search highlighting.
+        ---------------------------------------------------
+        Search patterns may be modified by one or more of:
+        ^N or !  Search for NON-matching lines.
+        ^E or *  Search multiple files (pass thru END OF FILE).
+        ^F or @  Start search at FIRST file (for /) or last file (for ?).
+        ^K       Highlight matches, but don't move (KEEP position).
+        ^R       Don't use REGULAR EXPRESSIONS.
+ ---------------------------------------------------------------------------
+                           JUMPING
+  g  <  ESC-<       *  Go to first line in file (or line N).
+  G  >  ESC->       *  Go to last line in file (or line N).
+  p  %              *  Go to beginning of file (or N percent into file).
+  t                 *  Go to the (N-th) next tag.
+  T                 *  Go to the (N-th) previous tag.
+  {  (  [           *  Find close bracket } ) ].
+  }  )  ]           *  Find open bracket { ( [.
+  ESC-^F <c1> <c2>  *  Find close bracket <c2>.
+  ESC-^B <c1> <c2>  *  Find open bracket <c1>
+        ---------------------------------------------------
+        Each "find close bracket" command goes forward to the close bracket
+          matching the (N-th) open bracket in the top line.
+        Each "find open bracket" command goes backward to the open bracket
+          matching the (N-th) close bracket in the bottom line.
+  m<letter>            Mark the current position with <letter>.
+  '<letter>            Go to a previously marked position.
+  ''                   Go to the previous position.
+  ^X^X                 Same as '.
+        ---------------------------------------------------
+        A mark is any upper-case or lower-case letter.
+        Certain marks are predefined:
+             ^  means  beginning of the file
+             $  means  end of the file
+ ---------------------------------------------------------------------------
+                        CHANGING FILES
+  :e [file]            Examine a new file.
+  ^X^V                 Same as :e.
+  :n                *  Examine the (N-th) next file from the command line.
+  :p                *  Examine the (N-th) previous file from the command line.
+  :x                *  Examine the first (or N-th) file from the command line.
+  :d                   Delete the current file from the command line list.
+  =  ^G  :f            Print current file name.
+ ---------------------------------------------------------------------------
+                    MISCELLANEOUS COMMANDS
+  -<flag>              Toggle a command line option [see OPTIONS below].
+  --<name>             Toggle a command line option, by name.
+  _<flag>              Display the setting of a command line option.
+  __<name>             Display the setting of an option, by name.
+  +cmd                 Execute the less cmd each time a new file is examined.
+  !command             Execute the shell command with $SHELL.
+  |Xcommand            Pipe file between current pos & mark X to shell command.
+  v                    Edit the current file with $VISUAL or $EDITOR.
+  V                    Print version number of "less".
+ ---------------------------------------------------------------------------
+                           OPTIONS
+        Most options may be changed either on the command line,
+        or from within less by using the - or -- command.
+        Options may be given in one of two forms: either a single
+        character preceded by a -, or a name preceeded by --.
+  -?  ........  --help
+                  Display help (from command line).
+  -a  ........  --search-skip-screen
+                  Forward search skips current screen.
+  -b [N]  ....  --buffers=[N]
+                  Number of buffers.
+  -B  ........  --auto-buffers
+                  Don't automatically allocate buffers for pipes.
+  -c  ........  --clear-screen
+                  Repaint by clearing rather than scrolling.
+  -d  ........  --dumb
+                  Dumb terminal.
+  -D [xn.n]  .  --color=xn.n
+                  Set screen colors. (MS-DOS only)
+  -e  -E  ....  --quit-at-eof  --QUIT-AT-EOF
+                  Quit at end of file.
+  -f  ........  --force
+                  Force open non-regular files.
+  -F  ........  --quit-if-one-screen
+                  Quit if entire file fits on first screen.
+  -g  ........  --hilite-search
+                  Highlight only last match for searches.
+  -G  ........  --HILITE-SEARCH
+                  Don't highlight any matches for searches.
+  -h [N]  ....  --max-back-scroll=[N]
+                  Backward scroll limit.
+  -i  ........  --ignore-case
+                  Ignore case in searches that do not contain uppercase.
+  -I  ........  --IGNORE-CASE
+                  Ignore case in all searches.
+  -j [N]  ....  --jump-target=[N]
+                  Screen position of target lines.
+  -J  ........  --status-column
+                  Display a status column at left edge of screen.
+  -k [file]  .  --lesskey-file=[file]
+                  Use a lesskey file.
+  -L  ........  --no-lessopen
+                  Ignore the LESSOPEN environment variable.
+  -m  -M  ....  --long-prompt  --LONG-PROMPT
+                  Set prompt style.
+  -n  -N  ....  --line-numbers  --LINE-NUMBERS
+                  Don't use line numbers.
+  -o [file]  .  --log-file=[file]
+                  Copy to log file (standard input only).
+  -O [file]  .  --LOG-FILE=[file]
+                  Copy to log file (unconditionally overwrite).
+  -p [pattern]  --pattern=[pattern]
+                  Start at pattern (from command line).
+  -P [prompt]   --prompt=[prompt]
+                  Define new prompt.
+  -q  -Q  ....  --quiet  --QUIET  --silent --SILENT
+                  Quiet the terminal bell.
+  -r  -R  ....  --raw-control-chars  --RAW-CONTROL-CHARS
+                  Output "raw" control characters.
+  -s  ........  --squeeze-blank-lines
+                  Squeeze multiple blank lines.
+  -S  ........  --chop-long-lines
+                  Chop long lines.
+  -t [tag]  ..  --tag=[tag]
+                  Find a tag.
+  -T [tagsfile] --tag-file=[tagsfile]
+                  Use an alternate tags file.
+  -u  -U  ....  --underline-special  --UNDERLINE-SPECIAL
+                  Change handling of backspaces.
+  -V  ........  --version
+                  Display the version number of "less".
+  -w  ........  --hilite-unread
+                  Highlight first new line after forward-screen.
+  -W  ........  --HILITE-UNREAD
+                  Highlight first new line after any forward movement.
+  -x [N[,...]]  --tabs=[N[,...]]
+                  Set tab stops.
+  -X  ........  --no-init
+                  Don't use termcap init/deinit strings.
+                --no-keypad
+                  Don't use termcap keypad init/deinit strings.
+  -y [N]  ....  --max-forw-scroll=[N]
+                  Forward scroll limit.
+  -z [N]  ....  --window=[N]
+                  Set size of window.
+  -" [c[c]]  .  --quotes=[c[c]]
+                  Set shell quote characters.
+  -~  ........  --tilde
+                  Don't display tildes after end of file.
+  -# [N]  ....  --shift=[N]
+                  Horizontal scroll amount (0 = one half screen width)
+
+ ---------------------------------------------------------------------------
+                          LINE EDITING
+        These keys can be used to edit text being entered
+        on the "command line" at the bottom of the screen.
+ RightArrow                       ESC-l     Move cursor right one character.
+ LeftArrow                        ESC-h     Move cursor left one character.
+ CNTL-RightArrow  ESC-RightArrow  ESC-w     Move cursor right one word.
+ CNTL-LeftArrow   ESC-LeftArrow   ESC-b     Move cursor left one word.
+ HOME                             ESC-0     Move cursor to start of line.
+ END                              ESC-$     Move cursor to end of line.
+ BACKSPACE                                  Delete char to left of cursor.
+ DELETE                           ESC-x     Delete char under cursor.
+ CNTL-BACKSPACE   ESC-BACKSPACE             Delete word to left of cursor.
+ CNTL-DELETE      ESC-DELETE      ESC-X     Delete word under cursor.
+ CNTL-U           ESC (MS-DOS only)         Delete entire line.
+ UpArrow                          ESC-k     Retrieve previous command line.
+ DownArrow                        ESC-j     Retrieve next command line.
+ TAB                                        Complete filename & cycle.
+ SHIFT-TAB                        ESC-TAB   Complete filename & reverse cycle.
+ CNTL-L                                     Complete filename, list all.
+*/
diff --git a/miscutils/makedevs.c b/miscutils/makedevs.c
new file mode 100644 (file)
index 0000000..be08055
--- /dev/null
@@ -0,0 +1,209 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * public domain -- Dave 'Kill a Cop' Cinege <dcinege@psychosis.com>
+ *
+ * makedevs
+ * Make ranges of device files quickly.
+ * known bugs: can't deal with alpha ranges
+ */
+
+#include "libbb.h"
+
+#if ENABLE_FEATURE_MAKEDEVS_LEAF
+/*
+makedevs NAME TYPE MAJOR MINOR FIRST LAST [s]
+TYPEs:
+b       Block device
+c       Character device
+f       FIFO
+
+FIRST..LAST specify numbers appended to NAME.
+If 's' is the last argument, the base device is created as well.
+Examples:
+        makedevs /dev/ttyS c 4 66 2 63   ->  ttyS2-ttyS63
+        makedevs /dev/hda b 3 0 0 8 s    ->  hda,hda1-hda8
+*/
+int makedevs_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int makedevs_main(int argc, char **argv)
+{
+       mode_t mode;
+       char *basedev, *type, *nodname, *buf;
+       int Smajor, Sminor, S, E;
+
+       if (argc < 7 || argv[1][0] == '-')
+               bb_show_usage();
+
+       basedev = argv[1];
+       buf = xasprintf("%s%u", argv[1], (unsigned)-1);
+       type = argv[2];
+       Smajor = xatoi_u(argv[3]);
+       Sminor = xatoi_u(argv[4]);
+       S = xatoi_u(argv[5]);
+       E = xatoi_u(argv[6]);
+       nodname = argv[7] ? basedev : buf;
+
+       mode = 0660;
+       switch (type[0]) {
+       case 'c':
+               mode |= S_IFCHR;
+               break;
+       case 'b':
+               mode |= S_IFBLK;
+               break;
+       case 'f':
+               mode |= S_IFIFO;
+               break;
+       default:
+               bb_show_usage();
+       }
+
+       while (S <= E) {
+               sprintf(buf, "%s%u", basedev, S);
+
+               /* if mode != S_IFCHR and != S_IFBLK,
+                * third param in mknod() ignored */
+               if (mknod(nodname, mode, makedev(Smajor, Sminor)))
+                       bb_perror_msg("can't create %s", nodname);
+
+               /*if (nodname == basedev)*/ /* ex. /dev/hda - to /dev/hda1 ... */
+                       nodname = buf;
+               S++;
+               Sminor++;
+       }
+
+       return 0;
+}
+
+#elif ENABLE_FEATURE_MAKEDEVS_TABLE
+
+/* Licensed under the GPL v2 or later, see the file LICENSE in this tarball. */
+
+int makedevs_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int makedevs_main(int argc UNUSED_PARAM, char **argv)
+{
+       parser_t *parser;
+       char *line = (char *)"-";
+       int ret = EXIT_SUCCESS;
+
+       opt_complementary = "=1"; /* exactly one param */
+       getopt32(argv, "d:", &line);
+       argv += optind;
+
+       xchdir(*argv); /* ensure root dir exists */
+
+       umask(0);
+
+       printf("rootdir=%s\ntable=", *argv);
+       if (NOT_LONE_DASH(line)) {
+               printf("'%s'\n", line);
+       } else {
+               puts("<stdin>");
+       }
+
+       parser = config_open(line);
+       while (config_read(parser, &line, 1, 1, "# \t", PARSE_NORMAL)) {
+               int linenum;
+               char type;
+               unsigned mode = 0755;
+               unsigned major = 0;
+               unsigned minor = 0;
+               unsigned count = 0;
+               unsigned increment = 0;
+               unsigned start = 0;
+               char name[41];
+               char user[41];
+               char group[41];
+               char *full_name = name;
+               uid_t uid;
+               gid_t gid;
+
+               linenum = parser->lineno;
+
+               if ((2 > sscanf(line, "%40s %c %o %40s %40s %u %u %u %u %u",
+                                       name, &type, &mode, user, group,
+                                       &major, &minor, &start, &increment, &count))
+                || ((unsigned)(major | minor | start | count | increment) > 255)
+               ) {
+                       bb_error_msg("invalid line %d: '%s'", linenum, line);
+                       ret = EXIT_FAILURE;
+                       continue;
+               }
+
+               gid = (*group) ? get_ug_id(group, xgroup2gid) : getgid();
+               uid = (*user) ? get_ug_id(user, xuname2uid) : getuid();
+               /* We are already in the right root dir,
+                * so make absolute paths relative */
+               if ('/' == *full_name)
+                       full_name++;
+
+               if (type == 'd') {
+                       bb_make_directory(full_name, mode | S_IFDIR, FILEUTILS_RECUR);
+                       if (chown(full_name, uid, gid) == -1) {
+ chown_fail:
+                               bb_perror_msg("line %d: can't chown %s", linenum, full_name);
+                               ret = EXIT_FAILURE;
+                               continue;
+                       }
+                       if (chmod(full_name, mode) < 0) {
+ chmod_fail:
+                               bb_perror_msg("line %d: can't chmod %s", linenum, full_name);
+                               ret = EXIT_FAILURE;
+                               continue;
+                       }
+               } else if (type == 'f') {
+                       struct stat st;
+                       if ((stat(full_name, &st) < 0 || !S_ISREG(st.st_mode))) {
+                               bb_perror_msg("line %d: regular file '%s' does not exist", linenum, full_name);
+                               ret = EXIT_FAILURE;
+                               continue;
+                       }
+                       if (chown(full_name, uid, gid) < 0)
+                               goto chown_fail;
+                       if (chmod(full_name, mode) < 0)
+                               goto chmod_fail;
+               } else {
+                       dev_t rdev;
+                       unsigned i;
+                       char *full_name_inc;
+
+                       if (type == 'p') {
+                               mode |= S_IFIFO;
+                       } else if (type == 'c') {
+                               mode |= S_IFCHR;
+                       } else if (type == 'b') {
+                               mode |= S_IFBLK;
+                       } else {
+                               bb_error_msg("line %d: unsupported file type %c", linenum, type);
+                               ret = EXIT_FAILURE;
+                               continue;
+                       }
+
+                       full_name_inc = xmalloc(strlen(full_name) + sizeof(int)*3 + 2);
+                       if (count)
+                               count--;
+                       for (i = start; i <= start + count; i++) {
+                               sprintf(full_name_inc, count ? "%s%u" : "%s", full_name, i);
+                               rdev = makedev(major, minor + (i - start) * increment);
+                               if (mknod(full_name_inc, mode, rdev) < 0) {
+                                       bb_perror_msg("line %d: can't create node %s", linenum, full_name_inc);
+                                       ret = EXIT_FAILURE;
+                               } else if (chown(full_name_inc, uid, gid) < 0) {
+                                       bb_perror_msg("line %d: can't chown %s", linenum, full_name_inc);
+                                       ret = EXIT_FAILURE;
+                               } else if (chmod(full_name_inc, mode) < 0) {
+                                       bb_perror_msg("line %d: can't chmod %s", linenum, full_name_inc);
+                                       ret = EXIT_FAILURE;
+                               }
+                       }
+                       free(full_name_inc);
+               }
+       }
+       if (ENABLE_FEATURE_CLEAN_UP)
+               config_close(parser);
+
+       return ret;
+}
+
+#else
+# error makedevs configuration error, either leaf or table must be selected
+#endif
diff --git a/miscutils/man.c b/miscutils/man.c
new file mode 100644 (file)
index 0000000..672ddb1
--- /dev/null
@@ -0,0 +1,278 @@
+/* mini man implementation for busybox
+ * Copyright (C) 2008 Denys Vlasenko <vda.linux@googlemail.com>
+ * Licensed under GPLv2, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.h"
+
+enum {
+       OPT_a = 1, /* all */
+       OPT_w = 2, /* print path */
+};
+
+/* This is what I see on my desktop system being executed:
+
+(
+echo ".ll 12.4i"
+echo ".nr LL 12.4i"
+echo ".pl 1100i"
+gunzip -c '/usr/man/man1/bzip2.1.gz'
+echo ".\\\""
+echo ".pl \n(nlu+10"
+) | gtbl | nroff -Tlatin1 -mandoc | less
+
+*/
+
+#if ENABLE_FEATURE_SEAMLESS_LZMA
+#define Z_SUFFIX ".lzma"
+#elif ENABLE_FEATURE_SEAMLESS_BZ2
+#define Z_SUFFIX ".bz2"
+#elif ENABLE_FEATURE_SEAMLESS_GZ
+#define Z_SUFFIX ".gz"
+#else
+#define Z_SUFFIX ""
+#endif
+
+static int show_manpage(const char *pager, char *man_filename, int man, int level);
+
+static int run_pipe(const char *pager, char *man_filename, int man, int level)
+{
+       char *cmd;
+
+       /* Prevent man page link loops */
+       if (level > 10)
+               return 0;
+
+       if (access(man_filename, R_OK) != 0)
+               return 0;
+
+       if (option_mask32 & OPT_w) {
+               puts(man_filename);
+               return 1;
+       }
+
+       if (man) { /* man page, not cat page */
+               /* Is this a link to another manpage? */
+               /* The link has the following on the first line: */
+               /* ".so another_man_page" */
+
+               struct stat sb;
+               char *line;
+               char *linkname, *p;
+
+               /* On my system:
+                * man1/genhostid.1.gz: 203 bytes - smallest real manpage
+                * man2/path_resolution.2.gz: 114 bytes - largest link
+                */
+               xstat(man_filename, &sb);
+               if (sb.st_size > 300) /* err on the safe side */
+                       goto ordinary_manpage;
+
+               line = xmalloc_open_zipped_read_close(man_filename, NULL);
+               if (!line || strncmp(line, ".so ", 4) != 0) {
+                       free(line);
+                       goto ordinary_manpage;
+               }
+               /* Example: man2/path_resolution.2.gz contains
+                * ".so man7/path_resolution.7\n<junk>"
+                */
+               *strchrnul(line, '\n') = '\0';
+               linkname = skip_whitespace(&line[4]);
+
+               /* If link has no slashes, we just replace man page name.
+                * If link has slashes (however many), we go back *once*.
+                * ".so zzz/ggg/page.3" does NOT go back two levels. */
+               p = strrchr(man_filename, '/');
+               if (!p)
+                       goto ordinary_manpage;
+               *p = '\0';
+               if (strchr(linkname, '/')) {
+                       p = strrchr(man_filename, '/');
+                       if (!p)
+                               goto ordinary_manpage;
+                       *p = '\0';
+               }
+
+               /* Links do not have .gz extensions, even if manpage
+                * is compressed */
+               man_filename = xasprintf("%s/%s" Z_SUFFIX, man_filename, linkname);
+               free(line);
+               /* Note: we leak "new" man_filename string as well... */
+               if (show_manpage(pager, man_filename, man, level + 1))
+                       return 1;
+               /* else: show the link, it's better than nothing */
+       }
+
+ ordinary_manpage:
+       close(STDIN_FILENO);
+       open_zipped(man_filename); /* guaranteed to use fd 0 (STDIN_FILENO) */
+       /* "2>&1" is added so that nroff errors are shown in pager too.
+        * Otherwise it may show just empty screen */
+       cmd = xasprintf(
+               man ? "gtbl | nroff -Tlatin1 -mandoc 2>&1 | %s"
+                   : "%s",
+               pager);
+       system(cmd);
+       free(cmd);
+       return 1;
+}
+
+/* man_filename is of the form "/dir/dir/dir/name.s" Z_SUFFIX */
+static int show_manpage(const char *pager, char *man_filename, int man, int level)
+{
+#if ENABLE_FEATURE_SEAMLESS_LZMA
+       if (run_pipe(pager, man_filename, man, level))
+               return 1;
+#endif
+
+#if ENABLE_FEATURE_SEAMLESS_BZ2
+#if ENABLE_FEATURE_SEAMLESS_LZMA
+       strcpy(strrchr(man_filename, '.') + 1, "bz2");
+#endif
+       if (run_pipe(pager, man_filename, man, level))
+               return 1;
+#endif
+
+#if ENABLE_FEATURE_SEAMLESS_GZ
+#if ENABLE_FEATURE_SEAMLESS_LZMA || ENABLE_FEATURE_SEAMLESS_BZ2
+       strcpy(strrchr(man_filename, '.') + 1, "gz");
+#endif
+       if (run_pipe(pager, man_filename, man, level))
+               return 1;
+#endif
+
+#if ENABLE_FEATURE_SEAMLESS_LZMA || ENABLE_FEATURE_SEAMLESS_BZ2 || ENABLE_FEATURE_SEAMLESS_GZ
+       *strrchr(man_filename, '.') = '\0';
+#endif
+       if (run_pipe(pager, man_filename, man, level))
+               return 1;
+
+       return 0;
+}
+
+int man_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int man_main(int argc UNUSED_PARAM, char **argv)
+{
+       parser_t *parser;
+       const char *pager;
+       char **man_path_list;
+       char *sec_list;
+       char *cur_path, *cur_sect;
+       int count_mp, cur_mp;
+       int opt, not_found;
+       char *token[2];
+
+       opt_complementary = "-1"; /* at least one argument */
+       opt = getopt32(argv, "+aw");
+       argv += optind;
+
+       sec_list = xstrdup("1:2:3:4:5:6:7:8:9");
+       /* Last valid man_path_list[] is [0x10] */
+       count_mp = 0;
+       man_path_list = xzalloc(0x11 * sizeof(man_path_list[0]));
+       man_path_list[0] = getenv("MANPATH");
+       if (!man_path_list[0]) /* default, may be overridden by /etc/man.conf */
+               man_path_list[0] = (char*)"/usr/man";
+       else
+               count_mp++;
+       pager = getenv("MANPAGER");
+       if (!pager) {
+               pager = getenv("PAGER");
+               if (!pager)
+                       pager = "more";
+       }
+
+       /* Parse man.conf */
+       parser = config_open2("/etc/man.conf", fopen_for_read);
+       while (config_read(parser, token, 2, 0, "# \t", PARSE_NORMAL)) {
+               if (!token[1])
+                       continue;
+               if (strcmp("MANPATH", token[0]) == 0) {
+                       char *path = token[1];
+                       while (*path) {
+                               char *next_path;
+                               char **path_element;
+
+                               next_path = strchr(path, ':');
+                               if (next_path) {
+                                       *next_path = '\0';
+                                       if (next_path++ == path) /* "::"? */
+                                               goto next;
+                               }
+                               /* Do we already have path? */
+                               path_element = man_path_list;
+                               while (*path_element) {
+                                       if (strcmp(*path_element, path) == 0)
+                                               goto skip;
+                                       path_element++;
+                               }
+                               man_path_list = xrealloc_vector(man_path_list, 4, count_mp);
+                               man_path_list[count_mp] = xstrdup(path);
+                               count_mp++;
+                               /* man_path_list is NULL terminated */
+                               /*man_path_list[count_mp] = NULL; - xrealloc_vector did it */
+ skip:
+                               if (!next_path)
+                                       break;
+ next:
+                               path = next_path;
+                       }
+               }
+               if (strcmp("MANSECT", token[0]) == 0) {
+                       free(sec_list);
+                       sec_list = xstrdup(token[1]);
+               }
+       }
+       config_close(parser);
+
+       not_found = 0;
+       do { /* for each argv[] */
+               int found = 0;
+               cur_mp = 0;
+
+               if (strchr(*argv, '/')) {
+                       found = show_manpage(pager, *argv, /*man:*/ 1, 0);
+                       goto check_found;
+               }
+               while ((cur_path = man_path_list[cur_mp++]) != NULL) {
+                       /* for each MANPATH */
+                       cur_sect = sec_list;
+                       do { /* for each section */
+                               char *next_sect = strchrnul(cur_sect, ':');
+                               int sect_len = next_sect - cur_sect;
+                               char *man_filename;
+                               int cat0man1 = 0;
+
+                               /* Search for cat, then man page */
+                               while (cat0man1 < 2) {
+                                       int found_here;
+                                       man_filename = xasprintf("%s/%s%.*s/%s.%.*s" Z_SUFFIX,
+                                                       cur_path,
+                                                       "cat\0man" + (cat0man1 * 4),
+                                                       sect_len, cur_sect,
+                                                       *argv,
+                                                       sect_len, cur_sect);
+                                       found_here = show_manpage(pager, man_filename, cat0man1, 0);
+                                       found |= found_here;
+                                       cat0man1 += found_here + 1;
+                                       free(man_filename);
+                               }
+
+                               if (found && !(opt & OPT_a))
+                                       goto next_arg;
+                               cur_sect = next_sect;
+                               while (*cur_sect == ':')
+                                       cur_sect++;
+                       } while (*cur_sect);
+               }
+ check_found:
+               if (!found) {
+                       bb_error_msg("no manual entry for '%s'", *argv);
+                       not_found = 1;
+               }
+ next_arg:
+               argv++;
+       } while (*argv);
+
+       return not_found;
+}
diff --git a/miscutils/microcom.c b/miscutils/microcom.c
new file mode 100644 (file)
index 0000000..a322197
--- /dev/null
@@ -0,0 +1,171 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * bare bones 'talk to modem' program - similar to 'cu -l $device'
+ * inspired by mgetty's microcom
+ *
+ * Copyright (C) 2008 by Vladimir Dronnikov <dronnikov@gmail.com>
+ *
+ * Licensed under GPLv2, see file LICENSE in this tarball for details.
+ */
+#include "libbb.h"
+
+// set raw tty mode
+static void xget1(int fd, struct termios *t, struct termios *oldt)
+{
+       tcgetattr(fd, oldt);
+       *t = *oldt;
+       cfmakeraw(t);
+//     t->c_lflag &= ~(ISIG|ICANON|ECHO|IEXTEN);
+//     t->c_iflag &= ~(BRKINT|IXON|ICRNL);
+//     t->c_oflag &= ~(ONLCR);
+//     t->c_cc[VMIN]  = 1;
+//     t->c_cc[VTIME] = 0;
+}
+
+static int xset1(int fd, struct termios *tio, const char *device)
+{
+       int ret = tcsetattr(fd, TCSAFLUSH, tio);
+
+       if (ret) {
+               bb_perror_msg("can't tcsetattr for %s", device);
+       }
+       return ret;
+}
+
+int microcom_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int microcom_main(int argc UNUSED_PARAM, char **argv)
+{
+       int sfd;
+       int nfd;
+       struct pollfd pfd[2];
+       struct termios tio0, tiosfd, tio;
+       char *device_lock_file;
+       enum {
+               OPT_X = 1 << 0, // do not respect Ctrl-X, Ctrl-@
+               OPT_s = 1 << 1, // baudrate
+               OPT_d = 1 << 2, // wait for device response, ms
+               OPT_t = 1 << 3, // timeout, ms
+       };
+       speed_t speed = 9600;
+       int delay = -1;
+       int timeout = -1;
+       unsigned opts;
+
+       // fetch options
+       opt_complementary = "=1:s+:d+:t+"; // exactly one arg, numeric options
+       opts = getopt32(argv, "Xs:d:t:", &speed, &delay, &timeout);
+//     argc -= optind;
+       argv += optind;
+
+       // try to create lock file in /var/lock
+       device_lock_file = (char *)bb_basename(argv[0]);
+       device_lock_file = xasprintf("/var/lock/LCK..%s", device_lock_file);
+       sfd = open(device_lock_file, O_CREAT | O_WRONLY | O_TRUNC | O_EXCL, 0644);
+       if (sfd < 0) {
+               // device already locked -> bail out
+               if (errno == EEXIST)
+                       bb_perror_msg_and_die("can't create %s", device_lock_file);
+               // can't create lock -> don't care
+               if (ENABLE_FEATURE_CLEAN_UP)
+                       free(device_lock_file);
+               device_lock_file = NULL;
+       } else {
+               // %4d to make concurrent mgetty (if any) happy.
+               // Mgetty treats 4-bytes lock files as binary,
+               // not text, PID. Making 5+ char file. Brrr...
+               fdprintf(sfd, "%4d\n", getpid());
+               close(sfd);
+       }
+
+       // setup signals
+       bb_signals(0
+               + (1 << SIGHUP)
+               + (1 << SIGINT)
+               + (1 << SIGTERM)
+               + (1 << SIGPIPE)
+               , record_signo);
+
+       // error exit code if we fail to open the device
+       bb_got_signal = 1;
+
+       // open device
+       sfd = open_or_warn(argv[0], O_RDWR | O_NOCTTY | O_NONBLOCK);
+       if (sfd < 0)
+               goto done;
+       fcntl(sfd, F_SETFL, 0);
+
+       // put device to "raw mode"
+       xget1(sfd, &tio, &tiosfd);
+       // set device speed
+       cfsetspeed(&tio, tty_value_to_baud(speed));
+       if (xset1(sfd, &tio, argv[0]))
+               goto done;
+
+       // put stdin to "raw mode" (if stdin is a TTY),
+       // handle one character at a time
+       if (isatty(STDIN_FILENO)) {
+               xget1(STDIN_FILENO, &tio, &tio0);
+               if (xset1(STDIN_FILENO, &tio, "stdin"))
+                       goto done;
+       }
+
+       // main loop: check with poll(), then read/write bytes across
+       pfd[0].fd = sfd;
+       pfd[0].events = POLLIN;
+       pfd[1].fd = STDIN_FILENO;
+       pfd[1].events = POLLIN;
+
+       bb_got_signal = 0;
+       nfd = 2;
+       while (!bb_got_signal && safe_poll(pfd, nfd, timeout) > 0) {
+               if (nfd > 1 && pfd[1].revents) {
+                       char c;
+                       // read from stdin -> write to device
+                       if (safe_read(STDIN_FILENO, &c, 1) < 1) {
+                               // don't poll stdin anymore if we got EOF/error
+                               nfd--;
+                               goto skip_write;
+                       }
+                       // do we need special processing?
+                       if (!(opts & OPT_X)) {
+                               // ^@ sends Break
+                               if (VINTR == c) {
+                                       tcsendbreak(sfd, 0);
+                                       goto skip_write;
+                               }
+                               // ^X exits
+                               if (24 == c)
+                                       break;
+                       }
+                       write(sfd, &c, 1);
+                       if (delay >= 0)
+                               safe_poll(pfd, 1, delay);
+skip_write: ;
+               }
+               if (pfd[0].revents) {
+#define iobuf bb_common_bufsiz1
+                       ssize_t len;
+                       // read from device -> write to stdout
+                       len = safe_read(sfd, iobuf, sizeof(iobuf));
+                       if (len > 0)
+                               full_write(STDOUT_FILENO, iobuf, len);
+                       else {
+                               // EOF/error -> bail out
+                               bb_got_signal = SIGHUP;
+                               break;
+                       }
+               }
+       }
+
+       // restore device mode
+       tcsetattr(sfd, TCSAFLUSH, &tiosfd);
+
+       if (isatty(STDIN_FILENO))
+               tcsetattr(STDIN_FILENO, TCSAFLUSH, &tio0);
+
+done:
+       if (device_lock_file)
+               unlink(device_lock_file);
+
+       return bb_got_signal;
+}
diff --git a/miscutils/mountpoint.c b/miscutils/mountpoint.c
new file mode 100644 (file)
index 0000000..b541ce2
--- /dev/null
@@ -0,0 +1,72 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * mountpoint implementation for busybox
+ *
+ * Copyright (C) 2005 Bernhard Reutner-Fischer
+ *
+ * Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
+ *
+ * Based on sysvinit's mountpoint
+ */
+
+#include "libbb.h"
+
+int mountpoint_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int mountpoint_main(int argc UNUSED_PARAM, char **argv)
+{
+       struct stat st;
+       const char *msg;
+       char *arg;
+       int rc, opt;
+
+       opt_complementary = "=1"; /* must have one argument */
+       opt = getopt32(argv, "qdxn");
+#define OPT_q (1)
+#define OPT_d (2)
+#define OPT_x (4)
+#define OPT_n (8)
+       arg = argv[optind];
+       msg = "%s";
+
+       rc = (opt & OPT_x) ? stat(arg, &st) : lstat(arg, &st);
+       if (rc != 0)
+               goto err;
+
+       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;
+               }
+               errno = 0; /* make perror_msg work as error_msg */
+               msg = "%s: not a block device";
+               goto err;
+       }
+
+       errno = ENOTDIR;
+       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 is_mnt = (st_dev != st.st_dev) || (st_dev == st.st_dev && st_ino == st.st_ino);
+                       int is_not_mnt = (st_dev == st.st_dev) && (st_ino != st.st_ino);
+
+                       if (opt & OPT_d)
+                               printf("%u:%u\n", major(st_dev), minor(st_dev));
+                       if (opt & OPT_n)
+                               printf("%s %s\n", find_block_device(arg), arg);
+                       if (!(opt & (OPT_q | OPT_d | OPT_n)))
+                               printf("%s is %sa mountpoint\n", arg, is_not_mnt ? "not " : "");
+                       return is_not_mnt;
+               }
+               arg = p;
+               /* else: stat had set errno, just fall through */
+       }
+
+ err:
+       if (!(opt & OPT_q))
+               bb_perror_msg(msg, arg);
+       return EXIT_FAILURE;
+}
diff --git a/miscutils/mt.c b/miscutils/mt.c
new file mode 100644 (file)
index 0000000..586373d
--- /dev/null
@@ -0,0 +1,140 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.h"
+#include <sys/mtio.h>
+
+/* missing: eod/seod, stoptions, stwrthreshold, densities */
+static const short opcode_value[] = {
+       MTBSF,
+       MTBSFM,
+       MTBSR,
+       MTBSS,
+       MTCOMPRESSION,
+       MTEOM,
+       MTERASE,
+       MTFSF,
+       MTFSFM,
+       MTFSR,
+       MTFSS,
+       MTLOAD,
+       MTLOCK,
+       MTMKPART,
+       MTNOP,
+       MTOFFL,
+       MTOFFL,
+       MTRAS1,
+       MTRAS2,
+       MTRAS3,
+       MTRESET,
+       MTRETEN,
+       MTREW,
+       MTSEEK,
+       MTSETBLK,
+       MTSETDENSITY,
+       MTSETDRVBUFFER,
+       MTSETPART,
+       MTTELL,
+       MTWSM,
+       MTUNLOAD,
+       MTUNLOCK,
+       MTWEOF,
+       MTWEOF
+};
+
+static const char opcode_name[] ALIGN1 =
+       "bsf"             "\0"
+       "bsfm"            "\0"
+       "bsr"             "\0"
+       "bss"             "\0"
+       "datacompression" "\0"
+       "eom"             "\0"
+       "erase"           "\0"
+       "fsf"             "\0"
+       "fsfm"            "\0"
+       "fsr"             "\0"
+       "fss"             "\0"
+       "load"            "\0"
+       "lock"            "\0"
+       "mkpart"          "\0"
+       "nop"             "\0"
+       "offline"         "\0"
+       "rewoffline"      "\0"
+       "ras1"            "\0"
+       "ras2"            "\0"
+       "ras3"            "\0"
+       "reset"           "\0"
+       "retension"       "\0"
+       "rewind"          "\0"
+       "seek"            "\0"
+       "setblk"          "\0"
+       "setdensity"      "\0"
+       "drvbuffer"       "\0"
+       "setpart"         "\0"
+       "tell"            "\0"
+       "wset"            "\0"
+       "unload"          "\0"
+       "unlock"          "\0"
+       "eof"             "\0"
+       "weof"            "\0";
+
+int mt_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int mt_main(int argc UNUSED_PARAM, char **argv)
+{
+       const char *file = "/dev/tape";
+       struct mtop op;
+       struct mtpos position;
+       int fd, mode, idx;
+
+       if (!argv[1]) {
+               bb_show_usage();
+       }
+
+       if (strcmp(argv[1], "-f") == 0) {
+               if (!argv[2] || !argv[3])
+                       bb_show_usage();
+               file = argv[2];
+               argv += 2;
+       }
+
+       idx = index_in_strings(opcode_name, argv[1]);
+
+       if (idx < 0)
+               bb_error_msg_and_die("unrecognized opcode %s", argv[1]);
+
+       op.mt_op = opcode_value[idx];
+       if (argv[2])
+               op.mt_count = xatoi_u(argv[2]);
+       else
+               op.mt_count = 1;                /* One, not zero, right? */
+
+       switch (opcode_value[idx]) {
+               case MTWEOF:
+               case MTERASE:
+               case MTWSM:
+               case MTSETDRVBUFFER:
+                       mode = O_WRONLY;
+                       break;
+
+               default:
+                       mode = O_RDONLY;
+                       break;
+       }
+
+       fd = xopen(file, mode);
+
+       switch (opcode_value[idx]) {
+               case MTTELL:
+                       ioctl_or_perror_and_die(fd, MTIOCPOS, &position, "%s", file);
+                       printf("At block %d\n", (int) position.mt_blkno);
+                       break;
+
+               default:
+                       ioctl_or_perror_and_die(fd, MTIOCTOP, &op, "%s", file);
+                       break;
+       }
+
+       return EXIT_SUCCESS;
+}
diff --git a/miscutils/raidautorun.c b/miscutils/raidautorun.c
new file mode 100644 (file)
index 0000000..a2a852b
--- /dev/null
@@ -0,0 +1,25 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * raidautorun implementation for busybox
+ *
+ * Copyright (C) 2006 Bernhard Reutner-Fischer
+ *
+ * Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
+ *
+ */
+
+#include "libbb.h"
+
+#include <linux/major.h>
+#include <linux/raid/md_u.h>
+
+int raidautorun_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int raidautorun_main(int argc, char **argv)
+{
+       if (argc != 2)
+               bb_show_usage();
+
+       xioctl(xopen(argv[1], O_RDONLY), RAID_AUTORUN, NULL);
+
+       return EXIT_SUCCESS;
+}
diff --git a/miscutils/readahead.c b/miscutils/readahead.c
new file mode 100644 (file)
index 0000000..fb71ce8
--- /dev/null
@@ -0,0 +1,40 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * readahead implementation for busybox
+ *
+ * Preloads the given files in RAM, to reduce access time.
+ * Does this by calling the readahead(2) system call.
+ *
+ * Copyright (C) 2006  Michael Opdenacker <michael@free-electrons.com>
+ *
+ * Licensed under GPLv2 or later, see file License in this tarball for details.
+ */
+
+#include "libbb.h"
+
+int readahead_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int readahead_main(int argc, char **argv)
+{
+       int retval = EXIT_SUCCESS;
+
+       if (argc == 1) bb_show_usage();
+
+       while (*++argv) {
+               int fd = open_or_warn(*argv, O_RDONLY);
+               if (fd >= 0) {
+                       off_t len;
+                       int r;
+
+                       /* fdlength was reported to be unreliable - use seek */
+                       len = xlseek(fd, 0, SEEK_END);
+                       xlseek(fd, 0, SEEK_SET);
+                       r = readahead(fd, 0, len);
+                       close(fd);
+                       if (r >= 0)
+                               continue;
+               }
+               retval = EXIT_FAILURE;
+       }
+
+       return retval;
+}
diff --git a/miscutils/runlevel.c b/miscutils/runlevel.c
new file mode 100644 (file)
index 0000000..6e10d9c
--- /dev/null
@@ -0,0 +1,43 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * runlevel    Prints out the previous and the current runlevel.
+ *
+ * Version:    @(#)runlevel  1.20  16-Apr-1997  MvS
+ *
+ *             This file is part of the sysvinit suite,
+ *             Copyright 1991-1997 Miquel van Smoorenburg.
+ *
+ * Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
+ *
+ * initially busyboxified by Bernhard Reutner-Fischer
+ */
+
+#include <utmp.h>
+#include "libbb.h"
+
+int runlevel_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int runlevel_main(int argc, char **argv)
+{
+       struct utmp *ut;
+       char prev;
+
+       if (argc > 1) utmpname(argv[1]);
+
+       setutent();
+       while ((ut = getutent()) != NULL) {
+               if (ut->ut_type == RUN_LVL) {
+                       prev = ut->ut_pid / 256;
+                       if (prev == 0) prev = 'N';
+                       printf("%c %c\n", prev, ut->ut_pid % 256);
+                       if (ENABLE_FEATURE_CLEAN_UP)
+                               endutent();
+                       return 0;
+               }
+       }
+
+       puts("unknown");
+
+       if (ENABLE_FEATURE_CLEAN_UP)
+               endutent();
+       return 1;
+}
diff --git a/miscutils/rx.c b/miscutils/rx.c
new file mode 100644 (file)
index 0000000..94eb452
--- /dev/null
@@ -0,0 +1,254 @@
+/* vi: set sw=4 ts=4: */
+/*-------------------------------------------------------------------------
+ * Filename:      xmodem.c
+ * Copyright:     Copyright (C) 2001, Hewlett-Packard Company
+ * Author:        Christopher Hoover <ch@hpl.hp.com>
+ * Description:   xmodem functionality for uploading of kernels
+ *                and the like
+ * Created at:    Thu Dec 20 01:58:08 PST 2001
+ *-----------------------------------------------------------------------*/
+/*
+ * xmodem.c: xmodem functionality for uploading of kernels and
+ *            the like
+ *
+ * Copyright (C) 2001 Hewlett-Packard Laboratories
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ *
+ * This was originally written for blob and then adapted for busybox.
+ */
+
+#include "libbb.h"
+
+#define SOH 0x01
+#define STX 0x02
+#define EOT 0x04
+#define ACK 0x06
+#define NAK 0x15
+#define BS  0x08
+
+/*
+Cf:
+  http://www.textfiles.com/apple/xmodem
+  http://www.phys.washington.edu/~belonis/xmodem/docxmodem.txt
+  http://www.phys.washington.edu/~belonis/xmodem/docymodem.txt
+  http://www.phys.washington.edu/~belonis/xmodem/modmprot.col
+*/
+
+#define TIMEOUT 1
+#define TIMEOUT_LONG 10
+#define MAXERRORS 10
+
+#define read_fd  STDIN_FILENO
+#define write_fd STDOUT_FILENO
+
+static int read_byte(unsigned timeout)
+{
+       char buf[1];
+       int n;
+
+       alarm(timeout);
+       /* NOT safe_read! We want ALRM to interrupt us */
+       n = read(read_fd, buf, 1);
+       alarm(0);
+       if (n == 1)
+               return (unsigned char)buf[0];
+       return -1;
+}
+
+static int receive(/*int read_fd, */int file_fd)
+{
+       unsigned char blockBuf[1024];
+       unsigned errors = 0;
+       unsigned wantBlockNo = 1;
+       unsigned length = 0;
+       int do_crc = 1;
+       char nak = 'C';
+       unsigned timeout = TIMEOUT_LONG;
+
+       /* Flush pending input */
+       tcflush(read_fd, TCIFLUSH);
+
+       /* Ask for CRC; if we get errors, we will go with checksum */
+       full_write(write_fd, &nak, 1);
+
+       for (;;) {
+               int blockBegin;
+               int blockNo, blockNoOnesCompl;
+               int blockLength;
+               int cksum_crc;  /* cksum OR crc */
+               int expected;
+               int i,j;
+
+               blockBegin = read_byte(timeout);
+               if (blockBegin < 0)
+                       goto timeout;
+
+               timeout = TIMEOUT;
+               nak = NAK;
+
+               switch (blockBegin) {
+               case SOH:
+               case STX:
+                       break;
+
+               case EOT:
+                       nak = ACK;
+                       full_write(write_fd, &nak, 1);
+                       return length;
+
+               default:
+                       goto error;
+               }
+
+               /* block no */
+               blockNo = read_byte(TIMEOUT);
+               if (blockNo < 0)
+                       goto timeout;
+
+               /* block no one's compliment */
+               blockNoOnesCompl = read_byte(TIMEOUT);
+               if (blockNoOnesCompl < 0)
+                       goto timeout;
+
+               if (blockNo != (255 - blockNoOnesCompl)) {
+                       bb_error_msg("bad block ones compl");
+                       goto error;
+               }
+
+               blockLength = (blockBegin == SOH) ? 128 : 1024;
+
+               for (i = 0; i < blockLength; i++) {
+                       int cc = read_byte(TIMEOUT);
+                       if (cc < 0)
+                               goto timeout;
+                       blockBuf[i] = cc;
+               }
+
+               if (do_crc) {
+                       cksum_crc = read_byte(TIMEOUT);
+                       if (cksum_crc < 0)
+                               goto timeout;
+                       cksum_crc = (cksum_crc << 8) | read_byte(TIMEOUT);
+                       if (cksum_crc < 0)
+                               goto timeout;
+               } else {
+                       cksum_crc = read_byte(TIMEOUT);
+                       if (cksum_crc < 0)
+                               goto timeout;
+               }
+
+               if (blockNo == ((wantBlockNo - 1) & 0xff)) {
+                       /* a repeat of the last block is ok, just ignore it. */
+                       /* this also ignores the initial block 0 which is */
+                       /* meta data. */
+                       goto next;
+               }
+               if (blockNo != (wantBlockNo & 0xff)) {
+                       bb_error_msg("unexpected block no, 0x%08x, expecting 0x%08x", blockNo, wantBlockNo);
+                       goto error;
+               }
+
+               expected = 0;
+               if (do_crc) {
+                       for (i = 0; i < blockLength; i++) {
+                               expected = expected ^ blockBuf[i] << 8;
+                               for (j = 0; j < 8; j++) {
+                                       if (expected & 0x8000)
+                                               expected = expected << 1 ^ 0x1021;
+                                       else
+                                               expected = expected << 1;
+                               }
+                       }
+                       expected &= 0xffff;
+               } else {
+                       for (i = 0; i < blockLength; i++)
+                               expected += blockBuf[i];
+                       expected &= 0xff;
+               }
+               if (cksum_crc != expected) {
+                       bb_error_msg(do_crc ? "crc error, expected 0x%04x, got 0x%04x"
+                                          : "checksum error, expected 0x%02x, got 0x%02x",
+                                           expected, cksum_crc);
+                       goto error;
+               }
+
+               wantBlockNo++;
+               length += blockLength;
+
+               errno = 0;
+               if (full_write(file_fd, blockBuf, blockLength) != blockLength) {
+                       bb_perror_msg("can't write to file");
+                       goto fatal;
+               }
+ next:
+               errors = 0;
+               nak = ACK;
+               full_write(write_fd, &nak, 1);
+               continue;
+ error:
+ timeout:
+               errors++;
+               if (errors == MAXERRORS) {
+                       /* Abort */
+
+                       /* if were asking for crc, try again w/o crc */
+                       if (nak == 'C') {
+                               nak = NAK;
+                               errors = 0;
+                               do_crc = 0;
+                               goto timeout;
+                       }
+                       bb_error_msg("too many errors; giving up");
+ fatal:
+                       /* 5 CAN followed by 5 BS. Don't try too hard... */
+                       safe_write(write_fd, "\030\030\030\030\030\010\010\010\010\010", 10);
+                       return -1;
+               }
+
+               /* Flush pending input */
+               tcflush(read_fd, TCIFLUSH);
+
+               full_write(write_fd, &nak, 1);
+       } /* for (;;) */
+}
+
+static void sigalrm_handler(int UNUSED_PARAM signum)
+{
+}
+
+int rx_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int rx_main(int argc, char **argv)
+{
+       struct termios tty, orig_tty;
+       int termios_err;
+       int file_fd;
+       int n;
+
+       if (argc != 2)
+               bb_show_usage();
+
+       /* Disabled by vda:
+        * why we can't receive from stdin? Why we *require*
+        * controlling tty?? */
+       /*read_fd = xopen(CURRENT_TTY, O_RDWR);*/
+       file_fd = xopen(argv[1], O_RDWR|O_CREAT|O_TRUNC);
+
+       termios_err = tcgetattr(read_fd, &tty);
+       if (termios_err == 0) {
+               orig_tty = tty;
+               cfmakeraw(&tty);
+               tcsetattr(read_fd, TCSAFLUSH, &tty);
+       }
+
+       /* No SA_RESTART: we want ALRM to interrupt read() */
+       signal_no_SA_RESTART_empty_mask(SIGALRM, sigalrm_handler);
+
+       n = receive(file_fd);
+
+       if (termios_err == 0)
+               tcsetattr(read_fd, TCSAFLUSH, &orig_tty);
+       if (ENABLE_FEATURE_CLEAN_UP)
+               close(file_fd);
+       fflush_stdout_and_exit(n >= 0);
+}
diff --git a/miscutils/setsid.c b/miscutils/setsid.c
new file mode 100644 (file)
index 0000000..d7de1f1
--- /dev/null
@@ -0,0 +1,36 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * setsid.c -- execute a command in a new session
+ * Rick Sladkey <jrs@world.std.com>
+ * In the public domain.
+ *
+ * 1999-02-22 Arkadiusz Mickiewicz <misiek@pld.ORG.PL>
+ * - added Native Language Support
+ *
+ * 2001-01-18 John Fremlin <vii@penguinpowered.com>
+ * - fork in case we are process group leader
+ *
+ * 2004-11-12 Paul Fox
+ * - busyboxed
+ */
+
+#include "libbb.h"
+
+int setsid_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int setsid_main(int argc UNUSED_PARAM, char **argv)
+{
+       if (!argv[1])
+               bb_show_usage();
+
+       /* setsid() is allowed only when we are not a process group leader.
+        * Otherwise our PID serves as PGID of some existing process group
+        * and cannot be used as PGID of a new process group. */
+       if (getpgrp() == getpid())
+               if (fork_or_rexec(argv))
+                       exit(EXIT_SUCCESS); /* parent */
+
+       setsid();  /* no error possible */
+
+       BB_EXECVP(argv[1], argv + 1);
+       bb_simple_perror_msg_and_die(argv[1]);
+}
diff --git a/miscutils/strings.c b/miscutils/strings.c
new file mode 100644 (file)
index 0000000..fea9edb
--- /dev/null
@@ -0,0 +1,83 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * strings implementation for busybox
+ *
+ * Copyright 2003 Tito Ragusa <farmatito@tiscali.it>
+ *
+ * Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
+ */
+
+#include "libbb.h"
+
+#define WHOLE_FILE             1
+#define PRINT_NAME             2
+#define PRINT_OFFSET   4
+#define SIZE                   8
+
+int strings_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int strings_main(int argc UNUSED_PARAM, char **argv)
+{
+       int n, c, status = EXIT_SUCCESS;
+       unsigned count;
+       off_t offset;
+       FILE *file;
+       char *string;
+       const char *fmt = "%s: ";
+       const char *n_arg = "4";
+
+       getopt32(argv, "afon:", &n_arg);
+       /* -a is our default behaviour */
+       /*argc -= optind;*/
+       argv += optind;
+
+       n = xatou_range(n_arg, 1, INT_MAX);
+       string = xzalloc(n + 1);
+       n--;
+
+       if (!*argv) {
+               fmt = "{%s}: ";
+               *--argv = (char *)bb_msg_standard_input;
+       }
+
+       do {
+               file = fopen_or_warn_stdin(*argv);
+               if (!file) {
+                       status = EXIT_FAILURE;
+                       continue;
+               }
+               offset = 0;
+               count = 0;
+               do {
+                       c = fgetc(file);
+                       if (isprint(c) || c == '\t') {
+                               if (count > n) {
+                                       bb_putchar(c);
+                               } else {
+                                       string[count] = c;
+                                       if (count == n) {
+                                               if (option_mask32 & PRINT_NAME) {
+                                                       printf(fmt, *argv);
+                                               }
+                                               if (option_mask32 & PRINT_OFFSET) {
+                                                       printf("%7"OFF_FMT"o ", offset - n);
+                                               }
+                                               fputs(string, stdout);
+                                       }
+                                       count++;
+                               }
+                       } else {
+                               if (count > n) {
+                                       bb_putchar('\n');
+                               }
+                               count = 0;
+                       }
+                       offset++;
+               } while (c != EOF);
+               fclose_if_not_stdin(file);
+       } while (*++argv);
+
+       if (ENABLE_FEATURE_CLEAN_UP)
+               free(string);
+
+       fflush_stdout_and_exit(status);
+}
diff --git a/miscutils/taskset.c b/miscutils/taskset.c
new file mode 100644 (file)
index 0000000..a0bbf0a
--- /dev/null
@@ -0,0 +1,137 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * taskset - retrieve or set a processes' CPU affinity
+ * Copyright (c) 2006 Bernhard Reutner-Fischer
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+#include <sched.h>
+#include "libbb.h"
+
+#if ENABLE_FEATURE_TASKSET_FANCY
+#define TASKSET_PRINTF_MASK "%s"
+/* craft a string from the mask */
+static char *from_cpuset(cpu_set_t *mask)
+{
+       int i;
+       char *ret = NULL;
+       char *str = xzalloc((CPU_SETSIZE / 4) + 1); /* we will leak it */
+
+       for (i = CPU_SETSIZE - 4; i >= 0; i -= 4) {
+               int val = 0;
+               int off;
+               for (off = 0; off <= 3; ++off)
+                       if (CPU_ISSET(i + off, mask))
+                               val |= 1 << off;
+               if (!ret && val)
+                       ret = str;
+               *str++ = bb_hexdigits_upcase[val] | 0x20;
+       }
+       return ret;
+}
+#else
+#define TASKSET_PRINTF_MASK "%llx"
+static unsigned long long from_cpuset(cpu_set_t *mask)
+{
+       struct BUG_CPU_SETSIZE_is_too_small {
+               char BUG_CPU_SETSIZE_is_too_small[
+                       CPU_SETSIZE < sizeof(int) ? -1 : 1];
+       };
+       char *p = (void*)mask;
+
+       /* Take the least significant bits. Careful!
+        * Consider both CPU_SETSIZE=4 and CPU_SETSIZE=1024 cases
+        */
+#if BB_BIG_ENDIAN
+       /* For big endian, it means LAST bits */
+       if (CPU_SETSIZE < sizeof(long))
+               p += CPU_SETSIZE - sizeof(int);
+       else if (CPU_SETSIZE < sizeof(long long))
+               p += CPU_SETSIZE - sizeof(long);
+       else
+               p += CPU_SETSIZE - sizeof(long long);
+#endif
+       if (CPU_SETSIZE < sizeof(long))
+               return *(unsigned*)p;
+       if (CPU_SETSIZE < sizeof(long long))
+               return *(unsigned long*)p;
+       return *(unsigned long long*)p;
+}
+#endif
+
+
+int taskset_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int taskset_main(int argc UNUSED_PARAM, char **argv)
+{
+       cpu_set_t mask;
+       pid_t pid = 0;
+       unsigned opt_p;
+       const char *current_new;
+       char *pid_str;
+       char *aff = aff; /* for compiler */
+
+       /* NB: we mimic util-linux's taskset: -p does not take
+        * an argument, i.e., "-pN" is NOT valid, only "-p N"!
+        * Indeed, util-linux-2.13-pre7 uses:
+        * getopt_long(argc, argv, "+pchV", ...), not "...p:..." */
+
+       opt_complementary = "-1"; /* at least 1 arg */
+       opt_p = getopt32(argv, "+p");
+       argv += optind;
+
+       if (opt_p) {
+               pid_str = *argv++;
+               if (*argv) { /* "-p <aff> <pid> ...rest.is.ignored..." */
+                       aff = pid_str;
+                       pid_str = *argv; /* NB: *argv != NULL in this case */
+               }
+               /* else it was just "-p <pid>", and *argv == NULL */
+               pid = xatoul_range(pid_str, 1, ((unsigned)(pid_t)ULONG_MAX) >> 1);
+       } else {
+               aff = *argv++; /* <aff> <cmd...> */
+               if (!*argv)
+                       bb_show_usage();
+       }
+
+       current_new = "current\0new";
+       if (opt_p) {
+ print_aff:
+               if (sched_getaffinity(pid, sizeof(mask), &mask) < 0)
+                       bb_perror_msg_and_die("can't %cet pid %d's affinity", 'g', pid);
+               printf("pid %d's %s affinity mask: "TASKSET_PRINTF_MASK"\n",
+                               pid, current_new, from_cpuset(&mask));
+               if (!*argv) {
+                       /* Either it was just "-p <pid>",
+                        * or it was "-p <aff> <pid>" and we came here
+                        * for the second time (see goto below) */
+                       return EXIT_SUCCESS;
+               }
+               *argv = NULL;
+               current_new += 8; /* "new" */
+       }
+
+       { /* Affinity was specified, translate it into cpu_set_t */
+               unsigned i;
+               /* Do not allow zero mask: */
+               unsigned long long m = xstrtoull_range(aff, 0, 1, ULLONG_MAX);
+               enum { CNT_BIT = CPU_SETSIZE < sizeof(m)*8 ? CPU_SETSIZE : sizeof(m)*8 };
+
+               CPU_ZERO(&mask);
+               for (i = 0; i < CNT_BIT; i++) {
+                       unsigned long long bit = (1ULL << i);
+                       if (bit & m)
+                               CPU_SET(i, &mask);
+               }
+       }
+
+       /* Set pid's or our own (pid==0) affinity */
+       if (sched_setaffinity(pid, sizeof(mask), &mask))
+               bb_perror_msg_and_die("can't %cet pid %d's affinity", 's', pid);
+
+       if (!*argv) /* "-p <aff> <pid> [...ignored...]" */
+               goto print_aff; /* print new affinity and exit */
+
+       BB_EXECVP(*argv, argv);
+       bb_simple_perror_msg_and_die(*argv);
+}
diff --git a/miscutils/time.c b/miscutils/time.c
new file mode 100644 (file)
index 0000000..30298fe
--- /dev/null
@@ -0,0 +1,429 @@
+/* vi: set sw=4 ts=4: */
+/* 'time' utility to display resource usage of processes.
+   Copyright (C) 1990, 91, 92, 93, 96 Free Software Foundation, Inc.
+
+   Licensed under GPL version 2, see file LICENSE in this tarball for details.
+*/
+/* Originally written by David Keppel <pardo@cs.washington.edu>.
+   Heavily modified by David MacKenzie <djm@gnu.ai.mit.edu>.
+   Heavily modified for busybox by Erik Andersen <andersen@codepoet.org>
+*/
+
+#include "libbb.h"
+
+/* Information on the resources used by a child process.  */
+typedef struct {
+       int waitstatus;
+       struct rusage ru;
+       unsigned elapsed_ms;    /* Wallclock time of process.  */
+} resource_t;
+
+/* msec = milliseconds = 1/1,000 (1*10e-3) second.
+   usec = microseconds = 1/1,000,000 (1*10e-6) second.  */
+
+#define UL unsigned long
+
+static const char default_format[] ALIGN1 = "real\t%E\nuser\t%u\nsys\t%T";
+
+/* The output format for the -p option .*/
+static const char posix_format[] ALIGN1 = "real %e\nuser %U\nsys %S";
+
+/* Format string for printing all statistics verbosely.
+   Keep this output to 24 lines so users on terminals can see it all.*/
+static const char long_format[] ALIGN1 =
+       "\tCommand being timed: \"%C\"\n"
+       "\tUser time (seconds): %U\n"
+       "\tSystem time (seconds): %S\n"
+       "\tPercent of CPU this job got: %P\n"
+       "\tElapsed (wall clock) time (h:mm:ss or m:ss): %E\n"
+       "\tAverage shared text size (kbytes): %X\n"
+       "\tAverage unshared data size (kbytes): %D\n"
+       "\tAverage stack size (kbytes): %p\n"
+       "\tAverage total size (kbytes): %K\n"
+       "\tMaximum resident set size (kbytes): %M\n"
+       "\tAverage resident set size (kbytes): %t\n"
+       "\tMajor (requiring I/O) page faults: %F\n"
+       "\tMinor (reclaiming a frame) page faults: %R\n"
+       "\tVoluntary context switches: %w\n"
+       "\tInvoluntary context switches: %c\n"
+       "\tSwaps: %W\n"
+       "\tFile system inputs: %I\n"
+       "\tFile system outputs: %O\n"
+       "\tSocket messages sent: %s\n"
+       "\tSocket messages received: %r\n"
+       "\tSignals delivered: %k\n"
+       "\tPage size (bytes): %Z\n"
+       "\tExit status: %x";
+
+/* Wait for and fill in data on child process PID.
+   Return 0 on error, 1 if ok.  */
+/* pid_t is short on BSDI, so don't try to promote it.  */
+static void resuse_end(pid_t pid, resource_t *resp)
+{
+       pid_t caught;
+
+       /* Ignore signals, but don't ignore the children.  When wait3
+          returns the child process, set the time the command finished. */
+       while ((caught = wait3(&resp->waitstatus, 0, &resp->ru)) != pid) {
+               if (caught == -1 && errno != EINTR) {
+                       bb_perror_msg("wait");
+                       return;
+               }
+       }
+       resp->elapsed_ms = (monotonic_us() / 1000) - resp->elapsed_ms;
+}
+
+static void printargv(char *const *argv)
+{
+       const char *fmt = " %s" + 1;
+       do {
+               printf(fmt, *argv);
+               fmt = " %s";
+       } while (*++argv);
+}
+
+/* Return the number of kilobytes corresponding to a number of pages PAGES.
+   (Actually, we use it to convert pages*ticks into kilobytes*ticks.)
+
+   Try to do arithmetic so that the risk of overflow errors is minimized.
+   This is funky since the pagesize could be less than 1K.
+   Note: Some machines express getrusage statistics in terms of K,
+   others in terms of pages.  */
+static unsigned long ptok(const unsigned pagesize, const unsigned long pages)
+{
+       unsigned long tmp;
+
+       /* Conversion.  */
+       if (pages > (LONG_MAX / pagesize)) { /* Could overflow.  */
+               tmp = pages / 1024;     /* Smaller first, */
+               return tmp * pagesize;  /* then larger.  */
+       }
+       /* Could underflow.  */
+       tmp = pages * pagesize; /* Larger first, */
+       return tmp / 1024;      /* then smaller.  */
+}
+
+/* summarize: Report on the system use of a command.
+
+   Print the FMT argument except that `%' sequences
+   have special meaning, and `\n' and `\t' are translated into
+   newline and tab, respectively, and `\\' is translated into `\'.
+
+   The character following a `%' can be:
+   (* means the tcsh time builtin also recognizes it)
+   % == a literal `%'
+   C == command name and arguments
+*  D == average unshared data size in K (ru_idrss+ru_isrss)
+*  E == elapsed real (wall clock) time in [hour:]min:sec
+*  F == major page faults (required physical I/O) (ru_majflt)
+*  I == file system inputs (ru_inblock)
+*  K == average total mem usage (ru_idrss+ru_isrss+ru_ixrss)
+*  M == maximum resident set size in K (ru_maxrss)
+*  O == file system outputs (ru_oublock)
+*  P == percent of CPU this job got (total cpu time / elapsed time)
+*  R == minor page faults (reclaims; no physical I/O involved) (ru_minflt)
+*  S == system (kernel) time (seconds) (ru_stime)
+*  T == system time in [hour:]min:sec
+*  U == user time (seconds) (ru_utime)
+*  u == user time in [hour:]min:sec
+*  W == times swapped out (ru_nswap)
+*  X == average amount of shared text in K (ru_ixrss)
+   Z == page size
+*  c == involuntary context switches (ru_nivcsw)
+   e == elapsed real time in seconds
+*  k == signals delivered (ru_nsignals)
+   p == average unshared stack size in K (ru_isrss)
+*  r == socket messages received (ru_msgrcv)
+*  s == socket messages sent (ru_msgsnd)
+   t == average resident set size in K (ru_idrss)
+*  w == voluntary context switches (ru_nvcsw)
+   x == exit status of command
+
+   Various memory usages are found by converting from page-seconds
+   to kbytes by multiplying by the page size, dividing by 1024,
+   and dividing by elapsed real time.
+
+   FMT is the format string, interpreted as described above.
+   COMMAND is the command and args that are being summarized.
+   RESP is resource information on the command.  */
+
+#ifndef TICKS_PER_SEC
+#define TICKS_PER_SEC 100
+#endif
+
+static void summarize(const char *fmt, char **command, resource_t *resp)
+{
+       unsigned vv_ms;     /* Elapsed virtual (CPU) milliseconds */
+       unsigned cpu_ticks; /* Same, in "CPU ticks" */
+       unsigned pagesize = getpagesize();
+
+       /* Impossible: we do not use WUNTRACED flag in wait()...
+       if (WIFSTOPPED(resp->waitstatus))
+               printf("Command stopped by signal %u\n",
+                               WSTOPSIG(resp->waitstatus));
+       else */
+       if (WIFSIGNALED(resp->waitstatus))
+               printf("Command terminated by signal %u\n",
+                               WTERMSIG(resp->waitstatus));
+       else if (WIFEXITED(resp->waitstatus) && WEXITSTATUS(resp->waitstatus))
+               printf("Command exited with non-zero status %u\n",
+                               WEXITSTATUS(resp->waitstatus));
+
+       vv_ms = (resp->ru.ru_utime.tv_sec + resp->ru.ru_stime.tv_sec) * 1000
+             + (resp->ru.ru_utime.tv_usec + resp->ru.ru_stime.tv_usec) / 1000;
+
+#if (1000 / TICKS_PER_SEC) * TICKS_PER_SEC == 1000
+       /* 1000 is exactly divisible by TICKS_PER_SEC (typical) */
+       cpu_ticks = vv_ms / (1000 / TICKS_PER_SEC);
+#else
+       cpu_ticks = vv_ms * (unsigned long long)TICKS_PER_SEC / 1000;
+#endif
+       if (!cpu_ticks) cpu_ticks = 1; /* we divide by it, must be nonzero */
+
+       while (*fmt) {
+               /* Handle leading literal part */
+               int n = strcspn(fmt, "%\\");
+               if (n) {
+                       printf("%.*s", n, fmt);
+                       fmt += n;
+                       continue;
+               }
+
+               switch (*fmt) {
+#ifdef NOT_NEEDED
+               /* Handle literal char */
+               /* Usually we optimize for size, but there is a limit
+                * for everything. With this we do a lot of 1-byte writes */
+               default:
+                       bb_putchar(*fmt);
+                       break;
+#endif
+
+               case '%':
+                       switch (*++fmt) {
+#ifdef NOT_NEEDED_YET
+               /* Our format strings do not have these */
+               /* and we do not take format str from user */
+                       default:
+                               bb_putchar('%');
+                               /*FALLTHROUGH*/
+                       case '%':
+                               if (!*fmt) goto ret;
+                               bb_putchar(*fmt);
+                               break;
+#endif
+                       case 'C':       /* The command that got timed.  */
+                               printargv(command);
+                               break;
+                       case 'D':       /* Average unshared data size.  */
+                               printf("%lu",
+                                       (ptok(pagesize, (UL) resp->ru.ru_idrss) +
+                                        ptok(pagesize, (UL) resp->ru.ru_isrss)) / cpu_ticks);
+                               break;
+                       case 'E': {     /* Elapsed real (wall clock) time.  */
+                               unsigned seconds = resp->elapsed_ms / 1000;
+                               if (seconds >= 3600)    /* One hour -> h:m:s.  */
+                                       printf("%uh %um %02us",
+                                                       seconds / 3600,
+                                                       (seconds % 3600) / 60,
+                                                       seconds % 60);
+                               else
+                                       printf("%um %u.%02us",  /* -> m:s.  */
+                                                       seconds / 60,
+                                                       seconds % 60,
+                                                       (unsigned)(resp->elapsed_ms / 10) % 100);
+                               break;
+                       }
+                       case 'F':       /* Major page faults.  */
+                               printf("%lu", resp->ru.ru_majflt);
+                               break;
+                       case 'I':       /* Inputs.  */
+                               printf("%lu", resp->ru.ru_inblock);
+                               break;
+                       case 'K':       /* Average mem usage == data+stack+text.  */
+                               printf("%lu",
+                                       (ptok(pagesize, (UL) resp->ru.ru_idrss) +
+                                        ptok(pagesize, (UL) resp->ru.ru_isrss) +
+                                        ptok(pagesize, (UL) resp->ru.ru_ixrss)) / cpu_ticks);
+                               break;
+                       case 'M':       /* Maximum resident set size.  */
+                               printf("%lu", ptok(pagesize, (UL) resp->ru.ru_maxrss));
+                               break;
+                       case 'O':       /* Outputs.  */
+                               printf("%lu", resp->ru.ru_oublock);
+                               break;
+                       case 'P':       /* Percent of CPU this job got.  */
+                               /* % cpu is (total cpu time)/(elapsed time).  */
+                               if (resp->elapsed_ms > 0)
+                                       printf("%u%%", (unsigned)(vv_ms * 100 / resp->elapsed_ms));
+                               else
+                                       printf("?%%");
+                               break;
+                       case 'R':       /* Minor page faults (reclaims).  */
+                               printf("%lu", resp->ru.ru_minflt);
+                               break;
+                       case 'S':       /* System time.  */
+                               printf("%u.%02u",
+                                               (unsigned)resp->ru.ru_stime.tv_sec,
+                                               (unsigned)(resp->ru.ru_stime.tv_usec / 10000));
+                               break;
+                       case 'T':       /* System time.  */
+                               if (resp->ru.ru_stime.tv_sec >= 3600) /* One hour -> h:m:s.  */
+                                       printf("%uh %um %02us",
+                                                       (unsigned)(resp->ru.ru_stime.tv_sec / 3600),
+                                                       (unsigned)(resp->ru.ru_stime.tv_sec % 3600) / 60,
+                                                       (unsigned)(resp->ru.ru_stime.tv_sec % 60));
+                               else
+                                       printf("%um %u.%02us",  /* -> m:s.  */
+                                                       (unsigned)(resp->ru.ru_stime.tv_sec / 60),
+                                                       (unsigned)(resp->ru.ru_stime.tv_sec % 60),
+                                                       (unsigned)(resp->ru.ru_stime.tv_usec / 10000));
+                               break;
+                       case 'U':       /* User time.  */
+                               printf("%u.%02u",
+                                               (unsigned)resp->ru.ru_utime.tv_sec,
+                                               (unsigned)(resp->ru.ru_utime.tv_usec / 10000));
+                               break;
+                       case 'u':       /* User time.  */
+                               if (resp->ru.ru_utime.tv_sec >= 3600) /* One hour -> h:m:s.  */
+                                       printf("%uh %um %02us",
+                                                       (unsigned)(resp->ru.ru_utime.tv_sec / 3600),
+                                                       (unsigned)(resp->ru.ru_utime.tv_sec % 3600) / 60,
+                                                       (unsigned)(resp->ru.ru_utime.tv_sec % 60));
+                               else
+                                       printf("%um %u.%02us",  /* -> m:s.  */
+                                                       (unsigned)(resp->ru.ru_utime.tv_sec / 60),
+                                                       (unsigned)(resp->ru.ru_utime.tv_sec % 60),
+                                                       (unsigned)(resp->ru.ru_utime.tv_usec / 10000));
+                               break;
+                       case 'W':       /* Times swapped out.  */
+                               printf("%lu", resp->ru.ru_nswap);
+                               break;
+                       case 'X':       /* Average shared text size.  */
+                               printf("%lu", ptok(pagesize, (UL) resp->ru.ru_ixrss) / cpu_ticks);
+                               break;
+                       case 'Z':       /* Page size.  */
+                               printf("%u", pagesize);
+                               break;
+                       case 'c':       /* Involuntary context switches.  */
+                               printf("%lu", resp->ru.ru_nivcsw);
+                               break;
+                       case 'e':       /* Elapsed real time in seconds.  */
+                               printf("%u.%02u",
+                                               (unsigned)resp->elapsed_ms / 1000,
+                                               (unsigned)(resp->elapsed_ms / 10) % 100);
+                               break;
+                       case 'k':       /* Signals delivered.  */
+                               printf("%lu", resp->ru.ru_nsignals);
+                               break;
+                       case 'p':       /* Average stack segment.  */
+                               printf("%lu", ptok(pagesize, (UL) resp->ru.ru_isrss) / cpu_ticks);
+                               break;
+                       case 'r':       /* Incoming socket messages received.  */
+                               printf("%lu", resp->ru.ru_msgrcv);
+                               break;
+                       case 's':       /* Outgoing socket messages sent.  */
+                               printf("%lu", resp->ru.ru_msgsnd);
+                               break;
+                       case 't':       /* Average resident set size.  */
+                               printf("%lu", ptok(pagesize, (UL) resp->ru.ru_idrss) / cpu_ticks);
+                               break;
+                       case 'w':       /* Voluntary context switches.  */
+                               printf("%lu", resp->ru.ru_nvcsw);
+                               break;
+                       case 'x':       /* Exit status.  */
+                               printf("%u", WEXITSTATUS(resp->waitstatus));
+                               break;
+                       }
+                       break;
+
+#ifdef NOT_NEEDED_YET
+               case '\\':              /* Format escape.  */
+                       switch (*++fmt) {
+                       default:
+                               bb_putchar('\\');
+                               /*FALLTHROUGH*/
+                       case '\\':
+                               if (!*fmt) goto ret;
+                               bb_putchar(*fmt);
+                               break;
+                       case 't':
+                               bb_putchar('\t');
+                               break;
+                       case 'n':
+                               bb_putchar('\n');
+                               break;
+                       }
+                       break;
+#endif
+               }
+               ++fmt;
+       }
+ /* ret: */
+       bb_putchar('\n');
+}
+
+/* Run command CMD and return statistics on it.
+   Put the statistics in *RESP.  */
+static void run_command(char *const *cmd, resource_t *resp)
+{
+       pid_t pid;                      /* Pid of child.  */
+       void (*interrupt_signal)(int);
+       void (*quit_signal)(int);
+
+       resp->elapsed_ms = monotonic_us() / 1000;
+       pid = vfork();          /* Run CMD as child process.  */
+       if (pid < 0)
+               bb_perror_msg_and_die("fork");
+       if (pid == 0) { /* If child.  */
+               /* Don't cast execvp arguments; that causes errors on some systems,
+                  versus merely warnings if the cast is left off.  */
+               BB_EXECVP(cmd[0], cmd);
+               xfunc_error_retval = (errno == ENOENT ? 127 : 126);
+               bb_error_msg_and_die("cannot run %s", cmd[0]);
+       }
+
+       /* Have signals kill the child but not self (if possible).  */
+//TODO: just block all sigs? and reenable them in the very end in main?
+       interrupt_signal = signal(SIGINT, SIG_IGN);
+       quit_signal = signal(SIGQUIT, SIG_IGN);
+
+       resuse_end(pid, resp);
+
+       /* Re-enable signals.  */
+       signal(SIGINT, interrupt_signal);
+       signal(SIGQUIT, quit_signal);
+}
+
+int time_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int time_main(int argc UNUSED_PARAM, char **argv)
+{
+       resource_t res;
+       const char *output_format = default_format;
+       int opt;
+
+       opt_complementary = "-1"; /* at least one arg */
+       /* "+": stop on first non-option */
+       opt = getopt32(argv, "+vp");
+       argv += optind;
+       if (opt & 1)
+               output_format = long_format;
+       if (opt & 2)
+               output_format = posix_format;
+
+       run_command(argv, &res);
+
+       /* Cheat. printf's are shorter :) */
+       /* (but see bb_putchar() body for additional wrinkle!) */
+       xdup2(2, 1); /* just in case libc does something silly :( */
+       stdout = stderr;
+       summarize(output_format, argv, &res);
+
+       if (WIFSTOPPED(res.waitstatus))
+               return WSTOPSIG(res.waitstatus);
+       if (WIFSIGNALED(res.waitstatus))
+               return WTERMSIG(res.waitstatus);
+       if (WIFEXITED(res.waitstatus))
+               return WEXITSTATUS(res.waitstatus);
+       fflush_stdout_and_exit(EXIT_SUCCESS);
+}
diff --git a/miscutils/timeout.c b/miscutils/timeout.c
new file mode 100644 (file)
index 0000000..83ae56e
--- /dev/null
@@ -0,0 +1,115 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * COPYING NOTES
+ *
+ * timeout.c -- a timeout handler for shell commands
+ *
+ * Copyright (C) 2005-6, Roberto A. Foglietta <me@roberto.foglietta.name>
+ *
+ *   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; version 2 of the License.
+ *
+ *   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.
+ */
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * REVISION NOTES:
+ * released 17-11-2005 by Roberto A. Foglietta
+ * talarm   04-12-2005 by Roberto A. Foglietta
+ * modified 05-12-2005 by Roberto A. Foglietta
+ * sizerdct 06-12-2005 by Roberto A. Foglietta
+ * splitszf 12-05-2006 by Roberto A. Foglietta
+ * rewrite  14-11-2008 vda
+ */
+
+#include "libbb.h"
+
+int timeout_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int timeout_main(int argc UNUSED_PARAM, char **argv)
+{
+       int signo;
+       int status;
+       int parent = 0;
+       int timeout = 10;
+       pid_t pid;
+#if !BB_MMU
+       char *sv1, *sv2;
+#endif
+       const char *opt_s = "TERM";
+
+       /* -p option is not documented, it is needed to support NOMMU. */
+
+       /* -t SECONDS; -p PARENT_PID */
+       opt_complementary = "t+" USE_FOR_NOMMU(":p+");
+       /* '+': stop at first non-option */
+       getopt32(argv, "+s:t:" USE_FOR_NOMMU("p:"), &opt_s, &timeout, &parent);
+       /*argv += optind; - no, wait for bb_daemonize_or_rexec! */
+       signo = get_signum(opt_s);
+       if (signo < 0)
+               bb_error_msg_and_die("unknown signal '%s'", opt_s);
+
+       /* We want to create a grandchild which will watch
+        * and kill the grandparent. Other methods:
+        * making parent watch child disrupts parent<->child link
+        * (example: "tcpsvd 0.0.0.0 1234 timeout service_prog" -
+        * it's better if service_prog is a child of tcpsvd!),
+        * making child watch parent results in programs having
+        * unexpected children. */
+
+       if (parent) /* we were re-execed, already grandchild */
+               goto grandchild;
+       if (!argv[optind]) /* no PROG? */
+               bb_show_usage();
+
+#if !BB_MMU
+       sv1 = argv[optind];
+       sv2 = argv[optind + 1];
+#endif
+       pid = vfork();
+       if (pid < 0)
+               bb_perror_msg_and_die("vfork");
+       if (pid == 0) {
+               /* Child: spawn grandchild and exit */
+               parent = getppid();
+#if !BB_MMU
+               argv[optind] = xasprintf("-p%u", parent);
+               argv[optind + 1] = NULL;
+#endif
+               /* NB: exits with nonzero on error: */
+               bb_daemonize_or_rexec(0, argv);
+               /* Here we are grandchild. Sleep, then kill grandparent */
+ grandchild:
+               /* Just sleep(NUGE_NUM); kill(parent) may kill wrong process! */
+               while (1) {
+                       sleep(1);
+                       if (--timeout <= 0)
+                               break;
+                       if (kill(parent, 0)) {
+                               /* process is gone */
+                               return EXIT_SUCCESS;
+                       }
+               }
+               kill(parent, signo);
+               return EXIT_SUCCESS;
+       }
+
+       /* Parent */
+       wait(&status); /* wait for child to die */
+       /* Did intermediate [v]fork or exec fail? */
+       if (!WIFEXITED(status) || WEXITSTATUS(status) != 0)
+               return EXIT_FAILURE;
+       /* Ok, exec a program as requested */
+       argv += optind;
+#if !BB_MMU
+       argv[0] = sv1;
+       argv[1] = sv2;
+#endif
+       BB_EXECVP(argv[0], argv);
+       bb_perror_msg_and_die("exec '%s'", argv[0]);
+}
diff --git a/miscutils/ttysize.c b/miscutils/ttysize.c
new file mode 100644 (file)
index 0000000..0545554
--- /dev/null
@@ -0,0 +1,44 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Replacement for "stty size", which is awkward for shell script use.
+ * - Allows to request width, height, or both, in any order.
+ * - Does not complain on error, but returns width 80, height 24.
+ * - Size: less than 200 bytes
+ *
+ * Copyright (C) 2007 by Denys Vlasenko <vda.linux@googlemail.com>
+ *
+ * Licensed under the GPL v2, see the file LICENSE in this tarball.
+ */
+#include "libbb.h"
+
+int ttysize_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int ttysize_main(int argc, char **argv)
+{
+       unsigned w, h;
+       struct winsize wsz;
+
+       w = 80;
+       h = 24;
+       if (!ioctl(0, TIOCGWINSZ, &wsz)) {
+               w = wsz.ws_col;
+               h = wsz.ws_row;
+       }
+
+       if (argc == 1) {
+               printf("%u %u", w, h);
+       } else {
+               const char *fmt, *arg;
+
+               fmt = "%u %u" + 3; /* "%u" */
+               while ((arg = *++argv) != NULL) {
+                       char c = arg[0];
+                       if (c == 'w')
+                               printf(fmt, w);
+                       if (c == 'h')
+                               printf(fmt, h);
+                       fmt = "%u %u" + 2; /* " %u" */
+               }
+       }
+       bb_putchar('\n');
+       return 0;
+}
diff --git a/miscutils/watchdog.c b/miscutils/watchdog.c
new file mode 100644 (file)
index 0000000..f85138e
--- /dev/null
@@ -0,0 +1,85 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Mini watchdog implementation for busybox
+ *
+ * Copyright (C) 2003  Paul Mundt <lethal@linux-sh.org>
+ * Copyright (C) 2006  Bernhard Reutner-Fischer <busybox@busybox.net>
+ * Copyright (C) 2008  Darius Augulis <augulis.darius@gmail.com>
+ *
+ * Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
+ */
+
+#include "libbb.h"
+#include "linux/types.h" /* for __u32 */
+#include "linux/watchdog.h"
+
+#define OPT_FOREGROUND  (1 << 0)
+#define OPT_STIMER      (1 << 1)
+#define OPT_HTIMER      (1 << 2)
+
+static void watchdog_shutdown(int sig UNUSED_PARAM)
+{
+       static const char V = 'V';
+
+       write(3, &V, 1);        /* Magic, see watchdog-api.txt in kernel */
+       if (ENABLE_FEATURE_CLEAN_UP)
+               close(3);
+       exit(EXIT_SUCCESS);
+}
+
+int watchdog_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int watchdog_main(int argc, char **argv)
+{
+       static const struct suffix_mult suffixes[] = {
+               { "ms", 1 },
+               { "", 1000 },
+               { }
+       };
+
+       unsigned opts;
+       unsigned stimer_duration; /* how often to restart */
+       unsigned htimer_duration = 60000; /* reboots after N ms if not restarted */
+       char *st_arg;
+       char *ht_arg;
+
+       opt_complementary = "=1"; /* must have exactly 1 argument */
+       opts = getopt32(argv, "Ft:T:", &st_arg, &ht_arg);
+
+       if (opts & OPT_HTIMER)
+               htimer_duration = xatou_sfx(ht_arg, suffixes);
+       stimer_duration = htimer_duration / 2;
+       if (opts & OPT_STIMER)
+               stimer_duration = xatou_sfx(st_arg, suffixes);
+
+       bb_signals(BB_FATAL_SIGS, watchdog_shutdown);
+
+       /* Use known fd # - avoid needing global 'int fd' */
+       xmove_fd(xopen(argv[argc - 1], O_WRONLY), 3);
+
+       /* WDIOC_SETTIMEOUT takes seconds, not milliseconds */
+       htimer_duration = htimer_duration / 1000;
+#ifndef WDIOC_SETTIMEOUT
+#error WDIOC_SETTIMEOUT is not defined, cannot compile watchdog applet
+#else
+       ioctl_or_warn(3, WDIOC_SETTIMEOUT, &htimer_duration);
+#endif
+#if 0
+       ioctl_or_warn(3, WDIOC_GETTIMEOUT, &htimer_duration);
+       printf("watchdog: SW timer is %dms, HW timer is %dms\n",
+               stimer_duration, htimer_duration * 1000);
+#endif
+
+       if (!(opts & OPT_FOREGROUND)) {
+               bb_daemonize_or_rexec(DAEMON_CHDIR_ROOT, argv);
+       }
+
+       while (1) {
+               /*
+                * Make sure we clear the counter before sleeping,
+                * as the counter value is undefined at this point -- PFM
+                */
+               write(3, "", 1); /* write zero byte */
+               usleep(stimer_duration * 1000L);
+       }
+       return EXIT_SUCCESS; /* - not reached, but gcc 4.2.1 is too dumb! */
+}
diff --git a/modutils/Config.in b/modutils/Config.in
new file mode 100644 (file)
index 0000000..ef8d969
--- /dev/null
@@ -0,0 +1,227 @@
+#
+# For a description of the syntax of this configuration file,
+# see scripts/kbuild/config-language.txt.
+#
+
+menu "Linux Module Utilities"
+
+config MODPROBE_SMALL
+       bool "Simplified modutils"
+       default n
+       help
+         Simplified modutils.
+
+         With this option modprobe does not require modules.dep file
+         and does not use /etc/modules.conf file.
+         It scans module files in /lib/modules/`uname -r` and
+         determines dependencies and module alias names on the fly.
+         This may make module loading slower, most notably
+         when one needs to load module by alias (this requires
+         scanning through module _bodies_).
+
+         At the first attempt to load a module by alias modprobe
+         will try to generate modules.dep.bb file in order to speed up
+         future loads by alias. Failure to do so (read-only /lib/modules,
+         etc) is not reported, and future modprobes will be slow too.
+
+         NB: modules.dep.bb file format is not compatible
+         with modules.dep file as created/used by standard module tools.
+
+         Additional module parameters can be stored in
+         /etc/modules/$module_name files.
+
+         Apart from modprobe, other utilities are also provided:
+         - insmod is an alias to modprobe
+         - rmmod is an alias to modprobe -r
+         - depmod generates modules.dep.bb
+
+         As of 2008-07, this code is experimental. It is 14kb smaller
+         than "non-small" modutils.
+
+config FEATURE_MODPROBE_SMALL_OPTIONS_ON_CMDLINE
+       bool "Accept module options on modprobe command line"
+       default n
+       depends on MODPROBE_SMALL
+       help
+         Allow insmod and modprobe take module options from command line.
+
+config FEATURE_MODPROBE_SMALL_CHECK_ALREADY_LOADED
+       bool "Skip loading of already loaded modules"
+       default n
+       depends on MODPROBE_SMALL
+       help
+         Check if the module is already loaded.
+
+config INSMOD
+       bool "insmod"
+       default n
+       depends on !MODPROBE_SMALL
+       help
+         insmod is used to load specified modules in the running kernel.
+
+config RMMOD
+       bool "rmmod"
+       default n
+       depends on !MODPROBE_SMALL
+       help
+         rmmod is used to unload specified modules from the kernel.
+
+config LSMOD
+       bool "lsmod"
+       default n
+       depends on !MODPROBE_SMALL
+       help
+         lsmod is used to display a list of loaded modules.
+
+config FEATURE_LSMOD_PRETTY_2_6_OUTPUT
+       bool "Pretty output"
+       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.
+         Increases size somewhat.
+
+config MODPROBE
+       bool "modprobe"
+       default n
+       depends on !MODPROBE_SMALL
+       help
+         Handle the loading of modules, and their dependencies on a high
+         level.
+
+config FEATURE_MODPROBE_BLACKLIST
+       bool
+       prompt "Blacklist support"
+       default n
+       depends on MODPROBE
+       help
+         Say 'y' here to enable support for the 'blacklist' command in
+         modprobe.conf. This prevents the alias resolver to resolve
+         blacklisted modules. This is useful if you want to prevent your
+         hardware autodetection scripts to load modules like evdev, frame
+         buffer drivers etc.
+
+config DEPMOD
+       bool "depmod"
+       default n
+       depends on !MODPROBE_SMALL
+       help
+         depmod generates modules.dep (and potentially modules.alias
+         and modules.symbols) that contain dependency information
+         for modprobe.
+
+comment "Options common to multiple modutils"
+
+config FEATURE_2_4_MODULES
+       bool "Support version 2.2/2.4 Linux kernels"
+       default n
+       depends on INSMOD || RMMOD || LSMOD
+       help
+         Support module loading for 2.2.x and 2.4.x Linux kernels.
+         This increases size considerably. Say N unless you plan
+         to run ancient kernels.
+
+config FEATURE_INSMOD_VERSION_CHECKING
+       bool "Enable module version checking"
+       default n
+       depends on FEATURE_2_4_MODULES && (INSMOD || MODPROBE)
+       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 FEATURE_2_4_MODULES && (INSMOD || MODPROBE)
+       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 proper 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 FEATURE_2_4_MODULES && (INSMOD || MODPROBE)
+       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 insmod load map (-m) option"
+       default n
+       depends on FEATURE_2_4_MODULES && INSMOD
+       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 && !MODPROBE_SMALL
+       help
+         Without this option, -m will only output section
+         load map. With this option, -m will also output
+         symbols load map.
+
+config FEATURE_CHECK_TAINTED_MODULE
+       bool "Support tainted module checking with new kernels"
+       default y
+       depends on (LSMOD || FEATURE_2_4_MODULES) && !MODPROBE_SMALL
+       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_MODUTILS_ALIAS
+       bool "Support for module.aliases file"
+       default y
+       depends on DEPMOD || MODPROBE
+       help
+         Generate and parse modules.alias containing aliases for bus
+         identifiers:
+           alias pcmcia:m*c*f03fn*pfn*pa*pb*pc*pd* parport_cs
+
+         and aliases for logical modules names e.g.:
+           alias padlock_aes aes
+           alias aes_i586 aes
+           alias aes_generic aes
+
+         Say Y if unsure.
+
+config FEATURE_MODUTILS_SYMBOLS
+       bool "Support for module.symbols file"
+       default y
+       depends on DEPMOD || MODPROBE
+       help
+         Generate and parse modules.symbols containing aliases for
+         symbol_request() kernel calls, such as:
+           alias symbol:usb_sg_init usbcore
+
+         Say Y if unsure.
+
+config DEFAULT_MODULES_DIR
+       string "Default directory containing modules"
+       default "/lib/modules"
+       depends on DEPMOD || MODPROBE || MODPROBE_SMALL
+       help
+         Directory that contains kernel modules.
+         Defaults to "/lib/modules"
+
+config DEFAULT_DEPMOD_FILE
+       string "Default name of modules.dep"
+       default "modules.dep"
+       depends on DEPMOD || MODPROBE || MODPROBE_SMALL
+       help
+         Filename that contains kernel modules dependencies.
+         Defaults to "modules.dep"
+
+endmenu
diff --git a/modutils/Kbuild b/modutils/Kbuild
new file mode 100644 (file)
index 0000000..31f7cbf
--- /dev/null
@@ -0,0 +1,14 @@
+# 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_MODPROBE_SMALL)      += modprobe-small.o
+lib-$(CONFIG_DEPMOD)              += depmod.o modutils.o
+lib-$(CONFIG_INSMOD)              += insmod.o modutils.o
+lib-$(CONFIG_LSMOD)               += lsmod.o modutils.o
+lib-$(CONFIG_MODPROBE)            += modprobe.o modutils.o
+lib-$(CONFIG_RMMOD)               += rmmod.o modutils.o
+lib-$(CONFIG_FEATURE_2_4_MODULES) += modutils-24.o
diff --git a/modutils/depmod.c b/modutils/depmod.c
new file mode 100644 (file)
index 0000000..5ec2a51
--- /dev/null
@@ -0,0 +1,245 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * depmod - generate modules.dep
+ * Copyright (c) 2008 Bernhard Reutner-Fischer
+ * Copyrihgt (c) 2008 Timo Teras <timo.teras@iki.fi>
+ * Copyright (c) 2008 Vladimir Dronnikov
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+#undef _GNU_SOURCE
+#define _GNU_SOURCE
+#include <libbb.h>
+#include <sys/utsname.h> /* uname() */
+#include "modutils.h"
+
+/*
+ * Theory of operation:
+ * - iterate over all modules and record their full path
+ * - iterate over all modules looking for "depends=" entries
+ *   for each depends, look through our list of full paths and emit if found
+ */
+
+typedef struct module_info {
+       struct module_info *next;
+       char *name, *modname;
+       llist_t *dependencies;
+       llist_t *aliases;
+       llist_t *symbols;
+       struct module_info *dnext, *dprev;
+} module_info;
+
+enum {
+       ARG_a = (1<<0), /* All modules, ignore mods in argv */
+       ARG_A = (1<<1), /* Only emit .ko that are newer than modules.dep file */
+       ARG_b = (1<<2), /* base directory when modules are in staging area */
+       ARG_e = (1<<3), /* with -F, print unresolved symbols */
+       ARG_F = (1<<4), /* System.map that contains the symbols */
+       ARG_n = (1<<5), /* dry-run, print to stdout only */
+       ARG_r = (1<<6)  /* Compat dummy. Linux Makefile uses it */
+};
+
+static int FAST_FUNC parse_module(const char *fname, struct stat *sb UNUSED_PARAM,
+                                 void *data, int depth UNUSED_PARAM)
+{
+       char modname[MODULE_NAME_LEN];
+       module_info **first = (module_info **) data;
+       char *image, *ptr;
+       module_info *info;
+       /* Arbitrary. Was sb->st_size, but that breaks .gz etc */
+       size_t len = (64*1024*1024 - 4096);
+
+       if (strrstr(fname, ".ko") == NULL)
+               return TRUE;
+
+       image = xmalloc_open_zipped_read_close(fname, &len);
+       info = xzalloc(sizeof(*info));
+
+       info->next = *first;
+       *first = info;
+
+       info->dnext = info->dprev = info;
+       info->name = xasprintf("/%s", fname);
+       info->modname = xstrdup(filename2modname(fname, modname));
+       for (ptr = image; ptr < image + len - 10; ptr++) {
+               if (strncmp(ptr, "depends=", 8) == 0) {
+                       char *u;
+
+                       ptr += 8;
+                       for (u = ptr; *u; u++)
+                               if (*u == '-')
+                                       *u = '_';
+                       ptr += string_to_llist(ptr, &info->dependencies, ",");
+               } else if (ENABLE_FEATURE_MODUTILS_ALIAS
+                && strncmp(ptr, "alias=", 6) == 0
+               ) {
+                       llist_add_to(&info->aliases, xstrdup(ptr + 6));
+                       ptr += strlen(ptr);
+               } else if (ENABLE_FEATURE_MODUTILS_SYMBOLS
+                && strncmp(ptr, "__ksymtab_", 10) == 0
+               ) {
+                       ptr += 10;
+                       if (strncmp(ptr, "gpl", 3) == 0 ||
+                           strcmp(ptr, "strings") == 0)
+                               continue;
+                       llist_add_to(&info->symbols, xstrdup(ptr));
+                       ptr += strlen(ptr);
+               }
+       }
+       free(image);
+
+       return TRUE;
+}
+
+static module_info *find_module(module_info *modules, const char *modname)
+{
+       module_info *m;
+
+       for (m = modules; m != NULL; m = m->next)
+               if (strcmp(m->modname, modname) == 0)
+                       return m;
+       return NULL;
+}
+
+static void order_dep_list(module_info *modules, module_info *start,
+                          llist_t *add)
+{
+       module_info *m;
+       llist_t *n;
+
+       for (n = add; n != NULL; n = n->link) {
+               m = find_module(modules, n->data);
+               if (m == NULL)
+                       continue;
+
+               /* unlink current entry */
+               m->dnext->dprev = m->dprev;
+               m->dprev->dnext = m->dnext;
+
+               /* and add it to tail */
+               m->dnext = start;
+               m->dprev = start->dprev;
+               start->dprev->dnext = m;
+               start->dprev = m;
+
+               /* recurse */
+               order_dep_list(modules, start, m->dependencies);
+       }
+}
+
+static void xfreopen_write(const char *file, FILE *f)
+{
+       if (freopen(file, "w", f) == NULL)
+               bb_perror_msg_and_die("can't open '%s'", file);
+}
+
+int depmod_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int depmod_main(int argc UNUSED_PARAM, char **argv)
+{
+       module_info *modules = NULL, *m, *dep;
+       const char *moddir_base = "/";
+       char *moddir, *version;
+       struct utsname uts;
+       int tmp;
+
+       getopt32(argv, "aAb:eF:nr", &moddir_base, NULL);
+       argv += optind;
+
+       /* goto modules location */
+       xchdir(moddir_base);
+
+       /* If a version is provided, then that kernel version's module directory
+        * is used, rather than the current kernel version (as returned by
+        * "uname -r").  */
+       if (*argv && sscanf(*argv, "%d.%d.%d", &tmp, &tmp, &tmp) == 3) {
+               version = *argv++;
+       } else {
+               uname(&uts);
+               version = uts.release;
+       }
+       moddir = concat_path_file(&CONFIG_DEFAULT_MODULES_DIR[1], version);
+
+       /* Scan modules */
+       if (*argv) {
+               char *modfile;
+               struct stat sb;
+               do {
+                       modfile = concat_path_file(moddir, *argv);
+                       xstat(modfile, &sb);
+                       parse_module(modfile, &sb, &modules, 0);
+                       free(modfile);
+               } while (*(++argv));
+       } else {
+               recursive_action(moddir, ACTION_RECURSE,
+                                parse_module, NULL, &modules, 0);
+       }
+
+       /* Prepare for writing out the dep files */
+       xchdir(moddir);
+       if (ENABLE_FEATURE_CLEAN_UP)
+               free(moddir);
+
+       /* Generate dependency and alias files */
+       if (!(option_mask32 & ARG_n))
+               xfreopen_write(CONFIG_DEFAULT_DEPMOD_FILE, stdout);
+       for (m = modules; m != NULL; m = m->next) {
+               printf("%s:", m->name);
+
+               order_dep_list(modules, m, m->dependencies);
+               while (m->dnext != m) {
+                       dep = m->dnext;
+                       printf(" %s", dep->name);
+
+                       /* unlink current entry */
+                       dep->dnext->dprev = dep->dprev;
+                       dep->dprev->dnext = dep->dnext;
+                       dep->dnext = dep->dprev = dep;
+               }
+               bb_putchar('\n');
+       }
+
+#if ENABLE_FEATURE_MODUTILS_ALIAS
+       if (!(option_mask32 & ARG_n))
+               xfreopen_write("modules.alias", stdout);
+       for (m = modules; m != NULL; m = m->next) {
+               const char *fname = bb_basename(m->name);
+               int fnlen = strchrnul(fname, '.') - fname;
+               while (m->aliases) {
+                       /* Last word can well be m->modname instead,
+                        * but depmod from module-init-tools 3.4
+                        * uses module basename, i.e., no s/-/_/g.
+                        * (pathname and .ko.* are still stripped)
+                        * Mimicking that... */
+                       printf("alias %s %.*s\n",
+                               (char*)llist_pop(&m->aliases),
+                               fnlen, fname);
+               }
+       }
+#endif
+#if ENABLE_FEATURE_MODUTILS_SYMBOLS
+       if (!(option_mask32 & ARG_n))
+               xfreopen_write("modules.symbols", stdout);
+       for (m = modules; m != NULL; m = m->next) {
+               const char *fname = bb_basename(m->name);
+               int fnlen = strchrnul(fname, '.') - fname;
+               while (m->symbols) {
+                       printf("alias symbol:%s %.*s\n",
+                               (char*)llist_pop(&m->symbols),
+                               fnlen, fname);
+               }
+       }
+#endif
+
+       if (ENABLE_FEATURE_CLEAN_UP) {
+               while (modules) {
+                       module_info *old = modules;
+                       modules = modules->next;
+                       free(old->name);
+                       free(old->modname);
+                       free(old);
+               }
+       }
+
+       return EXIT_SUCCESS;
+}
diff --git a/modutils/insmod.c b/modutils/insmod.c
new file mode 100644 (file)
index 0000000..90ed87a
--- /dev/null
@@ -0,0 +1,41 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Mini insmod implementation for busybox
+ *
+ * Copyright (C) 2008 Timo Teras <timo.teras@iki.fi>
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.h"
+#include "modutils.h"
+
+int insmod_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int insmod_main(int argc UNUSED_PARAM, char **argv)
+{
+       char *filename;
+       int rc;
+
+       /* Compat note:
+        * 2.6 style insmod has no options and required filename
+        * (not module name - .ko can't be omitted).
+        * 2.4 style insmod can take module name without .o
+        * and performs module search in default directories
+        * or in $MODPATH.
+        */
+
+       USE_FEATURE_2_4_MODULES(
+               getopt32(argv, INSMOD_OPTS INSMOD_ARGS);
+               argv += optind - 1;
+       );
+
+       filename = *++argv;
+       if (!filename)
+               bb_show_usage();
+
+       rc = bb_init_module(filename, parse_cmdline_module_options(argv));
+       if (rc)
+               bb_error_msg("can't insert '%s': %s", filename, moderror(rc));
+
+       return rc;
+}
diff --git a/modutils/lsmod.c b/modutils/lsmod.c
new file mode 100644 (file)
index 0000000..87dd1fc
--- /dev/null
@@ -0,0 +1,79 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Mini lsmod implementation for busybox
+ *
+ * Copyright (C) 1999-2004 by Erik Andersen <andersen@codepoet.org>
+ * Copyright (C) 2008 by Vladimir Dronnikov <dronnikov@gmail.com>
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.h"
+
+#if ENABLE_FEATURE_CHECK_TAINTED_MODULE
+enum {
+       TAINT_PROPRIETORY_MODULE = (1 << 0),
+       TAINT_FORCED_MODULE      = (1 << 1),
+       TAINT_UNSAFE_SMP         = (1 << 2),
+};
+
+static void check_tainted(void)
+{
+       int tainted = 0;
+       char *buf = xmalloc_open_read_close("/proc/sys/kernel/tainted", NULL);
+       if (buf) {
+               tainted = atoi(buf);
+               if (ENABLE_FEATURE_CLEAN_UP)
+                       free(buf);
+       }
+
+       if (tainted) {
+               printf("    Tainted: %c%c%c\n",
+                               tainted & TAINT_PROPRIETORY_MODULE      ? 'P' : 'G',
+                               tainted & TAINT_FORCED_MODULE           ? 'F' : ' ',
+                               tainted & TAINT_UNSAFE_SMP              ? 'S' : ' ');
+       } else {
+               puts("    Not tainted");
+       }
+}
+#else
+static void check_tainted(void) { putchar('\n'); }
+#endif
+
+int lsmod_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int lsmod_main(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
+{
+#if ENABLE_FEATURE_LSMOD_PRETTY_2_6_OUTPUT
+       char *token[4];
+       parser_t *parser = config_open("/proc/modules");
+       printf("%-24sSize  Used by", "Module");
+       check_tainted();
+
+       if (ENABLE_FEATURE_2_4_MODULES
+        && get_linux_version_code() < KERNEL_VERSION(2,6,0)
+       ) {
+               while (config_read(parser, token, 4, 3, "# \t", PARSE_NORMAL)) {
+                       if (token[3] != NULL && token[3][0] == '[') {
+                               token[3]++;
+                               token[3][strlen(token[3])-1] = '\0';
+                       } else
+                               token[3] = (char *) "";
+                       printf("%-19s %8s %2s %s\n", token[0], token[1], token[2], token[3]);
+               }
+       } else {
+               while (config_read(parser, token, 4, 4, "# \t", PARSE_NORMAL & ~PARSE_GREEDY)) {
+                       // N.B. token[3] is either '-' (module is not used by others)
+                       // or comma-separated list ended by comma
+                       // so trimming the trailing char is just what we need!
+                       token[3][strlen(token[3])-1] = '\0';
+                       printf("%-19s %8s %2s %s\n", token[0], token[1], token[2], token[3]);
+               }
+       }
+       if (ENABLE_FEATURE_CLEAN_UP)
+               config_close(parser);
+#else
+       check_tainted();
+       xprint_and_close_file(xfopen_for_read("/proc/modules"));
+#endif
+       return EXIT_SUCCESS;
+}
diff --git a/modutils/modprobe-small.c b/modutils/modprobe-small.c
new file mode 100644 (file)
index 0000000..6ee0164
--- /dev/null
@@ -0,0 +1,797 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * simplified modprobe
+ *
+ * Copyright (c) 2008 Vladimir Dronnikov
+ * Copyright (c) 2008 Bernhard Reutner-Fischer (initial depmod code)
+ *
+ * Licensed under GPLv2, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.h"
+
+#include <sys/utsname.h> /* uname() */
+#include <fnmatch.h>
+
+extern int init_module(void *module, unsigned long len, const char *options);
+extern int delete_module(const char *module, unsigned flags);
+extern int query_module(const char *name, int which, void *buf, size_t bufsize, size_t *ret);
+
+
+#define dbg1_error_msg(...) ((void)0)
+#define dbg2_error_msg(...) ((void)0)
+//#define dbg1_error_msg(...) bb_error_msg(__VA_ARGS__)
+//#define dbg2_error_msg(...) bb_error_msg(__VA_ARGS__)
+
+#define DEPFILE_BB CONFIG_DEFAULT_DEPMOD_FILE".bb"
+
+enum {
+       OPT_q = (1 << 0), /* be quiet */
+       OPT_r = (1 << 1), /* module removal instead of loading */
+};
+
+typedef struct module_info {
+       char *pathname;
+       char *aliases;
+       char *deps;
+} module_info;
+
+/*
+ * GLOBALS
+ */
+struct globals {
+       module_info *modinfo;
+       char *module_load_options;
+       smallint dep_bb_seen;
+       smallint wrote_dep_bb_ok;
+       int module_count;
+       int module_found_idx;
+       int stringbuf_idx;
+       char stringbuf[32 * 1024]; /* some modules have lots of stuff */
+       /* for example, drivers/media/video/saa7134/saa7134.ko */
+};
+#define G (*ptr_to_globals)
+#define modinfo             (G.modinfo            )
+#define dep_bb_seen         (G.dep_bb_seen        )
+#define wrote_dep_bb_ok     (G.wrote_dep_bb_ok    )
+#define module_count        (G.module_count       )
+#define module_found_idx    (G.module_found_idx   )
+#define module_load_options (G.module_load_options)
+#define stringbuf_idx       (G.stringbuf_idx      )
+#define stringbuf           (G.stringbuf          )
+#define INIT_G() do { \
+       SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
+} while (0)
+
+
+static void appendc(char c)
+{
+       if (stringbuf_idx < sizeof(stringbuf))
+               stringbuf[stringbuf_idx++] = c;
+}
+
+static void bksp(void)
+{
+       if (stringbuf_idx)
+               stringbuf_idx--;
+}
+
+static void append(const char *s)
+{
+       size_t len = strlen(s);
+       if (stringbuf_idx + len < sizeof(stringbuf)) {
+               memcpy(stringbuf + stringbuf_idx, s, len);
+               stringbuf_idx += len;
+       }
+}
+
+static void reset_stringbuf(void)
+{
+       stringbuf_idx = 0;
+}
+
+static char* copy_stringbuf(void)
+{
+       char *copy = xmalloc(stringbuf_idx);
+       return memcpy(copy, stringbuf, stringbuf_idx);
+}
+
+static char* find_keyword(char *ptr, size_t len, const char *word)
+{
+       int wlen;
+
+       if (!ptr) /* happens if xmalloc_open_zipped_read_close cannot read it */
+               return NULL;
+
+       wlen = strlen(word);
+       len -= wlen - 1;
+       while ((ssize_t)len > 0) {
+               char *old = ptr;
+               /* search for the first char in word */
+               ptr = memchr(ptr, *word, len);
+               if (ptr == NULL) /* no occurance left, done */
+                       break;
+               if (strncmp(ptr, word, wlen) == 0)
+                       return ptr + wlen; /* found, return ptr past it */
+               ++ptr;
+               len -= (ptr - old);
+       }
+       return NULL;
+}
+
+static void replace(char *s, char what, char with)
+{
+       while (*s) {
+               if (what == *s)
+                       *s = with;
+               ++s;
+       }
+}
+
+/* Take "word word", return malloced "word",NUL,"word",NUL,NUL */
+static char* str_2_list(const char *str)
+{
+       int len = strlen(str) + 1;
+       char *dst = xmalloc(len + 1);
+
+       dst[len] = '\0';
+       memcpy(dst, str, len);
+//TODO: protect against 2+ spaces: "word  word"
+       replace(dst, ' ', '\0');
+       return dst;
+}
+
+/* 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 or invalid parameter";
+       case ESRCH:
+               return "module has wrong symbol version";
+       case EINVAL: /* "invalid parameter" */
+               return "unknown symbol in module or invalid parameter"
+               + sizeof("unknown symbol in module or");
+       default:
+               return strerror(err);
+       }
+}
+
+static int load_module(const char *fname, const char *options)
+{
+#if 1
+       int r;
+       size_t len = MAXINT(ssize_t);
+       char *module_image;
+       dbg1_error_msg("load_module('%s','%s')", fname, options);
+
+       module_image = xmalloc_open_zipped_read_close(fname, &len);
+       r = (!module_image || init_module(module_image, len, options ? options : "") != 0);
+       free(module_image);
+       dbg1_error_msg("load_module:%d", r);
+       return r; /* 0 = success */
+#else
+       /* For testing */
+       dbg1_error_msg("load_module('%s','%s')", fname, options);
+       return 1;
+#endif
+}
+
+static void parse_module(module_info *info, const char *pathname)
+{
+       char *module_image;
+       char *ptr;
+       size_t len;
+       size_t pos;
+       dbg1_error_msg("parse_module('%s')", pathname);
+
+       /* Read (possibly compressed) module */
+       len = 64 * 1024 * 1024; /* 64 Mb at most */
+       module_image = xmalloc_open_zipped_read_close(pathname, &len);
+//TODO: optimize redundant module body reads
+
+       /* "alias1 symbol:sym1 alias2 symbol:sym2" */
+       reset_stringbuf();
+       pos = 0;
+       while (1) {
+               ptr = find_keyword(module_image + pos, len - pos, "alias=");
+               if (!ptr) {
+                       ptr = find_keyword(module_image + pos, len - pos, "__ksymtab_");
+                       if (!ptr)
+                               break;
+                       /* DOCME: __ksymtab_gpl and __ksymtab_strings occur
+                        * in many modules. What do they mean? */
+                       if (strcmp(ptr, "gpl") == 0 || strcmp(ptr, "strings") == 0)
+                               goto skip;
+                       dbg2_error_msg("alias:'symbol:%s'", ptr);
+                       append("symbol:");
+               } else {
+                       dbg2_error_msg("alias:'%s'", ptr);
+               }
+               append(ptr);
+               appendc(' ');
+ skip:
+               pos = (ptr - module_image);
+       }
+       bksp(); /* remove last ' ' */
+       appendc('\0');
+       info->aliases = copy_stringbuf();
+
+       /* "dependency1 depandency2" */
+       reset_stringbuf();
+       ptr = find_keyword(module_image, len, "depends=");
+       if (ptr && *ptr) {
+               replace(ptr, ',', ' ');
+               replace(ptr, '-', '_');
+               dbg2_error_msg("dep:'%s'", ptr);
+               append(ptr);
+       }
+       appendc('\0');
+       info->deps = copy_stringbuf();
+
+       free(module_image);
+}
+
+static int pathname_matches_modname(const char *pathname, const char *modname)
+{
+       const char *fname = bb_get_last_path_component_nostrip(pathname);
+       const char *suffix = strrstr(fname, ".ko");
+//TODO: can do without malloc?
+       char *name = xstrndup(fname, suffix - fname);
+       int r;
+       replace(name, '-', '_');
+       r = (strcmp(name, modname) == 0);
+       free(name);
+       return r;
+}
+
+static FAST_FUNC int fileAction(const char *pathname,
+               struct stat *sb UNUSED_PARAM,
+               void *modname_to_match,
+               int depth UNUSED_PARAM)
+{
+       int cur;
+       const char *fname;
+
+       pathname += 2; /* skip "./" */
+       fname = bb_get_last_path_component_nostrip(pathname);
+       if (!strrstr(fname, ".ko")) {
+               dbg1_error_msg("'%s' is not a module", pathname);
+               return TRUE; /* not a module, continue search */
+       }
+
+       cur = module_count++;
+       modinfo = xrealloc_vector(modinfo, 12, cur);
+       modinfo[cur].pathname = xstrdup(pathname);
+       /*modinfo[cur].aliases = NULL; - xrealloc_vector did it */
+       /*modinfo[cur+1].pathname = NULL;*/
+
+       if (!pathname_matches_modname(fname, modname_to_match)) {
+               dbg1_error_msg("'%s' module name doesn't match", pathname);
+               return TRUE; /* module name doesn't match, continue search */
+       }
+
+       dbg1_error_msg("'%s' module name matches", pathname);
+       module_found_idx = cur;
+       parse_module(&modinfo[cur], pathname);
+
+       if (!(option_mask32 & OPT_r)) {
+               if (load_module(pathname, module_load_options) == 0) {
+                       /* Load was successful, there is nothing else to do.
+                        * This can happen ONLY for "top-level" module load,
+                        * not a dep, because deps dont do dirscan. */
+                       exit(EXIT_SUCCESS);
+               }
+       }
+
+       return TRUE;
+}
+
+static int load_dep_bb(void)
+{
+       char *line;
+       FILE *fp = fopen_for_read(DEPFILE_BB);
+
+       if (!fp)
+               return 0;
+
+       dep_bb_seen = 1;
+       dbg1_error_msg("loading "DEPFILE_BB);
+
+       /* Why? There is a rare scenario: we did not find modprobe.dep.bb,
+        * we scanned the dir and found no module by name, then we search
+        * for alias (full scan), and we decided to generate modprobe.dep.bb.
+        * But we see modprobe.dep.bb.new! Other modprobe is at work!
+        * We wait and other modprobe renames it to modprobe.dep.bb.
+        * Now we can use it.
+        * But we already have modinfo[] filled, and "module_count = 0"
+        * makes us start anew. Yes, we leak modinfo[].xxx pointers -
+        * there is not much of data there anyway. */
+       module_count = 0;
+       memset(&modinfo[0], 0, sizeof(modinfo[0]));
+
+       while ((line = xmalloc_fgetline(fp)) != NULL) {
+               char* space;
+               int cur;
+
+               if (!line[0]) {
+                       free(line);
+                       continue;
+               }
+               space = strchrnul(line, ' ');
+               cur = module_count++;
+               modinfo = xrealloc_vector(modinfo, 12, cur);
+               /*modinfo[cur+1].pathname = NULL; - xrealloc_vector did it */
+               modinfo[cur].pathname = line; /* we take ownership of malloced block here */
+               if (*space)
+                       *space++ = '\0';
+               modinfo[cur].aliases = space;
+               modinfo[cur].deps = xmalloc_fgetline(fp) ? : xzalloc(1);
+               if (modinfo[cur].deps[0]) {
+                       /* deps are not "", so next line must be empty */
+                       line = xmalloc_fgetline(fp);
+                       /* Refuse to work with damaged config file */
+                       if (line && line[0])
+                               bb_error_msg_and_die("error in %s at '%s'", DEPFILE_BB, line);
+                       free(line);
+               }
+       }
+       return 1;
+}
+
+static int start_dep_bb_writeout(void)
+{
+       int fd;
+
+       /* depmod -n: write result to stdout */
+       if (applet_name[0] == 'd' && (option_mask32 & 1))
+               return STDOUT_FILENO;
+
+       fd = open(DEPFILE_BB".new", O_WRONLY | O_CREAT | O_TRUNC | O_EXCL, 0644);
+       if (fd < 0) {
+               if (errno == EEXIST) {
+                       int count = 5 * 20;
+                       dbg1_error_msg(DEPFILE_BB".new exists, waiting for "DEPFILE_BB);
+                       while (1) {
+                               usleep(1000*1000 / 20);
+                               if (load_dep_bb()) {
+                                       dbg1_error_msg(DEPFILE_BB" appeared");
+                                       return -2; /* magic number */
+                               }
+                               if (!--count)
+                                       break;
+                       }
+                       bb_error_msg("deleting stale %s", DEPFILE_BB".new");
+                       fd = open_or_warn(DEPFILE_BB".new", O_WRONLY | O_CREAT | O_TRUNC);
+               }
+       }
+       dbg1_error_msg("opened "DEPFILE_BB".new:%d", fd);
+       return fd;
+}
+
+static void write_out_dep_bb(int fd)
+{
+       int i;
+       FILE *fp;
+
+       /* We want good error reporting. fdprintf is not good enough. */
+       fp = fdopen(fd, "w");
+       if (!fp) {
+               close(fd);
+               goto err;
+       }
+       i = 0;
+       while (modinfo[i].pathname) {
+               fprintf(fp, "%s%s%s\n" "%s%s\n",
+                       modinfo[i].pathname, modinfo[i].aliases[0] ? " " : "", modinfo[i].aliases,
+                       modinfo[i].deps, modinfo[i].deps[0] ? "\n" : "");
+               i++;
+       }
+       /* Badly formatted depfile is a no-no. Be paranoid. */
+       errno = 0;
+       if (ferror(fp) | fclose(fp)) /* | instead of || is intended */
+               goto err;
+
+       if (fd == STDOUT_FILENO) /* it was depmod -n */
+               goto ok;
+
+       if (rename(DEPFILE_BB".new", DEPFILE_BB) != 0) {
+ err:
+               bb_perror_msg("can't create %s", DEPFILE_BB);
+               unlink(DEPFILE_BB".new");
+       } else {
+ ok:
+               wrote_dep_bb_ok = 1;
+               dbg1_error_msg("created "DEPFILE_BB);
+       }
+}
+
+static module_info* find_alias(const char *alias)
+{
+       int i;
+       int dep_bb_fd;
+       module_info *result;
+       dbg1_error_msg("find_alias('%s')", alias);
+
+ try_again:
+       /* First try to find by name (cheaper) */
+       i = 0;
+       while (modinfo[i].pathname) {
+               if (pathname_matches_modname(modinfo[i].pathname, alias)) {
+                       dbg1_error_msg("found '%s' in module '%s'",
+                                       alias, modinfo[i].pathname);
+                       if (!modinfo[i].aliases) {
+                               parse_module(&modinfo[i], modinfo[i].pathname);
+                       }
+                       return &modinfo[i];
+               }
+               i++;
+       }
+
+       /* Ok, we definitely have to scan module bodies. This is a good
+        * moment to generate modprobe.dep.bb, if it does not exist yet */
+       dep_bb_fd = dep_bb_seen ? -1 : start_dep_bb_writeout();
+       if (dep_bb_fd == -2) /* modprobe.dep.bb appeared? */
+               goto try_again;
+
+       /* Scan all module bodies, extract modinfo (it contains aliases) */
+       i = 0;
+       result = NULL;
+       while (modinfo[i].pathname) {
+               char *desc, *s;
+               if (!modinfo[i].aliases) {
+                       parse_module(&modinfo[i], modinfo[i].pathname);
+               }
+               if (result) {
+                       i++;
+                       continue;
+               }
+               /* "alias1 symbol:sym1 alias2 symbol:sym2" */
+               desc = str_2_list(modinfo[i].aliases);
+               /* Does matching substring exist? */
+               for (s = desc; *s; s += strlen(s) + 1) {
+                       /* Aliases in module bodies can be defined with
+                        * shell patterns. Example:
+                        * "pci:v000010DEd000000D9sv*sd*bc*sc*i*".
+                        * Plain strcmp() won't catch that */
+                       if (fnmatch(s, alias, 0) == 0) {
+                               dbg1_error_msg("found alias '%s' in module '%s'",
+                                               alias, modinfo[i].pathname);
+                               result = &modinfo[i];
+                               break;
+                       }
+               }
+               free(desc);
+               if (result && dep_bb_fd < 0)
+                       return result;
+               i++;
+       }
+
+       /* Create module.dep.bb if needed */
+       if (dep_bb_fd >= 0) {
+               write_out_dep_bb(dep_bb_fd);
+       }
+
+       dbg1_error_msg("find_alias '%s' returns %p", alias, result);
+       return result;
+}
+
+#if ENABLE_FEATURE_MODPROBE_SMALL_CHECK_ALREADY_LOADED
+// TODO: open only once, invent config_rewind()
+static int already_loaded(const char *name)
+{
+       int ret = 0;
+       char *s;
+       parser_t *parser = config_open2("/proc/modules", xfopen_for_read);
+       while (config_read(parser, &s, 1, 1, "# \t", PARSE_NORMAL & ~PARSE_GREEDY)) {
+               if (strcmp(s, name) == 0) {
+                       ret = 1;
+                       break;
+               }
+       }
+       config_close(parser);
+       return ret;
+}
+#else
+#define already_loaded(name) is_rmmod
+#endif
+
+/*
+ * Given modules definition and module name (or alias, or symbol)
+ * load/remove the module respecting dependencies.
+ * NB: also called by depmod with bogus name "/",
+ * just in order to force modprobe.dep.bb creation.
+*/
+#if !ENABLE_FEATURE_MODPROBE_SMALL_OPTIONS_ON_CMDLINE
+#define process_module(a,b) process_module(a)
+#define cmdline_options ""
+#endif
+static void process_module(char *name, const char *cmdline_options)
+{
+       char *s, *deps, *options;
+       module_info *info;
+       int is_rmmod = (option_mask32 & OPT_r) != 0;
+       dbg1_error_msg("process_module('%s','%s')", name, cmdline_options);
+
+       replace(name, '-', '_');
+
+       dbg1_error_msg("already_loaded:%d is_rmmod:%d", already_loaded(name), is_rmmod);
+       if (already_loaded(name) != is_rmmod) {
+               dbg1_error_msg("nothing to do for '%s'", name);
+               return;
+       }
+
+       options = NULL;
+       if (!is_rmmod) {
+               char *opt_filename = xasprintf("/etc/modules/%s", name);
+               options = xmalloc_open_read_close(opt_filename, NULL);
+               if (options)
+                       replace(options, '\n', ' ');
+#if ENABLE_FEATURE_MODPROBE_SMALL_OPTIONS_ON_CMDLINE
+               if (cmdline_options) {
+                       /* NB: cmdline_options always have one leading ' '
+                        * (see main()), we remove it here */
+                       char *op = xasprintf(options ? "%s %s" : "%s %s" + 3,
+                                               cmdline_options + 1, options);
+                       free(options);
+                       options = op;
+               }
+#endif
+               free(opt_filename);
+               module_load_options = options;
+               dbg1_error_msg("process_module('%s'): options:'%s'", name, options);
+       }
+
+       if (!module_count) {
+               /* Scan module directory. This is done only once.
+                * It will attempt module load, and will exit(EXIT_SUCCESS)
+                * on success. */
+               module_found_idx = -1;
+               recursive_action(".",
+                       ACTION_RECURSE, /* flags */
+                       fileAction, /* file action */
+                       NULL, /* dir action */
+                       name, /* user data */
+                       0); /* depth */
+               dbg1_error_msg("dirscan complete");
+               /* Module was not found, or load failed, or is_rmmod */
+               if (module_found_idx >= 0) { /* module was found */
+                       info = &modinfo[module_found_idx];
+               } else { /* search for alias, not a plain module name */
+                       info = find_alias(name);
+               }
+       } else {
+               info = find_alias(name);
+       }
+
+       /* rmmod? unload it by name */
+       if (is_rmmod) {
+               if (delete_module(name, O_NONBLOCK | O_EXCL) != 0
+                && !(option_mask32 & OPT_q)
+               ) {
+                       bb_perror_msg("remove '%s'", name);
+                       goto ret;
+               }
+               /* N.B. we do not stop here -
+                * continue to unload modules on which the module depends:
+                * "-r --remove: option causes modprobe to remove a module.
+                * If the modules it depends on are also unused, modprobe
+                * will try to remove them, too." */
+       }
+
+       if (!info) {
+               /* both dirscan and find_alias found nothing */
+               if (applet_name[0] != 'd') /* it wasn't depmod */
+                       bb_error_msg("module '%s' not found", name);
+//TODO: _and_die()?
+               goto ret;
+       }
+
+       /* Iterate thru dependencies, trying to (un)load them */
+       deps = str_2_list(info->deps);
+       for (s = deps; *s; s += strlen(s) + 1) {
+               //if (strcmp(name, s) != 0) // N.B. do loops exist?
+               dbg1_error_msg("recurse on dep '%s'", s);
+               process_module(s, NULL);
+               dbg1_error_msg("recurse on dep '%s' done", s);
+       }
+       free(deps);
+
+       /* modprobe -> load it */
+       if (!is_rmmod) {
+               if (!options || strstr(options, "blacklist") == NULL) {
+                       errno = 0;
+                       if (load_module(info->pathname, options) != 0) {
+                               if (EEXIST != errno) {
+                                       bb_error_msg("'%s': %s",
+                                               info->pathname,
+                                               moderror(errno));
+                               } else {
+                                       dbg1_error_msg("'%s': %s",
+                                               info->pathname,
+                                               moderror(errno));
+                               }
+                       }
+               } else {
+                       dbg1_error_msg("'%s': blacklisted", info->pathname);
+               }
+       }
+ ret:
+       free(options);
+//TODO: return load attempt result from process_module.
+//If dep didn't load ok, continuing makes little sense.
+}
+#undef cmdline_options
+
+
+/* For reference, module-init-tools v3.4 options:
+
+# insmod
+Usage: insmod filename [args]
+
+# rmmod --help
+Usage: rmmod [-fhswvV] modulename ...
+ -f (or --force) forces a module unload, and may crash your
+    machine. This requires the Forced Module Removal option
+    when the kernel was compiled.
+ -h (or --help) prints this help text
+ -s (or --syslog) says use syslog, not stderr
+ -v (or --verbose) enables more messages
+ -V (or --version) prints the version code
+ -w (or --wait) begins module removal even if it is used
+    and will stop new users from accessing the module (so it
+    should eventually fall to zero).
+
+# modprobe
+Usage: modprobe [-v] [-V] [-C config-file] [-n] [-i] [-q] [-b]
+    [-o <modname>] [ --dump-modversions ] <modname> [parameters...]
+modprobe -r [-n] [-i] [-v] <modulename> ...
+modprobe -l -t <dirname> [ -a <modulename> ...]
+
+# depmod --help
+depmod 3.4 -- part of module-init-tools
+depmod -[aA] [-n -e -v -q -V -r -u]
+      [-b basedirectory] [forced_version]
+depmod [-n -e -v -q -r -u] [-F kernelsyms] module1.ko module2.ko ...
+If no arguments (except options) are given, "depmod -a" is assumed.
+depmod will output a dependency list suitable for the modprobe utility.
+Options:
+    -a, --all           Probe all modules
+    -A, --quick         Only does the work if there's a new module
+    -n, --show          Write the dependency file on stdout only
+    -e, --errsyms       Report not supplied symbols
+    -V, --version       Print the release version
+    -v, --verbose       Enable verbose mode
+    -h, --help          Print this usage message
+The following options are useful for people managing distributions:
+    -b basedirectory
+    --basedir basedirectory
+                        Use an image of a module tree
+    -F kernelsyms
+    --filesyms kernelsyms
+                        Use the file instead of the current kernel symbols
+*/
+
+int modprobe_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int modprobe_main(int argc UNUSED_PARAM, char **argv)
+{
+       struct utsname uts;
+       char applet0 = applet_name[0];
+       USE_FEATURE_MODPROBE_SMALL_OPTIONS_ON_CMDLINE(char *options;)
+
+       /* are we lsmod? -> just dump /proc/modules */
+       if ('l' == applet0) {
+               xprint_and_close_file(xfopen_for_read("/proc/modules"));
+               return EXIT_SUCCESS;
+       }
+
+       INIT_G();
+
+       /* Prevent ugly corner cases with no modules at all */
+       modinfo = xzalloc(sizeof(modinfo[0]));
+
+       if ('i' != applet0) { /* not insmod */
+               /* Goto modules directory */
+               xchdir(CONFIG_DEFAULT_MODULES_DIR);
+       }
+       uname(&uts); /* never fails */
+
+       /* depmod? */
+       if ('d' == applet0) {
+               /* Supported:
+                * -n: print result to stdout
+                * -a: process all modules (default)
+                * optional VERSION parameter
+                * Ignored:
+                * -A: do work only if a module is newer than depfile
+                * -e: report any symbols which a module needs
+                *  which are not supplied by other modules or the kernel
+                * -F FILE: System.map (symbols for -e)
+                * -q, -r, -u: noop?
+                * Not supported:
+                * -b BASEDIR: (TODO!) modules are in
+                *  $BASEDIR/lib/modules/$VERSION
+                * -v: human readable deps to stdout
+                * -V: version (don't want to support it - people may depend
+                *  on it as an indicator of "standard" depmod)
+                * -h: help (well duh)
+                * module1.o module2.o parameters (just ignored for now)
+                */
+               getopt32(argv, "na" "AeF:qru" /* "b:vV", NULL */, NULL);
+               argv += optind;
+               /* if (argv[0] && argv[1]) bb_show_usage(); */
+               /* Goto $VERSION directory */
+               xchdir(argv[0] ? argv[0] : uts.release);
+               /* Force full module scan by asking to find a bogus module.
+                * This will generate modules.dep.bb as a side effect. */
+               process_module((char*)"/", NULL);
+               return !wrote_dep_bb_ok;
+       }
+
+       /* insmod, modprobe, rmmod require at least one argument */
+       opt_complementary = "-1";
+       /* only -q (quiet) and -r (rmmod),
+        * the rest are accepted and ignored (compat) */
+       getopt32(argv, "qrfsvw");
+       argv += optind;
+
+       /* are we rmmod? -> simulate modprobe -r */
+       if ('r' == applet0) {
+               option_mask32 |= OPT_r;
+       }
+
+       if ('i' != applet0) { /* not insmod */
+               /* Goto $VERSION directory */
+               xchdir(uts.release);
+       }
+
+#if ENABLE_FEATURE_MODPROBE_SMALL_OPTIONS_ON_CMDLINE
+       /* If not rmmod, parse possible module options given on command line.
+        * insmod/modprobe takes one module name, the rest are parameters. */
+       options = NULL;
+       if ('r' != applet0) {
+               char **arg = argv;
+               while (*++arg) {
+                       /* Enclose options in quotes */
+                       char *s = options;
+                       options = xasprintf("%s \"%s\"", s ? s : "", *arg);
+                       free(s);
+                       *arg = NULL;
+               }
+       }
+#else
+       if ('r' != applet0)
+               argv[1] = NULL;
+#endif
+
+       if ('i' == applet0) { /* insmod */
+               size_t len;
+               void *map;
+
+               len = MAXINT(ssize_t);
+               map = xmalloc_xopen_read_close(*argv, &len);
+               if (init_module(map, len,
+                       USE_FEATURE_MODPROBE_SMALL_OPTIONS_ON_CMDLINE(options ? options : "")
+                       SKIP_FEATURE_MODPROBE_SMALL_OPTIONS_ON_CMDLINE("")
+                               ) != 0)
+                       bb_error_msg_and_die("can't insert '%s': %s",
+                                       *argv, moderror(errno));
+               return 0;
+       }
+
+       /* Try to load modprobe.dep.bb */
+       load_dep_bb();
+
+       /* Load/remove modules.
+        * Only rmmod loops here, modprobe has only argv[0] */
+       do {
+               process_module(*argv++, options);
+       } while (*argv);
+
+       if (ENABLE_FEATURE_CLEAN_UP) {
+               USE_FEATURE_MODPROBE_SMALL_OPTIONS_ON_CMDLINE(free(options);)
+       }
+       return EXIT_SUCCESS;
+}
diff --git a/modutils/modprobe.c b/modutils/modprobe.c
new file mode 100644 (file)
index 0000000..310eebc
--- /dev/null
@@ -0,0 +1,421 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Modprobe written from scratch for BusyBox
+ *
+ * Copyright (c) 2008 Timo Teras <timo.teras@iki.fi>
+ * Copyright (c) 2008 Vladimir Dronnikov
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+/* Note that unlike older versions of modules.dep/depmod (busybox and m-i-t),
+ * we expect the full dependency list to be specified in modules.dep.  Older
+ * versions would only export the direct dependency list.
+ */
+
+#include "libbb.h"
+#include "modutils.h"
+#include <sys/utsname.h>
+#include <fnmatch.h>
+
+//#define DBG(fmt, ...) bb_error_msg("%s: " fmt, __func__, ## __VA_ARGS__)
+#define DBG(...) ((void)0)
+
+#define MODULE_FLAG_LOADED             0x0001
+#define MODULE_FLAG_NEED_DEPS          0x0002
+/* "was seen in modules.dep": */
+#define MODULE_FLAG_FOUND_IN_MODDEP    0x0004
+#define MODULE_FLAG_BLACKLISTED                0x0008
+
+struct module_entry { /* I'll call it ME. */
+       unsigned flags;
+       char *modname; /* stripped of /path/, .ext and s/-/_/g */
+       const char *probed_name; /* verbatim as seen on cmdline */
+       char *options; /* options from config files */
+       llist_t *realnames; /* strings. if this module is an alias, */
+       /* real module name is one of these. */
+//Can there really be more than one? Example from real kernel?
+       llist_t *deps; /* strings. modules we depend on */
+};
+
+#define MODPROBE_OPTS  "acdlnrt:VC:" USE_FEATURE_MODPROBE_BLACKLIST("b")
+enum {
+       MODPROBE_OPT_INSERT_ALL = (INSMOD_OPT_UNUSED << 0), /* a */
+       MODPROBE_OPT_DUMP_ONLY  = (INSMOD_OPT_UNUSED << 1), /* c */
+       MODPROBE_OPT_D          = (INSMOD_OPT_UNUSED << 2), /* d */
+       MODPROBE_OPT_LIST_ONLY  = (INSMOD_OPT_UNUSED << 3), /* l */
+       MODPROBE_OPT_SHOW_ONLY  = (INSMOD_OPT_UNUSED << 4), /* n */
+       MODPROBE_OPT_REMOVE     = (INSMOD_OPT_UNUSED << 5), /* r */
+       MODPROBE_OPT_RESTRICT   = (INSMOD_OPT_UNUSED << 6), /* t */
+       MODPROBE_OPT_VERONLY    = (INSMOD_OPT_UNUSED << 7), /* V */
+       MODPROBE_OPT_CONFIGFILE = (INSMOD_OPT_UNUSED << 8), /* C */
+       MODPROBE_OPT_BLACKLIST  = (INSMOD_OPT_UNUSED << 9) * ENABLE_FEATURE_MODPROBE_BLACKLIST,
+};
+
+struct globals {
+       llist_t *db; /* MEs of all modules ever seen (caching for speed) */
+       llist_t *probes; /* MEs of module(s) requested on cmdline */
+       char *cmdline_mopts; /* module options from cmdline */
+       int num_unresolved_deps;
+       /* bool. "Did we have 'symbol:FOO' requested on cmdline?" */
+       smallint need_symbols;
+};
+#define G (*(struct globals*)&bb_common_bufsiz1)
+#define INIT_G() do { } while (0)
+
+
+static int read_config(const char *path);
+
+static char *gather_options_str(char *opts, const char *append)
+{
+       /* Speed-optimized. We call gather_options_str many times. */
+       if (opts == NULL) {
+               opts = xstrdup(append);
+       } else {
+               int optlen = strlen(opts);
+               opts = xrealloc(opts, optlen + strlen(append) + 2);
+               sprintf(opts + optlen, " %s", append);
+       }
+       return opts;
+}
+
+static struct module_entry *helper_get_module(const char *module, int create)
+{
+       char modname[MODULE_NAME_LEN];
+       struct module_entry *e;
+       llist_t *l;
+
+       filename2modname(module, modname);
+       for (l = G.db; l != NULL; l = l->link) {
+               e = (struct module_entry *) l->data;
+               if (strcmp(e->modname, modname) == 0)
+                       return e;
+       }
+       if (!create)
+               return NULL;
+
+       e = xzalloc(sizeof(*e));
+       e->modname = xstrdup(modname);
+       llist_add_to(&G.db, e);
+
+       return e;
+}
+static struct module_entry *get_or_add_modentry(const char *module)
+{
+       return helper_get_module(module, 1);
+}
+static struct module_entry *get_modentry(const char *module)
+{
+       return helper_get_module(module, 0);
+}
+
+static void add_probe(const char *name)
+{
+       struct module_entry *m;
+
+       m = get_or_add_modentry(name);
+       if (!(option_mask32 & MODPROBE_OPT_REMOVE)
+        && (m->flags & MODULE_FLAG_LOADED)
+       ) {
+               DBG("skipping %s, it is already loaded", name);
+               return;
+       }
+
+       DBG("queuing %s", name);
+       m->probed_name = name;
+       m->flags |= MODULE_FLAG_NEED_DEPS;
+       llist_add_to_end(&G.probes, m);
+       G.num_unresolved_deps++;
+       if (ENABLE_FEATURE_MODUTILS_SYMBOLS
+        && strncmp(m->modname, "symbol:", 7) == 0
+       ) {
+               G.need_symbols = 1;
+       }
+}
+
+static int FAST_FUNC config_file_action(const char *filename,
+                                       struct stat *statbuf UNUSED_PARAM,
+                                       void *userdata UNUSED_PARAM,
+                                       int depth UNUSED_PARAM)
+{
+       char *tokens[3];
+       parser_t *p;
+        struct module_entry *m;
+       int rc = TRUE;
+
+       if (bb_basename(filename)[0] == '.')
+               goto error;
+
+       p = config_open2(filename, fopen_for_read);
+       if (p == NULL) {
+               rc = FALSE;
+               goto error;
+       }
+
+       while (config_read(p, tokens, 3, 2, "# \t", PARSE_NORMAL)) {
+//Use index_in_strings?
+               if (strcmp(tokens[0], "alias") == 0) {
+                       /* alias <wildcard> <modulename> */
+                       llist_t *l;
+                       char wildcard[MODULE_NAME_LEN];
+                       char *rmod;
+
+                       if (tokens[2] == NULL)
+                               continue;
+                       filename2modname(tokens[1], wildcard);
+
+                       for (l = G.probes; l != NULL; l = l->link) {
+                               m = (struct module_entry *) l->data;
+                               if (fnmatch(wildcard, m->modname, 0) != 0)
+                                       continue;
+                               rmod = filename2modname(tokens[2], NULL);
+                               llist_add_to(&m->realnames, rmod);
+
+                               if (m->flags & MODULE_FLAG_NEED_DEPS) {
+                                       m->flags &= ~MODULE_FLAG_NEED_DEPS;
+                                       G.num_unresolved_deps--;
+                               }
+
+                               m = get_or_add_modentry(rmod);
+                               if (!(m->flags & MODULE_FLAG_NEED_DEPS)) {
+                                       m->flags |= MODULE_FLAG_NEED_DEPS;
+                                       G.num_unresolved_deps++;
+                               }
+                       }
+               } else if (strcmp(tokens[0], "options") == 0) {
+                       /* options <modulename> <option...> */
+                       if (tokens[2] == NULL)
+                               continue;
+                       m = get_or_add_modentry(tokens[1]);
+                       m->options = gather_options_str(m->options, tokens[2]);
+               } else if (strcmp(tokens[0], "include") == 0) {
+                       /* include <filename> */
+                       read_config(tokens[1]);
+               } else if (ENABLE_FEATURE_MODPROBE_BLACKLIST
+                && strcmp(tokens[0], "blacklist") == 0
+               ) {
+                       /* blacklist <modulename> */
+                       get_or_add_modentry(tokens[1])->flags |= MODULE_FLAG_BLACKLISTED;
+               }
+       }
+       config_close(p);
+error:
+       return rc;
+}
+
+static int read_config(const char *path)
+{
+       return recursive_action(path, ACTION_RECURSE | ACTION_QUIET,
+                               config_file_action, NULL, NULL, 1);
+}
+
+static int do_modprobe(struct module_entry *m)
+{
+       struct module_entry *m2 = m2; /* for compiler */
+       char *fn, *options;
+       int rc, first;
+       llist_t *l;
+
+       if (!(m->flags & MODULE_FLAG_FOUND_IN_MODDEP)) {
+               DBG("skipping %s, not found in modules.dep", m->modname);
+               return -ENOENT;
+       }
+       DBG("do_modprob'ing %s", m->modname);
+
+       if (!(option_mask32 & MODPROBE_OPT_REMOVE))
+               m->deps = llist_rev(m->deps);
+
+       for (l = m->deps; l != NULL; l = l->link)
+               DBG("dep: %s", l->data);
+
+       first = 1;
+       rc = 0;
+       while (m->deps && rc == 0) {
+               fn = llist_pop(&m->deps);
+               m2 = get_or_add_modentry(fn);
+               if (option_mask32 & MODPROBE_OPT_REMOVE) {
+                       if (m2->flags & MODULE_FLAG_LOADED) {
+                               if (bb_delete_module(m2->modname, O_EXCL) != 0) {
+                                       if (first)
+                                               rc = errno;
+                               } else {
+                                       m2->flags &= ~MODULE_FLAG_LOADED;
+                               }
+                       }
+                       /* do not error out if *deps* fail to unload */
+                       first = 0;
+               } else if (!(m2->flags & MODULE_FLAG_LOADED)) {
+                       options = m2->options;
+                       m2->options = NULL;
+                       if (m == m2)
+                               options = gather_options_str(options, G.cmdline_mopts);
+                       rc = bb_init_module(fn, options);
+                       DBG("loaded %s '%s', rc:%d", fn, options, rc);
+                       if (rc == 0)
+                               m2->flags |= MODULE_FLAG_LOADED;
+                       free(options);
+               } else {
+                       DBG("%s is already loaded, skipping", fn);
+               }
+
+               free(fn);
+       }
+
+       if (rc && !(option_mask32 & INSMOD_OPT_SILENT)) {
+               bb_error_msg("failed to %sload module %s: %s",
+                       (option_mask32 & MODPROBE_OPT_REMOVE) ? "un" : "",
+                       m2->probed_name ? m2->probed_name : m2->modname,
+                       moderror(rc)
+               );
+       }
+
+       return rc;
+}
+
+static void load_modules_dep(void)
+{
+       struct module_entry *m;
+       char *colon, *tokens[2];
+       parser_t *p;
+
+       /* Modprobe does not work at all without modprobe.dep,
+        * even if the full module name is given. Returning error here
+        * was making us later confuse user with this message:
+        * "module /full/path/to/existing/file/module.ko not found".
+        * It's better to die immediately, with good message.
+        * xfopen_for_read provides that. */
+       p = config_open2(CONFIG_DEFAULT_DEPMOD_FILE, xfopen_for_read);
+
+       while (G.num_unresolved_deps
+        && config_read(p, tokens, 2, 1, "# \t", PARSE_NORMAL)
+       ) {
+               colon = last_char_is(tokens[0], ':');
+               if (colon == NULL)
+                       continue;
+               *colon = 0;
+
+               m = get_modentry(tokens[0]);
+               if (m == NULL)
+                       continue;
+
+               /* Optimization... */
+               if ((m->flags & MODULE_FLAG_LOADED)
+                && !(option_mask32 & MODPROBE_OPT_REMOVE)
+               ) {
+                       DBG("skip deps of %s, it's already loaded", tokens[0]);
+                       continue;
+               }
+
+               m->flags |= MODULE_FLAG_FOUND_IN_MODDEP;
+               if ((m->flags & MODULE_FLAG_NEED_DEPS) && (m->deps == NULL)) {
+                       G.num_unresolved_deps--;
+                       llist_add_to(&m->deps, xstrdup(tokens[0]));
+                       if (tokens[1])
+                               string_to_llist(tokens[1], &m->deps, " ");
+               } else
+                       DBG("skipping dep line");
+       }
+       config_close(p);
+}
+
+int modprobe_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int modprobe_main(int argc UNUSED_PARAM, char **argv)
+{
+       struct utsname uts;
+       int rc;
+       unsigned opt;
+       struct module_entry *me;
+
+       opt_complementary = "q-v:v-q";
+       opt = getopt32(argv, INSMOD_OPTS MODPROBE_OPTS INSMOD_ARGS, NULL, NULL);
+       argv += optind;
+
+       if (opt & (MODPROBE_OPT_DUMP_ONLY | MODPROBE_OPT_LIST_ONLY |
+                               MODPROBE_OPT_SHOW_ONLY))
+               bb_error_msg_and_die("not supported");
+
+       if (!argv[0]) {
+               if (opt & MODPROBE_OPT_REMOVE) {
+                       /* "modprobe -r" (w/o params).
+                        * "If name is NULL, all unused modules marked
+                        * autoclean will be removed".
+                        */
+                       if (bb_delete_module(NULL, O_NONBLOCK|O_EXCL) != 0)
+                               bb_perror_msg_and_die("rmmod");
+               }
+               return EXIT_SUCCESS;
+       }
+
+       /* Goto modules location */
+       xchdir(CONFIG_DEFAULT_MODULES_DIR);
+       uname(&uts);
+       xchdir(uts.release);
+
+       /* Retrieve module names of already loaded modules */
+       {
+               char *s;
+               parser_t *parser = config_open2("/proc/modules", fopen_for_read);
+               while (config_read(parser, &s, 1, 1, "# \t", PARSE_NORMAL & ~PARSE_GREEDY))
+                       get_or_add_modentry(s)->flags |= MODULE_FLAG_LOADED;
+               config_close(parser);
+       }
+
+       if (opt & (MODPROBE_OPT_INSERT_ALL | MODPROBE_OPT_REMOVE)) {
+               /* Each argument is a module name */
+               do {
+                       DBG("adding module %s", *argv);
+                       add_probe(*argv++);
+               } while (*argv);
+       } else {
+               /* First argument is module name, rest are parameters */
+               DBG("probing just module %s", *argv);
+               add_probe(argv[0]);
+               G.cmdline_mopts = parse_cmdline_module_options(argv);
+       }
+
+       /* Happens if all requested modules are already loaded */
+       if (G.probes == NULL)
+               return EXIT_SUCCESS;
+
+       read_config("/etc/modprobe.conf");
+       read_config("/etc/modprobe.d");
+       if (ENABLE_FEATURE_MODUTILS_SYMBOLS && G.need_symbols)
+               read_config("modules.symbols");
+       load_modules_dep();
+       if (ENABLE_FEATURE_MODUTILS_ALIAS && G.num_unresolved_deps) {
+               read_config("modules.alias");
+               load_modules_dep();
+       }
+
+       while ((me = llist_pop(&G.probes)) != NULL) {
+               if (me->realnames == NULL) {
+                       /* This is not an alias. Literal names are blacklisted
+                        * only if '-b' is given.
+                        */
+                       if (!(opt & MODPROBE_OPT_BLACKLIST)
+                        || !(me->flags & MODULE_FLAG_BLACKLISTED)
+                       ) {
+                               rc = do_modprobe(me);
+//FIXME: what if rc > 0?
+                               if (rc < 0 && !(opt & INSMOD_OPT_SILENT))
+                                       bb_error_msg("module %s not found",
+                                                    me->probed_name);
+                       }
+               } else {
+                       /* Probe all realnames */
+                       do {
+                               char *realname = llist_pop(&me->realnames);
+                               struct module_entry *m2;
+
+                               DBG("probing %s by realname %s", me->modname, realname);
+                               m2 = get_or_add_modentry(realname);
+                               if (!(m2->flags & MODULE_FLAG_BLACKLISTED))
+                                       do_modprobe(m2);
+//FIXME: error check?
+                               free(realname);
+                       } while (me->realnames != NULL);
+               }
+       }
+
+       return EXIT_SUCCESS;
+}
diff --git a/modutils/modutils-24.c b/modutils/modutils-24.c
new file mode 100644 (file)
index 0000000..a16cb1b
--- /dev/null
@@ -0,0 +1,3905 @@
+/* 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 <andersen@codepoet.org>
+ * and Ron Alder <alder@lineo.com>
+ *
+ * Rodney Radford <rradford@mindspring.com> 17-Aug-2004.
+ *   Added x86_64 support.
+ *
+ * Miles Bader <miles@gnu.org> added NEC V850E support.
+ *
+ * Modified by Bryan Rittmeyer <bryan@ixiacom.com> to support SH4
+ * and (theoretically) SH3. I have only tested SH4 in little endian mode.
+ *
+ * Modified by Alcove, Julien Gaulmin <julien.gaulmin@alcove.fr> and
+ * Nicolas Ferre <nicolas.ferre@alcove.fr> to support ARM7TDMI.  Only
+ * very minor changes required to also work with StrongArm and presumably
+ * all ARM based systems.
+ *
+ * Yoshinori Sato <ysato@users.sourceforge.jp> 19-May-2004.
+ *   added Renesas H8/300 support.
+ *
+ * Paul Mundt <lethal@linux-sh.org> 08-Aug-2003.
+ *   Integrated support for sh64 (SH-5), from preliminary modutils
+ *   patches from Benedict Gaster <benedict.gaster@superh.com>.
+ *   Currently limited to support for 32bit ABI.
+ *
+ * Magnus Damm <damm@opensource.se> 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 <damm@opensource.se> 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 <jensenq@lineo.com> 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 <ralf@gnu.ai.mit.edu>
+ *
+ * Based almost entirely on the Linux modutils-2.3.11 implementation.
+ *   Copyright 1996, 1997 Linux International.
+ *   New implementation contributed by Richard Henderson <rth@tamu.edu>
+ *   Based on original work by Bjorn Ekwall <bj0rn@blox.se>
+ *   Restructured (and partly rewritten) by:
+ *   Björn Ekwall <bj0rn@blox.se> February 1999
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.h"
+#include "modutils.h"
+#include <libgen.h>
+#include <sys/utsname.h>
+
+#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
+#include <linux/elf-em.h>
+#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 <rth@tamu.edu>
+
+   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
+
+/*======================================================================*/
+/* 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 *module, unsigned int flags);
+
+
+#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 <rth@tamu.edu>
+
+   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
+
+/* The relocatable object is manipulated using elfin types.  */
+
+#include <elf.h>
+#include <endian.h>
+
+#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 void obj_string_patch(struct obj_file *f, int secidx, ElfW(Addr) offset,
+                    const char *string);
+
+static void obj_symbol_patch(struct obj_file *f, int secidx, ElfW(Addr) offset,
+                    struct obj_symbol *sym);
+
+static void 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);
+
+#if !LOADBITS
+#define obj_load(image, image_size, loadprogbits) \
+       obj_load(image, image_size)
+#endif
+static struct obj_file *obj_load(char *image, size_t image_size, 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
+#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
+
+enum { STRVERSIONLEN = 64 };
+
+/*======================================================================*/
+
+#define flag_force_load (option_mask32 & INSMOD_OPT_FORCE)
+#define flag_autoclean (option_mask32 & INSMOD_OPT_KERNELD)
+#define flag_verbose (option_mask32 & INSMOD_OPT_VERBOSE)
+#define flag_quiet (option_mask32 & INSMOD_OPT_SILENT)
+#define flag_noexport (option_mask32 & INSMOD_OPT_NO_EXPORT)
+#define flag_print_load_map (option_mask32 & INSMOD_OPT_PRINT_MAP)
+
+/*======================================================================*/
+
+#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;
+
+/*======================================================================*/
+
+
+static struct obj_file *arch_new_file(void)
+{
+       struct arch_file *f;
+       f = xzalloc(sizeof(*f));
+       return &f->root; /* it's a first member */
+}
+
+static struct obj_section *arch_new_section(void)
+{
+       return xzalloc(sizeof(struct obj_section));
+}
+
+static struct obj_symbol *arch_new_symbol(void)
+{
+       struct arch_symbol *sym;
+       sym = xzalloc(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)
+{
+#if defined(__arm__) || defined(__i386__) || defined(__mc68000__) \
+ || defined(__sh__) || defined(__s390__) || defined(__x86_64__) \
+ || defined(__powerpc__) || defined(__mips__)
+       struct arch_file *ifile = (struct arch_file *) f;
+#endif
+       enum obj_reloc ret = obj_reloc_ok;
+       ElfW(Addr) *loc = (ElfW(Addr) *) (targsec->contents + rel->r_offset);
+#if defined(__arm__) || defined(__H8300H__) || defined(__H8300S__) \
+ || defined(__i386__) || defined(__mc68000__) || defined(__microblaze__) \
+ || defined(__mips__) || defined(__nios2__) || defined(__powerpc__) \
+ || defined(__s390__) || defined(__sh__) || defined(__x86_64__)
+       ElfW(Addr) dot = targsec->header.sh_addr + rel->r_offset;
+#endif
+#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:
+               case R_386_GOTOFF:
+                       *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;
+                       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 = 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 = xzalloc(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, const 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;
+               g = (h & 0xf0000000);
+               if (g != 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 != (unsigned long)(-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;
+       }
+       /* 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") != 0
+       ) {
+               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 *helper_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_vector(f->sections, 2, newidx);
+       f->sections[newidx] = sec = arch_new_section();
+
+       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 = xzalloc(size);
+
+       return sec;
+}
+
+static struct obj_section *obj_create_alloced_section(struct obj_file *f,
+                               const char *name,
+                               unsigned long align,
+                               unsigned long size)
+{
+       struct obj_section *sec;
+
+       sec = helper_create_alloced_section(f, name, align, 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)
+{
+       struct obj_section *sec;
+
+       sec = helper_create_alloced_section(f, name, align, 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->header.sh_size += more;
+               sec->contents = xrealloc(sec->contents, sec->header.sh_size);
+       }
+       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 = NULL;
+       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
+
+               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.  */
+
+/* From Linux-2.6 sources */
+/* You can use " around spaces, but can't escape ". */
+/* Hyphens and underscores equivalent in parameter names. */
+static char *next_arg(char *args, char **param, char **val)
+{
+       unsigned int i, equals = 0;
+       int in_quote = 0, quoted = 0;
+       char *next;
+
+       if (*args == '"') {
+               args++;
+               in_quote = 1;
+               quoted = 1;
+       }
+
+       for (i = 0; args[i]; i++) {
+               if (args[i] == ' ' && !in_quote)
+                       break;
+               if (equals == 0) {
+                       if (args[i] == '=')
+                               equals = i;
+               }
+               if (args[i] == '"')
+                       in_quote = !in_quote;
+       }
+
+       *param = args;
+       if (!equals)
+               *val = NULL;
+       else {
+               args[equals] = '\0';
+               *val = args + equals + 1;
+
+               /* Don't include quotes in value. */
+               if (**val == '"') {
+                       (*val)++;
+                       if (args[i-1] == '"')
+                               args[i-1] = '\0';
+               }
+               if (quoted && args[i-1] == '"')
+                       args[i-1] = '\0';
+       }
+
+       if (args[i]) {
+               args[i] = '\0';
+               next = args + i + 1;
+       } else
+               next = args + i;
+
+       /* Chew up trailing spaces. */
+       return skip_whitespace(next);
+}
+
+static void
+new_process_module_arguments(struct obj_file *f, const char *options)
+{
+       char *xoptions, *pos;
+       char *param, *val;
+
+       xoptions = pos = xstrdup(skip_whitespace(options));
+       while (*pos) {
+               unsigned long charssize = 0;
+               char *tmp, *contents, *loc, *pinfo, *p;
+               struct obj_symbol *sym;
+               int min, max, n, len;
+
+               pos = next_arg(pos, &param, &val);
+
+               tmp = xasprintf("parm_%s", param);
+               pinfo = get_modinfo_value(f, tmp);
+               free(tmp);
+               if (pinfo == NULL)
+                       bb_error_msg_and_die("invalid parameter %s", param);
+
+#ifdef SYMBOL_PREFIX
+               tmp = xasprintf(SYMBOL_PREFIX "%s", param);
+               sym = obj_find_symbol(f, tmp);
+               free(tmp);
+#else
+               sym = obj_find_symbol(f, param);
+#endif
+
+               /* Also check that the parameter was not resolved from the kernel.  */
+               if (sym == NULL || sym->secidx > SHN_HIRESERVE)
+                       bb_error_msg_and_die("symbol for parameter %s not found", param);
+
+               /* Number of parameters */
+               if (isdigit(*pinfo)) {
+                       min = strtoul(pinfo, &pinfo, 10);
+                       if (*pinfo == '-')
+                               max = strtoul(pinfo + 1, &pinfo, 10);
+                       else
+                               max = min;
+               } else
+                       min = max = 1;
+
+               contents = f->sections[sym->secidx]->contents;
+               loc = contents + sym->value;
+
+               if (*pinfo == 'c') {
+                       if (!isdigit(*(pinfo + 1))) {
+                               bb_error_msg_and_die("parameter type 'c' for %s must be followed by"
+                                                    " the maximum size", param);
+                       }
+                       charssize = strtoul(pinfo + 1, (char **) NULL, 10);
+               }
+
+               if (val == NULL) {
+                       if (*pinfo != 'b')
+                               bb_error_msg_and_die("argument expected for parameter %s", param);
+                       val = (char *) "1";
+               }
+
+               /* Parse parameter values */
+               n = 0;
+               p = val;
+               while (*p != 0) {
+                       if (++n > max)
+                               bb_error_msg_and_die("too many values for %s (max %d)", param, max);
+
+                       switch (*pinfo) {
+                       case 's':
+                               len = strcspn(p, ",");
+                               p[len] = 0;
+                               obj_string_patch(f, sym->secidx,
+                                                loc - contents, p);
+                               loc += tgt_sizeof_char_p;
+                               p += len;
+                               break;
+                       case 'c':
+                               len = strcspn(p, ",");
+                               p[len] = 0;
+                               if (len >= charssize)
+                                       bb_error_msg_and_die("string too long for %s (max %ld)", param,
+                                                            charssize - 1);
+                               strcpy((char *) loc, p);
+                               loc += charssize;
+                               p += len;
+                               break;
+                       case 'b':
+                               *loc++ = strtoul(p, &p, 0);
+                               break;
+                       case 'h':
+                               *(short *) loc = strtoul(p, &p, 0);
+                               loc += tgt_sizeof_short;
+                               break;
+                       case 'i':
+                               *(int *) loc = strtoul(p, &p, 0);
+                               loc += tgt_sizeof_int;
+                               break;
+                       case 'l':
+                               *(long *) loc = strtoul(p, &p, 0);
+                               loc += tgt_sizeof_long;
+                               break;
+                       default:
+                               bb_error_msg_and_die("unknown parameter type '%c' for %s",
+                                                    *pinfo, param);
+                       }
+
+                       p = skip_whitespace(p);
+                       if (*p != ',')
+                               break;
+                       p = skip_whitespace(p + 1);
+               }
+
+               if (n < min)
+                       bb_error_msg_and_die("parameter %s requires at least %d arguments", param, min);
+               if (*p != '\0')
+                       bb_error_msg_and_die("invalid argument syntax for %s", param);
+       }
+
+       free(xoptions);
+}
+
+#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);
+       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 void 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.  */
+
+       bufsize = 256;
+       module_names = xmalloc(bufsize);
+
+ retry_modules_load:
+       if (query_module(NULL, QM_MODULES, module_names, bufsize, &ret)) {
+               if (errno == ENOSPC && bufsize < ret) {
+                       bufsize = ret;
+                       module_names = xrealloc(module_names, bufsize);
+                       goto retry_modules_load;
+               }
+               bb_perror_msg_and_die("QM_MODULES");
+       }
+
+       n_ext_modules = nmod = ret;
+
+       /* Collect the modules' symbols.  */
+
+       if (nmod) {
+               ext_modules = modules = xzalloc(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_and_die("query_module: QM_INFO: %s", mn);
+                       }
+
+                       bufsize = 1024;
+                       syms = xmalloc(bufsize);
+ retry_mod_sym_load:
+                       if (query_module(mn, QM_SYMBOLS, syms, bufsize, &ret)) {
+                               switch (errno) {
+                                       case ENOSPC:
+                                               bufsize = ret;
+                                               syms = xrealloc(syms, bufsize);
+                                               goto retry_mod_sym_load;
+                                       case ENOENT:
+                                               /* The module was removed out from underneath us.  */
+                                               continue;
+                                       default:
+                                               bb_perror_msg_and_die("query_module: QM_SYMBOLS: %s", mn);
+                               }
+                       }
+                       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.  */
+
+       bufsize = 16 * 1024;
+       syms = xmalloc(bufsize);
+ retry_kern_sym_load:
+       if (query_module(NULL, QM_SYMBOLS, syms, bufsize, &ret)) {
+               if (errno == ENOSPC && bufsize < ret) {
+                       bufsize = ret;
+                       syms = xrealloc(syms, bufsize);
+                       goto retry_kern_sym_load;
+               }
+               bb_perror_msg_and_die("kernel: QM_SYMBOLS");
+       }
+       nksyms = nsyms = ret;
+       ksyms = syms;
+
+       for (j = 0, s = syms; j < nsyms; ++j, ++s) {
+               s->name += (unsigned long) syms;
+       }
+}
+
+
+/* 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 void 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));
+       /* done by obj_create_alloced_section_first: */
+       /*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);
+}
+
+#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.  */
+               i = f->header.e_shnum;
+               loaded = alloca(sizeof(int) * i);
+               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 void
+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 = xzalloc(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);
+}
+
+static void
+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;
+}
+
+static void obj_check_undefineds(struct obj_file *f)
+{
+       unsigned 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_UNDEF) {
+                               if (ELF_ST_BIND(sym->info) == STB_WEAK) {
+                                       sym->secidx = SHN_ABS;
+                                       sym->value = 0;
+                               } else {
+                                       if (!flag_quiet)
+                                               bb_error_msg_and_die("unresolved symbol %s", sym->name);
+                               }
+                       }
+               }
+       }
+}
+
+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;
+                               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->header.e_shnum++;
+                       f->sections = xrealloc_vector(f->sections, 2, i);
+                       f->sections[i] = sec = arch_new_section();
+
+                       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) {
+                       s->contents = NULL;
+                       if (s->header.sh_size != 0)
+                               s->contents = xzalloc(s->header.sh_size);
+                       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 = NULL;
+                       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(char *image, size_t image_size, int loadprogbits)
+{
+#if BB_LITTLE_ENDIAN
+# define ELFMAG_U32 ((uint32_t)(ELFMAG0 + 0x100 * (ELFMAG1 + (0x100 * (ELFMAG2 + 0x100 * ELFMAG3)))))
+#else
+# define ELFMAG_U32 ((uint32_t)((((ELFMAG0 * 0x100) + ELFMAG1) * 0x100 + ELFMAG2) * 0x100 + ELFMAG3))
+#endif
+       struct obj_file *f;
+       ElfW(Shdr) * section_headers;
+       size_t shnum, i;
+       char *shstrtab;
+
+       /* Read the file header.  */
+
+       f = arch_new_file();
+       f->symbol_cmp = strcmp;
+       f->symbol_hash = obj_elf_hash;
+       f->load_order_search_start = &f->load_order;
+
+       if (image_size < sizeof(f->header))
+               bb_error_msg_and_die("error while loading ELF header");
+       memcpy(&f->header, image, sizeof(f->header));
+
+       if (*(uint32_t*)(&f->header.e_ident) != ELFMAG_U32) {
+               bb_error_msg_and_die("not an ELF file");
+       }
+       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_and_die("ELF file not for this architecture");
+       }
+       if (f->header.e_type != ET_REL) {
+               bb_error_msg_and_die("ELF file not a relocatable object");
+       }
+
+       /* Read the section headers.  */
+
+       if (f->header.e_shentsize != sizeof(ElfW(Shdr))) {
+               bb_error_msg_and_die("section header size mismatch: %lu != %lu",
+                               (unsigned long) f->header.e_shentsize,
+                               (unsigned long) sizeof(ElfW(Shdr)));
+       }
+
+       shnum = f->header.e_shnum;
+       /* Growth of ->sections vector will be done by
+        * xrealloc_vector(..., 2, ...), therefore we must allocate
+        * at least 2^2 = 4 extra elements here. */
+       f->sections = xzalloc(sizeof(f->sections[0]) * (shnum + 4));
+
+       section_headers = alloca(sizeof(ElfW(Shdr)) * shnum);
+       if (image_size < f->header.e_shoff + sizeof(ElfW(Shdr)) * shnum)
+               bb_error_msg_and_die("error while loading section headers");
+       memcpy(section_headers, image + f->header.e_shoff, sizeof(ElfW(Shdr)) * shnum);
+
+       /* Read the section data.  */
+
+       for (i = 0; i < shnum; ++i) {
+               struct obj_section *sec;
+
+               f->sections[i] = sec = arch_new_section();
+
+               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:
+                               sec->contents = NULL;
+                               if (sec->header.sh_size > 0) {
+                                       sec->contents = xmalloc(sec->header.sh_size);
+                                       if (image_size < (sec->header.sh_offset + sec->header.sh_size))
+                                               bb_error_msg_and_die("error while loading section data");
+                                       memcpy(sec->contents, image + sec->header.sh_offset, sec->header.sh_size);
+                               }
+                               break;
+#if SHT_RELM == SHT_REL
+                       case SHT_RELA:
+                               bb_error_msg_and_die("RELA relocations not supported on this architecture");
+#else
+                       case SHT_REL:
+                               bb_error_msg_and_die("REL relocations not supported on this architecture");
+#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_and_die("can't handle sections of type %ld",
+                                               (long) sec->header.sh_type);
+                       }
+               }
+       }
+
+       /* 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_and_die("symbol size mismatch: %lu != %lu",
+                                                       (unsigned long) sec->header.sh_entsize,
+                                                       (unsigned long) sizeof(ElfW(Sym)));
+                               }
+
+                               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_and_die("relocation entry size mismatch: %lu != %lu",
+                                               (unsigned long) sec->header.sh_entsize,
+                                               (unsigned long) sizeof(ElfW(RelM)));
+                       }
+                       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(char *image, size_t image_size, 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);
+               if (image_size < (sec->header.sh_offset + sec->header.sh_size)) {
+                       bb_error_msg("error reading ELF section data");
+                       return 0; /* need to delete half-loaded module! */
+               }
+               memcpy(sec->contents, image + sec->header.sh_offset, sec->header.sh_size);
+       }
+       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) {
+                               unsigned i;
+                               if (license)
+                                       *license = value+1;
+                               for (i = 0; i < ARRAY_SIZE(gpl_licenses); ++i) {
+                                       if (strcmp(value+1, gpl_licenses[i]) == 0)
+                                               return 0;
+                               }
+                               return 2;
+                       }
+                       ptr = strchr(ptr, '\0');
+                       if (ptr)
+                               ptr++;
+                       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(int fd, const char *m_name,
+               int kernel_has_tainted, int taint, const char *text1, const char *text2)
+{
+       static smallint printed_info;
+
+       char buf[80];
+       int oldval;
+
+       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 (!printed_info) {
+               printf("  See %s for information about tainted modules\n", TAINT_URL);
+               printed_info = 1;
+       }
+       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);
+               xwrite_str(fd, buf);
+       }
+}
+
+/* Check if loading this module will taint the kernel. */
+static void check_tainted_module(struct obj_file *f, const char *m_name)
+{
+       static const char tainted_file[] ALIGN1 = 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(fd, m_name, kernel_has_tainted, TAINT_PROPRIETORY_MODULE, "no license", "");
+                       break;
+               default: /* 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(fd, m_name, 1, TAINT_PROPRIETORY_MODULE, "non-GPL license - ", ptr);
+                       break;
+       }
+
+       if (flag_force_load)
+               set_tainted(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
+
+#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.
+ */
+#if !ENABLE_FEATURE_INSMOD_VERSION_CHECKING
+#define get_module_version(f, str) get_module_version(str)
+#endif
+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
+       strncpy(str, "???", sizeof(str));
+       return -1;
+#endif
+}
+
+/* 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[] ALIGN1 = "__insmod_";
+       static const char section_names[][8] = {
+               ".text",
+               ".rodata",
+               ".data",
+               ".bss",
+               ".sbss"
+       };
+
+       struct obj_section *sec;
+       struct obj_symbol *sym;
+       char *name, *absolute_filename;
+       char str[STRVERSIONLEN];
+       unsigned i;
+       int lm_name, lfilename, use_ksymtab, version;
+       struct stat statbuf;
+
+       /* WARNING: was using realpath, but replaced by readlink to stop using
+        * lots of stack. But here it seems to be able to cause problems? */
+       absolute_filename = xmalloc_readlink(filename);
+       if (!absolute_filename)
+               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;
+
+       sec = obj_find_section(f, ".this");
+       if (sec) {
+               /* 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.
+                */
+               if (stat(absolute_filename, &statbuf) != 0)
+                       statbuf.st_mtime = 0;
+               version = get_module_version(f, str);   /* -1 if not found */
+               name = xasprintf("%s%s_O%s_M%0*lX_V%d",
+                               symprefix, m_name, absolute_filename,
+                               (int)(2 * sizeof(statbuf.st_mtime)),
+                               (long)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) {
+               name = xasprintf("%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
+       /* tag the desired sections if size is non-zero */
+       for (i = 0; i < ARRAY_SIZE(section_names); ++i) {
+               sec = obj_find_section(f, section_names[i]);
+               if (sec && sec->header.sh_size) {
+                       name = xasprintf("%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_section *sec;
+#if ENABLE_FEATURE_INSMOD_LOAD_MAP_FULL
+       struct obj_symbol **all, **p;
+       int i, nsyms;
+       char *loaded; /* array of booleans */
+       struct obj_symbol *sym;
+#endif
+       /* 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 indices are loaded.  */
+       i = f->header.e_shnum;
+       loaded = alloca(i * sizeof(loaded[0]));
+       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(all[0]));
+
+       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 |= 0x20; /* tolower. safe for '?' too */
+
+               printf("%0*lx %c %s\n", (int) (2 * sizeof(void *)), value,
+                               type, sym->name);
+       }
+#endif
+}
+#else /* !FEATURE_INSMOD_LOAD_MAP */
+static void print_load_map(struct obj_file *f UNUSED_PARAM)
+{
+}
+#endif
+
+int FAST_FUNC bb_init_module_24(const char *m_filename, const char *options)
+{
+       int k_crcs;
+       unsigned long m_size;
+       ElfW(Addr) m_addr;
+       struct obj_file *f;
+       int exit_status = EXIT_FAILURE;
+       char *m_name;
+#if ENABLE_FEATURE_INSMOD_VERSION_CHECKING
+       int m_has_modinfo;
+#endif
+       char *image;
+       size_t image_size = 64 * 1024 * 1024;
+
+       /* Load module into memory and unzip if compressed */
+       image = xmalloc_open_zipped_read_close(m_filename, &image_size);
+       if (!image)
+               return EXIT_FAILURE;
+
+       m_name = xstrdup(bb_basename(m_filename));
+       /* "module.o[.gz]" -> "module" */
+       *strchrnul(m_name, '.') = '\0';
+
+       f = obj_load(image, image_size, LOADBITS);
+
+#if ENABLE_FEATURE_INSMOD_VERSION_CHECKING
+       /* Version correspondence?  */
+       m_has_modinfo = (get_modinfo_value(f, "kernel_version") != NULL);
+       if (!flag_quiet) {
+               char m_strversion[STRVERSIONLEN];
+               struct utsname uts;
+
+               if (m_has_modinfo) {
+                       int m_version = new_get_module_version(f, m_strversion);
+                       if (m_version == -1) {
+                               bb_error_msg_and_die("can't find the kernel version "
+                                       "the module was compiled for");
+                       }
+               }
+
+               uname(&uts);
+               if (strncmp(uts.release, m_strversion, STRVERSIONLEN) != 0) {
+                       bb_error_msg("%skernel-module version mismatch\n"
+                               "\t%s was compiled for kernel version %s\n"
+                               "\twhile this kernel is version %s",
+                               flag_force_load ? "warning: " : "",
+                               m_name, m_strversion, uts.release);
+                       if (!flag_force_load)
+                               goto out;
+               }
+       }
+#endif
+
+       if (query_module(NULL, 0, NULL, 0, NULL))
+               bb_error_msg_and_die("old (unsupported) kernel");
+       new_get_kernel_symbols();
+       k_crcs = new_is_kernel_checksummed();
+
+#if ENABLE_FEATURE_INSMOD_VERSION_CHECKING
+       {
+               int 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
+
+       /* Let the module know about the kernel symbols.  */
+       add_kernel_symbols(f);
+
+       /* Allocate common symbols, symbol tables, and string tables.  */
+       new_create_this_module(f, m_name);
+       obj_check_undefineds(f);
+       obj_allocate_commons(f);
+       check_tainted_module(f, m_name);
+
+       /* Done with the module name, on to the optional var=value arguments */
+       new_process_module_arguments(f, options);
+
+       arch_create_got(f);
+       hide_special_symbols(f);
+
+#if ENABLE_FEATURE_INSMOD_KSYMOOPS_SYMBOLS
+       add_ksymoops_symbols(f, m_filename, m_name);
+#endif
+
+       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 == (ElfW(Addr))(-1)) switch (errno) {
+       case EEXIST:
+               bb_error_msg_and_die("a module named %s already exists", m_name);
+       case ENOMEM:
+               bb_error_msg_and_die("can't allocate kernel memory for module; needed %lu bytes",
+                               m_size);
+       default:
+               bb_perror_msg_and_die("create_module: %s", m_name);
+       }
+
+#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(image, image_size, f, (char*)m_addr)) {
+               delete_module(m_name, 0);
+               goto out;
+       }
+#endif
+
+       if (!obj_relocate(f, m_addr)) {
+               delete_module(m_name, 0);
+               goto out;
+       }
+
+       if (!new_init_module(m_name, f, m_size)) {
+               delete_module(m_name, 0);
+               goto out;
+       }
+
+       if (flag_print_load_map)
+               print_load_map(f);
+
+       exit_status = EXIT_SUCCESS;
+
+ out:
+       free(image);
+       free(m_name);
+
+       return exit_status;
+}
diff --git a/modutils/modutils.c b/modutils/modutils.c
new file mode 100644 (file)
index 0000000..f437a98
--- /dev/null
@@ -0,0 +1,129 @@
+/*
+ * Common modutils related functions for busybox
+ *
+ * Copyright (C) 2008 by Timo Teras <timo.teras@iki.fi>
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+#include "modutils.h"
+
+#ifdef __UCLIBC__
+extern int init_module(void *module, unsigned long len, const char *options);
+extern int delete_module(const char *module, unsigned int flags);
+#else
+# include <sys/syscall.h>
+# define init_module(mod, len, opts) syscall(__NR_init_module, mod, len, opts)
+# define delete_module(mod, flags) syscall(__NR_delete_module, mod, flags)
+#endif
+
+void FAST_FUNC replace(char *s, char what, char with)
+{
+       while (*s) {
+               if (what == *s)
+                       *s = with;
+               ++s;
+       }
+}
+
+char * FAST_FUNC replace_underscores(char *s)
+{
+       replace(s, '-', '_');
+       return s;
+}
+
+int FAST_FUNC string_to_llist(char *string, llist_t **llist, const char *delim)
+{
+       char *tok;
+       int len = 0;
+
+       while ((tok = strsep(&string, delim)) != NULL) {
+               if (tok[0] == '\0')
+                       continue;
+               llist_add_to_end(llist, xstrdup(tok));
+               len += strlen(tok);
+       }
+       return len;
+}
+
+char * FAST_FUNC filename2modname(const char *filename, char *modname)
+{
+       int i;
+       char *from;
+
+       if (filename == NULL)
+               return NULL;
+       if (modname == NULL)
+               modname = xmalloc(MODULE_NAME_LEN);
+       from = bb_get_last_path_component_nostrip(filename);
+       for (i = 0; i < (MODULE_NAME_LEN-1) && from[i] != '\0' && from[i] != '.'; i++)
+               modname[i] = (from[i] == '-') ? '_' : from[i];
+       modname[i] = '\0';
+
+       return modname;
+}
+
+const char * FAST_FUNC moderror(int err)
+{
+       switch (err) {
+       case -1:
+               return "no such module";
+       case ENOEXEC:
+               return "invalid module format";
+       case ENOENT:
+               return "unknown symbol in module, or unknown parameter";
+       case ESRCH:
+               return "module has wrong symbol version";
+       case ENOSYS:
+               return "kernel does not support requested operation";
+       default:
+               return strerror(err);
+       }
+}
+
+char * FAST_FUNC parse_cmdline_module_options(char **argv)
+{
+       char *options;
+       int optlen;
+
+       options = xzalloc(1);
+       optlen = 0;
+       while (*++argv) {
+               options = xrealloc(options, optlen + 2 + strlen(*argv) + 2);
+               /* Spaces handled by "" pairs, but no way of escaping quotes */
+               optlen += sprintf(options + optlen, (strchr(*argv, ' ') ? "\"%s\" " : "%s "), *argv);
+       }
+       return options;
+}
+
+int FAST_FUNC bb_init_module(const char *filename, const char *options)
+{
+       size_t len;
+       char *image;
+       int rc;
+
+       if (!options)
+               options = "";
+
+#if ENABLE_FEATURE_2_4_MODULES
+       if (get_linux_version_code() < KERNEL_VERSION(2,6,0))
+               return bb_init_module_24(filename, options);
+#endif
+
+       /* Use the 2.6 way */
+       len = INT_MAX - 4095;
+       rc = ENOENT;
+       image = xmalloc_open_zipped_read_close(filename, &len);
+       if (image) {
+               rc = 0;
+               if (init_module(image, len, options) != 0)
+                       rc = errno;
+               free(image);
+       }
+
+       return rc;
+}
+
+int FAST_FUNC bb_delete_module(const char *module, unsigned int flags)
+{
+       return delete_module(module, flags);
+}
diff --git a/modutils/modutils.h b/modutils/modutils.h
new file mode 100644 (file)
index 0000000..5104f1b
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+ * Common modutils related functions for busybox
+ *
+ * Copyright (C) 2008 by Timo Teras <timo.teras@iki.fi>
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+#ifndef MODUTILS_H
+#define MODUTILS_H 1
+
+#include "libbb.h"
+
+PUSH_AND_SET_FUNCTION_VISIBILITY_TO_HIDDEN
+
+/* linux/include/linux/module.h has 64, but this is also used
+ * internally for the maximum alias name length, which can be quite long */
+#define MODULE_NAME_LEN 256
+
+const char *moderror(int err) FAST_FUNC;
+void replace(char *s, char what, char with) FAST_FUNC;
+char *replace_underscores(char *s) FAST_FUNC;
+int string_to_llist(char *string, llist_t **llist, const char *delim) FAST_FUNC;
+char *filename2modname(const char *filename, char *modname) FAST_FUNC;
+char *parse_cmdline_module_options(char **argv) FAST_FUNC;
+
+#define INSMOD_OPTS \
+       "vq" \
+       USE_FEATURE_2_4_MODULES("sLo:fkx") \
+       USE_FEATURE_INSMOD_LOAD_MAP("m")
+
+#define INSMOD_ARGS USE_FEATURE_2_4_MODULES(, NULL)
+
+enum {
+       INSMOD_OPT_VERBOSE      = 0x0001,
+       INSMOD_OPT_SILENT       = 0x0002,
+       INSMOD_OPT_SYSLOG       = 0x0004 * ENABLE_FEATURE_2_4_MODULES,
+       INSMOD_OPT_LOCK         = 0x0008 * ENABLE_FEATURE_2_4_MODULES,
+       INSMOD_OPT_OUTPUTNAME   = 0x0010 * ENABLE_FEATURE_2_4_MODULES,
+       INSMOD_OPT_FORCE        = 0x0020 * ENABLE_FEATURE_2_4_MODULES,
+       INSMOD_OPT_KERNELD      = 0x0040 * ENABLE_FEATURE_2_4_MODULES,
+       INSMOD_OPT_NO_EXPORT    = 0x0080 * ENABLE_FEATURE_2_4_MODULES,
+       INSMOD_OPT_PRINT_MAP    = 0x0100 * ENABLE_FEATURE_INSMOD_LOAD_MAP,
+#if ENABLE_FEATURE_2_4_MODULES
+# if ENABLE_FEATURE_INSMOD_LOAD_MAP
+       INSMOD_OPT_UNUSED       = 0x0200,
+# else
+       INSMOD_OPT_UNUSED       = 0x0100,
+# endif
+#else
+       INSMOD_OPT_UNUSED       = 0x0004,
+#endif
+};
+
+int FAST_FUNC bb_init_module(const char *module, const char *options);
+int FAST_FUNC bb_delete_module(const char *module, unsigned int flags);
+
+#if ENABLE_FEATURE_2_4_MODULES
+int FAST_FUNC bb_init_module_24(const char *module, const char *options);
+#endif
+
+POP_SAVED_FUNCTION_VISIBILITY
+
+#endif
diff --git a/modutils/rmmod.c b/modutils/rmmod.c
new file mode 100644 (file)
index 0000000..ee32dfd
--- /dev/null
@@ -0,0 +1,53 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Mini rmmod implementation for busybox
+ *
+ * Copyright (C) 1999-2004 by Erik Andersen <andersen@codepoet.org>
+ * Copyright (C) 2008 Timo Teras <timo.teras@iki.fi>
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.h"
+#include "modutils.h"
+
+int rmmod_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int rmmod_main(int argc UNUSED_PARAM, char **argv)
+{
+       int n;
+       unsigned flags = O_NONBLOCK | O_EXCL;
+
+       /* Parse command line. */
+       n = getopt32(argv, "wfas"); // -s ignored
+       argv += optind;
+       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 */
+               if (bb_delete_module(NULL, flags) != 0 && errno != EFAULT)
+                       bb_perror_msg_and_die("rmmod");
+               return EXIT_SUCCESS;
+       }
+
+       if (!*argv)
+               bb_show_usage();
+
+       n = ENABLE_FEATURE_2_4_MODULES && get_linux_version_code() < KERNEL_VERSION(2,6,0);
+       while (*argv) {
+               char modname[MODULE_NAME_LEN];
+               const char *bname;
+
+               bname = bb_basename(*argv++);
+               if (n)
+                       safe_strncpy(modname, bname, MODULE_NAME_LEN);
+               else
+                       filename2modname(bname, modname);
+               if (bb_delete_module(modname, flags))
+                       bb_error_msg_and_die("can't unload '%s': %s",
+                                            modname, moderror(errno));
+       }
+
+       return EXIT_SUCCESS;
+}
diff --git a/networking/Config.in b/networking/Config.in
new file mode 100644 (file)
index 0000000..392afcf
--- /dev/null
@@ -0,0 +1,941 @@
+#
+# 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 FEATURE_PREFER_IPV4_ADDRESS
+       bool "Prefer IPv4 addresses from DNS queries"
+       default y
+       depends on FEATURE_IPV6
+       help
+         Use IPv4 address of network host if it has one.
+
+         If this option is off, the first returned address will be used.
+         This may cause problems when your DNS server is IPv6-capable and
+         is returning IPv6 host addresses too. If IPv6 address
+         precedes IPv4 one in DNS reply, busybox network applets
+         (e.g. wget) will use IPv6 address. On an IPv6-incapable host
+         or network applets will fail to connect to the host
+         using IPv6 address.
+
+config VERBOSE_RESOLUTION_ERRORS
+       bool "Verbose resolution errors"
+       default n
+       help
+         Enable if you are not satisfied with simplistic
+         "can't resolve 'hostname.com'" and want to know more.
+         This may increase size of your executable a bit.
+
+config ARP
+       bool "arp"
+       default n
+       help
+         Manipulate the system ARP cache.
+
+config ARPING
+       bool "arping"
+       default n
+       help
+         Ping hosts by ARP packets.
+
+config BRCTL
+       bool "brctl"
+       default n
+       help
+         Manage ethernet bridges.
+         Supports addbr/delbr and addif/delif.
+
+config FEATURE_BRCTL_FANCY
+       bool "Fancy options"
+       default n
+       depends on BRCTL
+       help
+         Add support for extended option like:
+           setageing, setfd, sethello, setmaxage,
+           setpathcost, setportprio, setbridgeprio,
+           stp
+         This adds about 600 bytes.
+
+config FEATURE_BRCTL_SHOW
+       bool "Support show, showmac and showstp"
+       default n
+       depends on BRCTL && FEATURE_BRCTL_FANCY
+       help
+         Add support for option which prints the current config:
+           showmacs, showstp, show
+
+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 FTPD
+       bool "ftpd"
+       default n
+       help
+         simple FTP daemon. You have to run it via inetd.
+
+config FEATURE_FTP_WRITE
+       bool "Enable upload commands"
+       default y
+       depends on FTPD
+       help
+         Enable all kinds of FTP upload commands (-w option)
+
+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_RANGES
+       bool "Support 'Ranges:' header"
+       default n
+       depends on HTTPD
+       help
+         Makes httpd emit "Accept-Ranges: bytes" header and understand
+         "Range: bytes=NNN-[MMM]" header. Allows for resuming interrupted
+         downloads, seeking in multimedia players etc.
+
+config FEATURE_HTTPD_USE_SENDFILE
+       bool "Use sendfile system call"
+       default n
+       depends on HTTPD
+       help
+         When enabled, httpd will use the kernel sendfile() function
+         instead of read/write loop.
+
+config FEATURE_HTTPD_SETUID
+       bool "Enable -u <user> option"
+       default n
+       depends on HTTPD
+       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_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 "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 additional line in your httpd
+         config file:
+         *.php:/path/to/your/php
+
+config FEATURE_HTTPD_SET_REMOTE_PORT_TO_ENV
+       bool "Set 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 -e option (useful for CGIs written as shell scripts)"
+       default y
+       depends on HTTPD
+       help
+         This option allows html encoding of arbitrary strings for display
+         by the browser. Output goes to stdout.
+         For example, httpd -e "<Hello World>" produces
+         "&#60Hello&#32World&#62".
+
+config FEATURE_HTTPD_ERROR_PAGES
+       bool "Support for custom error pages"
+       default n
+       depends on HTTPD
+       help
+         This option allows you to define custom error pages in
+         the configuration file instead of the default HTTP status
+         error pages. For instance, if you add the line:
+               E404:/path/e404.html
+         in the config file, the server will respond the specified
+         '/path/e404.html' file instead of the terse '404 NOT FOUND'
+         message.
+
+config FEATURE_HTTPD_PROXY
+       bool "Support for reverse proxy"
+       default n
+       depends on HTTPD
+       help
+         This option allows you to define URLs that will be forwarded
+         to another HTTP server. To setup add the following line to the
+         configuration file
+               P:/url/:http://hostname[:port]/new/path/
+         Then a request to /url/myfile will be forwarded to
+         http://hostname[:port]/new/path/myfile.
+
+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 IFENSLAVE
+       bool "ifenslave"
+       default n
+       help
+         Userspace application to bind several interfaces
+         to a logical interface (use with kernel bonding driver).
+
+config IFUPDOWN
+       bool "ifupdown"
+       default n
+       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", "route" and "run-parts" or the "ip" command, either
+         via busybox or via standalone utilities.
+
+config IFUPDOWN_IFSTATE_PATH
+       string "Absolute path to ifstate file"
+       default "/var/run/ifstate"
+       depends on IFUPDOWN
+       help
+         ifupdown keeps state information in a file called ifstate.
+         Typically it is located in /var/run/ifstate, however
+         some distributions tend to put it in other places
+         (debian, for example, uses /etc/network/run/ifstate).
+         This config option defines location of ifstate.
+
+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 "Support for IPv4"
+       default y
+       depends on IFUPDOWN
+       help
+         If you want ifup/ifdown to talk IPv4, leave this on.
+
+config FEATURE_IFUPDOWN_IPV6
+       bool "Support for IPv6"
+       default n
+       depends on IFUPDOWN && FEATURE_IPV6
+       help
+         If you need support for IPv6, turn this option on.
+
+### UNUSED
+###config FEATURE_IFUPDOWN_IPX
+###    bool "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 FEATURE_IFUPDOWN_EXTERNAL_DHCP
+       bool "Support for external dhcp clients"
+       default n
+       depends on IFUPDOWN
+       help
+         This enables support for the external dhcp clients. Clients are
+         tried in the following order: dhcpcd, dhclient, pump and udhcpc.
+         Otherwise, if udhcpc applet is enabled, it is used.
+         Otherwise, ifup/ifdown will have no support for DHCP.
+
+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
+       select 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 <OBJECT> commands:
+         ip addr   -> ipaddr
+         ip link   -> iplink
+         ip route  -> iproute
+         ip tunnel -> iptunnel
+         ip rule   -> iprule
+
+         Say N unless you desparately need the short form of the ip
+         object commands.
+
+config FEATURE_IP_RARE_PROTOCOLS
+       bool "Support displaying rarely used link types"
+       default n
+       depends on IP
+       help
+         If you are not going to use links of type "frad", "econet",
+         "bif" etc, you probably don't need to enable this.
+         Ethernet, wireless, infrared, ppp/slip, ip tunnelling
+         link types are supported without this option selected.
+
+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: IFNAMSIZ = 16
+         File fields are separated by space or tab.
+         File format:
+         # Comment
+         new_interface_name    XX:XX:XX:XX:XX:XX
+
+config FEATURE_NAMEIF_EXTENDED
+       bool "Extended nameif"
+       default n
+       depends on NAMEIF
+       help
+         This extends the nameif syntax to support the bus_info and driver
+         checks. The syntax is compatible to the normal nameif.
+         File format:
+           new_interface_name  driver=asix bus=usb-0000:00:08.2-3
+           new_interface_name  bus=usb-0000:00:08.2-3 00:80:C8:38:91:B5
+           new_interface_name  mac=00:80:C8:38:91:B5
+           new_interface_name  00:80:C8:38:91:B5
+
+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 (-l)"
+       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 FEATURE_NETSTAT_WIDE
+       bool "Enable wide netstat output"
+       default n
+       depends on NETSTAT
+       help
+         Add support for wide columns. Useful when displaying IPv6 addresses
+         (-W option).
+
+config FEATURE_NETSTAT_PRG
+       bool "Enable PID/Program name output"
+       default n
+       depends on NETSTAT
+       help
+         Add support for -p flag to print out PID and program name.
+         +700 bytes of code.
+
+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 PING6
+       bool "ping6"
+       default n
+       depends on FEATURE_IPV6 && PING
+       help
+         This will give you a ping that can talk IPv6.
+
+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 PSCAN
+       bool "pscan"
+       default n
+       help
+         Simple network port scanner.
+
+config ROUTE
+       bool "route"
+       default n
+       help
+         Route displays or manipulates the kernel's IP routing tables.
+
+config SLATTACH
+       bool "slattach"
+       default n
+       help
+         slattach is a small utility to attach network interfaces to serial
+         lines.
+
+#config TC
+#      bool "tc"
+#      default n
+#      help
+#        show / manipulate traffic control settings
+#
+#config FEATURE_TC_INGRESS
+#      def_bool n
+#      depends on TC
+
+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 TFTPD
+       bool "tftpd"
+       default n
+       help
+         This enables the Trivial File Transfer Protocol server program.
+         It expects that stdin is a datagram socket and a packet
+         is already pending on it. It will exit after one transfer.
+         In other words: it should be run from inetd in nowait mode,
+         or from udpsvd. Example: "udpsvd -E 0 69 tftpd DIR"
+
+config FEATURE_TFTP_GET
+       bool "Enable \"get\" command"
+       default y
+       depends on TFTP || TFTPD
+       help
+         Add support for the GET command within the TFTP client. This allows
+         a client to retrieve a file from a TFTP server.
+         Also enable upload support in tftpd, if tftpd is selected.
+
+config FEATURE_TFTP_PUT
+       bool "Enable \"put\" command"
+       default y
+       depends on TFTP || TFTPD
+       help
+         Add support for the PUT command within the TFTP client. This allows
+         a client to transfer a file to a TFTP server.
+         Also enable download support in tftpd, if tftpd is selected.
+
+config FEATURE_TFTP_BLOCKSIZE
+       bool "Enable \"blksize\" protocol option"
+       default n
+       depends on TFTP || TFTPD
+       help
+         Allow tftp to specify block size, and tftpd to understand
+         "blksize" option.
+
+config TFTP_DEBUG
+       bool "Enable debug"
+       default n
+       depends on TFTP || TFTPD
+       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 among 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 option -I to use ICMP ECHO instead of UDP datagrams.
+
+source networking/udhcp/Config.in
+
+config IFUPDOWN_UDHCPC_CMD_OPTIONS
+       string "ifup udhcpc command line options"
+       default "-R -n"
+       depends on IFUPDOWN && APP_UDHCPC
+       help
+         Command line options to pass to udhcpc from ifup.
+         Intended to alter options not available in /etc/network/interfaces.
+         (IE: --syslog --background etc...)
+
+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_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.
+
+config TCPSVD
+       bool "tcpsvd"
+       default n
+       help
+         tcpsvd listens on a TCP port and runs a program for each new
+         connection.
+
+config TUNCTL
+       bool "tunctl"
+       default n
+       help
+         tunctl creates or deletes tun devices.
+
+config FEATURE_TUNCTL_UG
+       bool "Support owner:group assignment"
+       default n
+       depends on TUNCTL
+       help
+         Allow to specify owner and group of newly created interface.
+         340 bytes of pure bloat. Say no here.
+
+config UDPSVD
+       bool "udpsvd"
+       default n
+       help
+         udpsvd listens on an UDP port and runs a program for each new
+         connection.
+
+endmenu
diff --git a/networking/Kbuild b/networking/Kbuild
new file mode 100644 (file)
index 0000000..d632774
--- /dev/null
@@ -0,0 +1,46 @@
+# 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_ARP)          += arp.o interface.o
+lib-$(CONFIG_ARPING)       += arping.o
+lib-$(CONFIG_BRCTL)        += brctl.o
+lib-$(CONFIG_DNSD)         += dnsd.o
+lib-$(CONFIG_ETHER_WAKE)   += ether-wake.o
+lib-$(CONFIG_FAKEIDENTD)   += isrv_identd.o isrv.o
+lib-$(CONFIG_FTPD)         += ftpd.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_IFENSLAVE)    += ifenslave.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_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)        += ping.o
+lib-$(CONFIG_PSCAN)        += pscan.o
+lib-$(CONFIG_ROUTE)        += route.o
+lib-$(CONFIG_SLATTACH)     += slattach.o
+lib-$(CONFIG_TC)           += tc.o
+lib-$(CONFIG_TELNET)       += telnet.o
+lib-$(CONFIG_TELNETD)      += telnetd.o
+lib-$(CONFIG_TFTP)         += tftp.o
+lib-$(CONFIG_TFTPD)        += tftp.o
+lib-$(CONFIG_TRACEROUTE)   += traceroute.o
+lib-$(CONFIG_TUNCTL)       += tunctl.o
+lib-$(CONFIG_VCONFIG)      += vconfig.o
+lib-$(CONFIG_WGET)         += wget.o
+lib-$(CONFIG_ZCIP)         += zcip.o
+
+lib-$(CONFIG_TCPSVD)       += tcpudp.o tcpudp_perhost.o
+lib-$(CONFIG_UDPSVD)       += tcpudp.o tcpudp_perhost.o
diff --git a/networking/arp.c b/networking/arp.c
new file mode 100644 (file)
index 0000000..278f2dc
--- /dev/null
@@ -0,0 +1,515 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * arp.c - Manipulate the system ARP cache
+ *
+ * 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.
+ *
+ * Author: Fred N. van Kempen, <waltje at uwalt.nl.mugnet.org>
+ * Busybox port: Paul van Gool <pvangool at mimotech.com>
+ *
+ * modified for getopt32 by Arne Bernin <arne [at] alamut.de>
+ */
+
+#include "libbb.h"
+#include "inet_common.h"
+
+#include <arpa/inet.h>
+#include <net/if.h>
+#include <net/if_arp.h>
+#include <netinet/ether.h>
+#include <netpacket/packet.h>
+
+#define DEBUG 0
+
+#define DFLT_AF "inet"
+#define DFLT_HW "ether"
+
+enum {
+       ARP_OPT_A = (1 << 0),
+       ARP_OPT_p = (1 << 1),
+       ARP_OPT_H = (1 << 2),
+       ARP_OPT_t = (1 << 3),
+       ARP_OPT_i = (1 << 4),
+       ARP_OPT_a = (1 << 5),
+       ARP_OPT_d = (1 << 6),
+       ARP_OPT_n = (1 << 7), /* do not resolve addresses */
+       ARP_OPT_D = (1 << 8), /* HW-address is devicename */
+       ARP_OPT_s = (1 << 9),
+       ARP_OPT_v = (1 << 10) * DEBUG, /* debugging output flag */
+};
+
+enum {
+       sockfd = 3, /* active socket descriptor */
+};
+
+struct globals {
+       const struct aftype *ap; /* current address family */
+       const struct hwtype *hw; /* current hardware type */
+       const char *device;      /* current device */
+       smallint hw_set;         /* flag if hw-type was set (-H) */
+
+};
+#define G (*(struct globals*)&bb_common_bufsiz1)
+#define ap         (G.ap        )
+#define hw         (G.hw        )
+#define device     (G.device    )
+#define hw_set     (G.hw_set    )
+#define INIT_G() do { \
+       device = ""; \
+} while (0)
+
+
+static const char options[] ALIGN1 =
+       "pub\0"
+       "priv\0"
+       "temp\0"
+       "trail\0"
+       "dontpub\0"
+       "auto\0"
+       "dev\0"
+       "netmask\0";
+
+/* Delete an entry from the ARP cache. */
+/* Called only from main, once */
+static int arp_del(char **args)
+{
+       char *host;
+       struct arpreq req;
+       struct sockaddr sa;
+       int flags = 0;
+       int err;
+
+       memset(&req, 0, sizeof(req));
+
+       /* Resolve the host name. */
+       host = *args;
+       if (ap->input(host, &sa) < 0) {
+               bb_herror_msg_and_die("%s", host);
+       }
+
+       /* If a host has more than one address, use the correct one! */
+       memcpy(&req.arp_pa, &sa, sizeof(struct sockaddr));
+
+       if (hw_set)
+               req.arp_ha.sa_family = hw->type;
+
+       req.arp_flags = ATF_PERM;
+       args++;
+       while (*args != NULL) {
+               switch (index_in_strings(options, *args)) {
+               case 0: /* "pub" */
+                       flags |= 1;
+                       args++;
+                       break;
+               case 1: /* "priv" */
+                       flags |= 2;
+                       args++;
+                       break;
+               case 2: /* "temp" */
+                       req.arp_flags &= ~ATF_PERM;
+                       args++;
+                       break;
+               case 3: /* "trail" */
+                       req.arp_flags |= ATF_USETRAILERS;
+                       args++;
+                       break;
+               case 4: /* "dontpub" */
+#ifdef HAVE_ATF_DONTPUB
+                       req.arp_flags |= ATF_DONTPUB;
+#else
+                       bb_error_msg("feature ATF_DONTPUB is not supported");
+#endif
+                       args++;
+                       break;
+               case 5: /* "auto" */
+#ifdef HAVE_ATF_MAGIC
+                       req.arp_flags |= ATF_MAGIC;
+#else
+                       bb_error_msg("feature ATF_MAGIC is not supported");
+#endif
+                       args++;
+                       break;
+               case 6: /* "dev" */
+                       if (*++args == NULL)
+                               bb_show_usage();
+                       device = *args;
+                       args++;
+                       break;
+               case 7: /* "netmask" */
+                       if (*++args == NULL)
+                               bb_show_usage();
+                       if (strcmp(*args, "255.255.255.255") != 0) {
+                               host = *args;
+                               if (ap->input(host, &sa) < 0) {
+                                       bb_herror_msg_and_die("%s", host);
+                               }
+                               memcpy(&req.arp_netmask, &sa, sizeof(struct sockaddr));
+                               req.arp_flags |= ATF_NETMASK;
+                       }
+                       args++;
+                       break;
+               default:
+                       bb_show_usage();
+                       break;
+               }
+       }
+       if (flags == 0)
+               flags = 3;
+
+       strncpy(req.arp_dev, device, sizeof(req.arp_dev));
+
+       err = -1;
+
+       /* Call the kernel. */
+       if (flags & 2) {
+               if (option_mask32 & ARP_OPT_v)
+                       bb_error_msg("SIOCDARP(nopub)");
+               err = ioctl(sockfd, SIOCDARP, &req);
+               if (err < 0) {
+                       if (errno == ENXIO) {
+                               if (flags & 1)
+                                       goto nopub;
+                               printf("No ARP entry for %s\n", host);
+                               return -1;
+                       }
+                       bb_perror_msg_and_die("SIOCDARP(priv)");
+               }
+       }
+       if ((flags & 1) && err) {
+ nopub:
+               req.arp_flags |= ATF_PUBL;
+               if (option_mask32 & ARP_OPT_v)
+                       bb_error_msg("SIOCDARP(pub)");
+               if (ioctl(sockfd, SIOCDARP, &req) < 0) {
+                       if (errno == ENXIO) {
+                               printf("No ARP entry for %s\n", host);
+                               return -1;
+                       }
+                       bb_perror_msg_and_die("SIOCDARP(pub)");
+               }
+       }
+       return 0;
+}
+
+/* Get the hardware address to a specified interface name */
+static void arp_getdevhw(char *ifname, struct sockaddr *sa,
+                                                const struct hwtype *hwt)
+{
+       struct ifreq ifr;
+       const struct hwtype *xhw;
+
+       strcpy(ifr.ifr_name, ifname);
+       ioctl_or_perror_and_die(sockfd, SIOCGIFHWADDR, &ifr,
+                                       "cant get HW-Address for '%s'", ifname);
+       if (hwt && (ifr.ifr_hwaddr.sa_family != hw->type)) {
+               bb_error_msg_and_die("protocol type mismatch");
+       }
+       memcpy(sa, &(ifr.ifr_hwaddr), sizeof(struct sockaddr));
+
+       if (option_mask32 & ARP_OPT_v) {
+               xhw = get_hwntype(ifr.ifr_hwaddr.sa_family);
+               if (!xhw || !xhw->print) {
+                       xhw = get_hwntype(-1);
+               }
+               bb_error_msg("device '%s' has HW address %s '%s'",
+                                        ifname, xhw->name,
+                                        xhw->print((unsigned char *) &ifr.ifr_hwaddr.sa_data));
+       }
+}
+
+/* Set an entry in the ARP cache. */
+/* Called only from main, once */
+static int arp_set(char **args)
+{
+       char *host;
+       struct arpreq req;
+       struct sockaddr sa;
+       int flags;
+
+       memset(&req, 0, sizeof(req));
+
+       host = *args++;
+       if (ap->input(host, &sa) < 0) {
+               bb_herror_msg_and_die("%s", host);
+       }
+       /* If a host has more than one address, use the correct one! */
+       memcpy(&req.arp_pa, &sa, sizeof(struct sockaddr));
+
+       /* Fetch the hardware address. */
+       if (*args == NULL) {
+               bb_error_msg_and_die("need hardware address");
+       }
+       if (option_mask32 & ARP_OPT_D) {
+               arp_getdevhw(*args++, &req.arp_ha, hw_set ? hw : NULL);
+       } else {
+               if (hw->input(*args++, &req.arp_ha) < 0) {
+                       bb_error_msg_and_die("invalid hardware address");
+               }
+       }
+
+       /* Check out any modifiers. */
+       flags = ATF_PERM | ATF_COM;
+       while (*args != NULL) {
+               switch (index_in_strings(options, *args)) {
+               case 0: /* "pub" */
+                       flags |= ATF_PUBL;
+                       args++;
+                       break;
+               case 1: /* "priv" */
+                       flags &= ~ATF_PUBL;
+                       args++;
+                       break;
+               case 2: /* "temp" */
+                       flags &= ~ATF_PERM;
+                       args++;
+                       break;
+               case 3: /* "trail" */
+                       flags |= ATF_USETRAILERS;
+                       args++;
+                       break;
+               case 4: /* "dontpub" */
+#ifdef HAVE_ATF_DONTPUB
+                       flags |= ATF_DONTPUB;
+#else
+                       bb_error_msg("feature ATF_DONTPUB is not supported");
+#endif
+                       args++;
+                       break;
+               case 5: /* "auto" */
+#ifdef HAVE_ATF_MAGIC
+                       flags |= ATF_MAGIC;
+#else
+                       bb_error_msg("feature ATF_MAGIC is not supported");
+#endif
+                       args++;
+                       break;
+               case 6: /* "dev" */
+                       if (*++args == NULL)
+                               bb_show_usage();
+                       device = *args;
+                       args++;
+                       break;
+               case 7: /* "netmask" */
+                       if (*++args == NULL)
+                               bb_show_usage();
+                       if (strcmp(*args, "255.255.255.255") != 0) {
+                               host = *args;
+                               if (ap->input(host, &sa) < 0) {
+                                       bb_herror_msg_and_die("%s", host);
+                               }
+                               memcpy(&req.arp_netmask, &sa, sizeof(struct sockaddr));
+                               flags |= ATF_NETMASK;
+                       }
+                       args++;
+                       break;
+               default:
+                       bb_show_usage();
+                       break;
+               }
+       }
+
+       /* Fill in the remainder of the request. */
+       req.arp_flags = flags;
+
+       strncpy(req.arp_dev, device, sizeof(req.arp_dev));
+
+       /* Call the kernel. */
+       if (option_mask32 & ARP_OPT_v)
+               bb_error_msg("SIOCSARP()");
+       xioctl(sockfd, SIOCSARP, &req);
+       return 0;
+}
+
+
+/* Print the contents of an ARP request block. */
+static void
+arp_disp(const char *name, char *ip, int type, int arp_flags,
+                char *hwa, char *mask, char *dev)
+{
+       static const int arp_masks[] = {
+               ATF_PERM, ATF_PUBL,
+#ifdef HAVE_ATF_MAGIC
+               ATF_MAGIC,
+#endif
+#ifdef HAVE_ATF_DONTPUB
+               ATF_DONTPUB,
+#endif
+               ATF_USETRAILERS,
+       };
+       static const char arp_labels[] ALIGN1 = "PERM\0""PUP\0"
+#ifdef HAVE_ATF_MAGIC
+               "AUTO\0"
+#endif
+#ifdef HAVE_ATF_DONTPUB
+               "DONTPUB\0"
+#endif
+               "TRAIL\0"
+       ;
+
+       const struct hwtype *xhw;
+
+       xhw = get_hwntype(type);
+       if (xhw == NULL)
+               xhw = get_hwtype(DFLT_HW);
+
+       printf("%s (%s) at ", name, ip);
+
+       if (!(arp_flags & ATF_COM)) {
+               if (arp_flags & ATF_PUBL)
+                       printf("* ");
+               else
+                       printf("<incomplete> ");
+       } else {
+               printf("%s [%s] ", hwa, xhw->name);
+       }
+
+       if (arp_flags & ATF_NETMASK)
+               printf("netmask %s ", mask);
+
+       print_flags_separated(arp_masks, arp_labels, arp_flags, " ");
+       printf(" on %s\n", dev);
+}
+
+/* Display the contents of the ARP cache in the kernel. */
+/* Called only from main, once */
+static int arp_show(char *name)
+{
+       const char *host;
+       const char *hostname;
+       FILE *fp;
+       struct sockaddr sa;
+       int type, flags;
+       int num;
+       unsigned entries = 0, shown = 0;
+       char ip[128];
+       char hwa[128];
+       char mask[128];
+       char line[128];
+       char dev[128];
+
+       host = NULL;
+       if (name != NULL) {
+               /* Resolve the host name. */
+               if (ap->input(name, &sa) < 0) {
+                       bb_herror_msg_and_die("%s", name);
+               }
+               host = xstrdup(ap->sprint(&sa, 1));
+       }
+       fp = xfopen_for_read("/proc/net/arp");
+       /* Bypass header -- read one line */
+       fgets(line, sizeof(line), fp);
+
+       /* Read the ARP cache entries. */
+       while (fgets(line, sizeof(line), fp)) {
+
+               mask[0] = '-'; mask[1] = '\0';
+               dev[0] = '-'; dev[1] = '\0';
+               /* All these strings can't overflow
+                * because fgets above reads limited amount of data */
+               num = sscanf(line, "%s 0x%x 0x%x %s %s %s\n",
+                                        ip, &type, &flags, hwa, mask, dev);
+               if (num < 4)
+                       break;
+
+               entries++;
+               /* if the user specified hw-type differs, skip it */
+               if (hw_set && (type != hw->type))
+                       continue;
+
+               /* if the user specified address differs, skip it */
+               if (host && strcmp(ip, host) != 0)
+                       continue;
+
+               /* if the user specified device differs, skip it */
+               if (device[0] && strcmp(dev, device) != 0)
+                       continue;
+
+               shown++;
+               /* This IS ugly but it works -be */
+               hostname = "?";
+               if (!(option_mask32 & ARP_OPT_n)) {
+                       if (ap->input(ip, &sa) < 0)
+                               hostname = ip;
+                       else
+                               hostname = ap->sprint(&sa, (option_mask32 & ARP_OPT_n) | 0x8000);
+                       if (strcmp(hostname, ip) == 0)
+                               hostname = "?";
+               }
+
+               arp_disp(hostname, ip, type, flags, hwa, mask, dev);
+       }
+       if (option_mask32 & ARP_OPT_v)
+               printf("Entries: %d\tSkipped: %d\tFound: %d\n",
+                          entries, entries - shown, shown);
+
+       if (!shown) {
+               if (hw_set || host || device[0])
+                       printf("No match found in %d entries\n", entries);
+       }
+       if (ENABLE_FEATURE_CLEAN_UP) {
+               free((char*)host);
+               fclose(fp);
+       }
+       return 0;
+}
+
+int arp_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int arp_main(int argc UNUSED_PARAM, char **argv)
+{
+       const char *hw_type = "ether";
+       const char *protocol;
+       unsigned opts;
+
+       INIT_G();
+
+       xmove_fd(xsocket(AF_INET, SOCK_DGRAM, 0), sockfd);
+       ap = get_aftype(DFLT_AF);
+       if (!ap)
+               bb_error_msg_and_die("%s: %s not supported", DFLT_AF, "address family");
+
+       opts = getopt32(argv, "A:p:H:t:i:adnDsv", &protocol, &protocol,
+                                &hw_type, &hw_type, &device);
+       argv += optind;
+       if (opts & (ARP_OPT_A | ARP_OPT_p)) {
+               ap = get_aftype(protocol);
+               if (ap == NULL)
+                       bb_error_msg_and_die("%s: unknown %s", protocol, "address family");
+       }
+       if (opts & (ARP_OPT_A | ARP_OPT_p)) {
+               hw = get_hwtype(hw_type);
+               if (hw == NULL)
+                       bb_error_msg_and_die("%s: unknown %s", hw_type, "hardware type");
+               hw_set = 1;
+       }
+       //if (opts & ARP_OPT_i)... -i
+
+       if (ap->af != AF_INET) {
+               bb_error_msg_and_die("%s: kernel only supports 'inet'", ap->name);
+       }
+
+       /* If no hw type specified get default */
+       if (!hw) {
+               hw = get_hwtype(DFLT_HW);
+               if (!hw)
+                       bb_error_msg_and_die("%s: %s not supported", DFLT_HW, "hardware type");
+       }
+
+       if (hw->alen <= 0) {
+               bb_error_msg_and_die("%s: %s without ARP support",
+                                                        hw->name, "hardware type");
+       }
+
+       /* Now see what we have to do here... */
+       if (opts & (ARP_OPT_d | ARP_OPT_s)) {
+               if (argv[0] == NULL)
+                       bb_error_msg_and_die("need host name");
+               if (opts & ARP_OPT_s)
+                       return arp_set(argv);
+               return arp_del(argv);
+       }
+       //if (opts & ARP_OPT_a) - default
+       return arp_show(argv[0]);
+}
diff --git a/networking/arping.c b/networking/arping.c
new file mode 100644 (file)
index 0000000..915af32
--- /dev/null
@@ -0,0 +1,410 @@
+/* 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 <kuznet@ms2.inr.ac.ru>
+ * Busybox port: Nick Fedchik <nick@fedchik.org.ua>
+ */
+
+#include <arpa/inet.h>
+#include <net/if.h>
+#include <netinet/ether.h>
+#include <netpacket/packet.h>
+
+#include "libbb.h"
+
+/* We don't expect to see 1000+ seconds delay, unsigned is enough */
+#define MONOTONIC_US() ((unsigned)monotonic_us())
+
+enum {
+       DAD = 1,
+       UNSOLICITED = 2,
+       ADVERT = 4,
+       QUIET = 8,
+       QUIT_ON_REPLY = 16,
+       BCAST_ONLY = 32,
+       UNICASTING = 64
+};
+
+struct globals {
+       struct in_addr src;
+       struct in_addr dst;
+       struct sockaddr_ll me;
+       struct sockaddr_ll he;
+       int sock_fd;
+
+       int count; // = -1;
+       unsigned last;
+       unsigned timeout_us;
+       unsigned start;
+
+       unsigned sent;
+       unsigned brd_sent;
+       unsigned received;
+       unsigned brd_recv;
+       unsigned req_recv;
+};
+#define G (*(struct globals*)&bb_common_bufsiz1)
+#define src        (G.src       )
+#define dst        (G.dst       )
+#define me         (G.me        )
+#define he         (G.he        )
+#define sock_fd    (G.sock_fd   )
+#define count      (G.count     )
+#define last       (G.last      )
+#define timeout_us (G.timeout_us)
+#define start      (G.start     )
+#define sent       (G.sent      )
+#define brd_sent   (G.brd_sent  )
+#define received   (G.received  )
+#define brd_recv   (G.brd_recv  )
+#define req_recv   (G.req_recv  )
+#define INIT_G() do { \
+       count = -1; \
+} while (0)
+
+// If GNUisms are not available...
+//static void *mempcpy(void *_dst, const void *_src, int n)
+//{
+//     memcpy(_dst, _src, n);
+//     return (char*)_dst + n;
+//}
+
+static int send_pack(struct in_addr *src_addr,
+                       struct in_addr *dst_addr, struct sockaddr_ll *ME,
+                       struct sockaddr_ll *HE)
+{
+       int err;
+       unsigned char buf[256];
+       struct arphdr *ah = (struct arphdr *) buf;
+       unsigned char *p = (unsigned char *) (ah + 1);
+
+       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 = option_mask32 & ADVERT ? htons(ARPOP_REPLY) : htons(ARPOP_REQUEST);
+
+       p = mempcpy(p, &ME->sll_addr, ah->ar_hln);
+       p = mempcpy(p, src_addr, 4);
+
+       if (option_mask32 & ADVERT)
+               p = mempcpy(p, &ME->sll_addr, ah->ar_hln);
+       else
+               p = mempcpy(p, &HE->sll_addr, ah->ar_hln);
+
+       p = mempcpy(p, dst_addr, 4);
+
+       err = sendto(sock_fd, buf, p - buf, 0, (struct sockaddr *) HE, sizeof(*HE));
+       if (err == p - buf) {
+               last = MONOTONIC_US();
+               sent++;
+               if (!(option_mask32 & UNICASTING))
+                       brd_sent++;
+       }
+       return err;
+}
+
+static void finish(void) NORETURN;
+static void finish(void)
+{
+       if (!(option_mask32 & QUIET)) {
+               printf("Sent %u probe(s) (%u broadcast(s))\n"
+                       "Received %u repl%s"
+                       " (%u request(s), %u broadcast(s))\n",
+                       sent, brd_sent,
+                       received, (received == 1) ? "ies" : "y",
+                       req_recv, brd_recv);
+       }
+       if (option_mask32 & DAD)
+               exit(!!received);
+       if (option_mask32 & UNSOLICITED)
+               exit(EXIT_SUCCESS);
+       exit(!received);
+}
+
+static void catcher(void)
+{
+       unsigned now;
+
+       now = MONOTONIC_US();
+       if (start == 0)
+               start = now;
+
+       if (count == 0 || (timeout_us && (now - start) > timeout_us))
+               finish();
+
+       /* count < 0 means "infinite count" */
+       if (count > 0)
+               count--;
+
+       if (last == 0 || (now - last) > 500000) {
+               send_pack(&src, &dst, &me, &he);
+               if (count == 0 && (option_mask32 & UNSOLICITED))
+                       finish();
+       }
+       alarm(1);
+}
+
+static bool 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;
+       /* moves below assume in_addr is 4 bytes big, ensure that */
+       struct BUG_in_addr_must_be_4 {
+               char BUG_in_addr_must_be_4[
+                       sizeof(struct in_addr) == 4 ? 1 : -1
+               ];
+               char BUG_s_addr_must_be_4[
+                       sizeof(src_ip.s_addr) == 4 ? 1 : -1
+               ];
+       };
+
+       /* Filter out wild packets */
+       if (FROM->sll_pkttype != PACKET_HOST
+        && FROM->sll_pkttype != PACKET_BROADCAST
+        && FROM->sll_pkttype != PACKET_MULTICAST)
+               return false;
+
+       /* Only these types are recognised */
+       if (ah->ar_op != htons(ARPOP_REQUEST) && ah->ar_op != htons(ARPOP_REPLY))
+               return false;
+
+       /* 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 false;
+
+       /* Protocol must be IP. */
+       if (ah->ar_pro != htons(ETH_P_IP)
+        || (ah->ar_pln != 4)
+        || (ah->ar_hln != me.sll_halen)
+        || (len < (int)(sizeof(*ah) + 2 * (4 + ah->ar_hln))))
+               return false;
+
+       move_from_unaligned32(src_ip.s_addr, p + ah->ar_hln);
+       move_from_unaligned32(dst_ip.s_addr, p + ah->ar_hln + 4 + ah->ar_hln);
+
+       if (dst.s_addr != src_ip.s_addr)
+               return false;
+       if (!(option_mask32 & DAD)) {
+               if ((src.s_addr != dst_ip.s_addr)
+                       || (memcmp(p + ah->ar_hln + 4, &me.sll_addr, ah->ar_hln)))
+                       return false;
+       } else {
+               /* DAD packet was:
+                  src_ip = 0 (or some src)
+                  src_hw = ME
+                  dst_ip = tested address
+                  dst_hw = <unspec>
+
+                  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 ((memcmp(p, &me.sll_addr, me.sll_halen) == 0)
+                || (src.s_addr && src.s_addr != dst_ip.s_addr))
+                       return false;
+       }
+       if (!(option_mask32 & QUIET)) {
+               int s_printed = 0;
+
+               printf("%scast re%s from %s [%s]",
+                       FROM->sll_pkttype == PACKET_HOST ? "Uni" : "Broad",
+                       ah->ar_op == htons(ARPOP_REPLY) ? "ply" : "quest",
+                       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) {
+                       unsigned diff = MONOTONIC_US() - last;
+                       printf(" %u.%03ums\n", diff / 1000, diff % 1000);
+               } 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 (option_mask32 & QUIT_ON_REPLY)
+               finish();
+       if (!(option_mask32 & BCAST_ONLY)) {
+               memcpy(he.sll_addr, p, me.sll_halen);
+               option_mask32 |= UNICASTING;
+       }
+       return true;
+}
+
+int arping_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int arping_main(int argc UNUSED_PARAM, char **argv)
+{
+       const char *device = "eth0";
+       char *source = NULL;
+       char *target;
+       unsigned char *packet;
+       char *err_str;
+
+       INIT_G();
+
+       sock_fd = xsocket(AF_PACKET, SOCK_DGRAM, 0);
+
+       // Drop suid root privileges
+       // Need to remove SUID_NEVER from applets.h for this to work
+       //xsetuid(getuid());
+
+       err_str = xasprintf("interface %s %%s", device);
+       {
+               unsigned opt;
+               char *str_timeout;
+
+               /* Dad also sets quit_on_reply.
+                * Advert also sets unsolicited.
+                */
+               opt_complementary = "=1:Df:AU:c+";
+               opt = getopt32(argv, "DUAqfbc:w:I:s:",
+                               &count, &str_timeout, &device, &source);
+               if (opt & 0x80) /* -w: timeout */
+                       timeout_us = xatou_range(str_timeout, 0, INT_MAX/2000000) * 1000000 + 500000;
+               //if (opt & 0x200) /* -s: source */
+               option_mask32 &= 0x3f; /* set respective flags */
+       }
+
+       target = argv[optind];
+
+       xfunc_error_retval = 2;
+
+       {
+               struct ifreq ifr;
+
+               memset(&ifr, 0, sizeof(ifr));
+               strncpy_IFNAMSIZ(ifr.ifr_name, device);
+               /* We use ifr.ifr_name in error msg so that problem
+                * with truncated name will be visible */
+               ioctl_or_perror_and_die(sock_fd, SIOCGIFINDEX, &ifr, err_str, "not found");
+               me.sll_ifindex = ifr.ifr_ifindex;
+
+               xioctl(sock_fd, SIOCGIFFLAGS, (char *) &ifr);
+
+               if (!(ifr.ifr_flags & IFF_UP)) {
+                       bb_error_msg_and_die(err_str, "is down");
+               }
+               if (ifr.ifr_flags & (IFF_NOARP | IFF_LOOPBACK)) {
+                       bb_error_msg(err_str, "is not ARPable");
+                       return (option_mask32 & DAD ? 0 : 2);
+               }
+       }
+
+       /* if (!inet_aton(target, &dst)) - not needed */ {
+               len_and_sockaddr *lsa;
+               lsa = xhost_and_af2sockaddr(target, 0, AF_INET);
+               dst = lsa->u.sin.sin_addr;
+               if (ENABLE_FEATURE_CLEAN_UP)
+                       free(lsa);
+       }
+
+       if (source && !inet_aton(source, &src)) {
+               bb_error_msg_and_die("invalid source address %s", source);
+       }
+
+       if ((option_mask32 & (DAD|UNSOLICITED)) == UNSOLICITED && src.s_addr == 0)
+               src = dst;
+
+       if (!(option_mask32 & DAD) || src.s_addr) {
+               struct sockaddr_in saddr;
+               int probe_fd = xsocket(AF_INET, SOCK_DGRAM, 0);
+
+               setsockopt_bindtodevice(probe_fd, device);
+               memset(&saddr, 0, sizeof(saddr));
+               saddr.sin_family = AF_INET;
+               if (src.s_addr) {
+                       /* Check that this is indeed our IP */
+                       saddr.sin_addr = src;
+                       xbind(probe_fd, (struct sockaddr *) &saddr, sizeof(saddr));
+               } else { /* !(option_mask32 & DAD) case */
+                       /* Find IP address on this iface */
+                       socklen_t alen = sizeof(saddr);
+
+                       saddr.sin_port = htons(1025);
+                       saddr.sin_addr = dst;
+
+                       if (setsockopt(probe_fd, SOL_SOCKET, SO_DONTROUTE, &const_int_1, sizeof(const_int_1)) == -1)
+                               bb_perror_msg("setsockopt(SO_DONTROUTE)");
+                       xconnect(probe_fd, (struct sockaddr *) &saddr, sizeof(saddr));
+                       if (getsockname(probe_fd, (struct sockaddr *) &saddr, &alen) == -1) {
+                               bb_perror_msg_and_die("getsockname");
+                       }
+                       if (saddr.sin_family != AF_INET)
+                               bb_error_msg_and_die("no IP address configured");
+                       src = saddr.sin_addr;
+               }
+               close(probe_fd);
+       }
+
+       me.sll_family = AF_PACKET;
+       //me.sll_ifindex = ifindex; - done before
+       me.sll_protocol = htons(ETH_P_ARP);
+       xbind(sock_fd, (struct sockaddr *) &me, sizeof(me));
+
+       {
+               socklen_t alen = sizeof(me);
+
+               if (getsockname(sock_fd, (struct sockaddr *) &me, &alen) == -1) {
+                       bb_perror_msg_and_die("getsockname");
+               }
+       }
+       if (me.sll_halen == 0) {
+               bb_error_msg(err_str, "is not ARPable (no ll address)");
+               return (option_mask32 & DAD ? 0 : 2);
+       }
+       he = me;
+       memset(he.sll_addr, -1, he.sll_halen);
+
+       if (!(option_mask32 & QUIET)) {
+               /* inet_ntoa uses static storage, can't use in same printf */
+               printf("ARPING to %s", inet_ntoa(dst));
+               printf(" from %s via %s\n", inet_ntoa(src), device);
+       }
+
+       signal_SA_RESTART_empty_mask(SIGINT,  (void (*)(int))finish);
+       signal_SA_RESTART_empty_mask(SIGALRM, (void (*)(int))catcher);
+
+       catcher();
+
+       packet = xmalloc(4096);
+       while (1) {
+               sigset_t sset, osset;
+               struct sockaddr_ll from;
+               socklen_t alen = sizeof(from);
+               int cc;
+
+               cc = recvfrom(sock_fd, packet, 4096, 0, (struct sockaddr *) &from, &alen);
+               if (cc < 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);
+       }
+}
diff --git a/networking/brctl.c b/networking/brctl.c
new file mode 100644 (file)
index 0000000..1b52689
--- /dev/null
@@ -0,0 +1,286 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Small implementation of brctl for busybox.
+ *
+ * Copyright (C) 2008 by Bernhard Reutner-Fischer
+ *
+ * Some helper functions from bridge-utils are
+ * Copyright (C) 2000 Lennert Buytenhek
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+/* This applet currently uses only the ioctl interface and no sysfs at all.
+ * At the time of this writing this was considered a feature.
+ */
+#include "libbb.h"
+#include <linux/sockios.h>
+#include <net/if.h>
+
+#ifndef SIOCBRADDBR
+# define SIOCBRADDBR BRCTL_ADD_BRIDGE
+#endif
+#ifndef SIOCBRDELBR
+# define SIOCBRDELBR BRCTL_DEL_BRIDGE
+#endif
+#ifndef SIOCBRADDIF
+# define SIOCBRADDIF BRCTL_ADD_IF
+#endif
+#ifndef SIOCBRDELIF
+# define SIOCBRDELIF BRCTL_DEL_IF
+#endif
+
+
+/* Maximum number of ports supported per bridge interface.  */
+#ifndef MAX_PORTS
+#define MAX_PORTS 32
+#endif
+
+/* Use internal number parsing and not the "exact" conversion.  */
+/* #define BRCTL_USE_INTERNAL 0 */ /* use exact conversion */
+#define BRCTL_USE_INTERNAL 1
+
+#if ENABLE_FEATURE_BRCTL_FANCY
+#include <linux/if_bridge.h>
+
+/* FIXME: These 4 funcs are not really clean and could be improved */
+static ALWAYS_INLINE void strtotimeval(struct timeval *tv,
+               const char *time_str)
+{
+       double secs;
+#if BRCTL_USE_INTERNAL
+       secs = /*bb_*/strtod(time_str, NULL);
+       if (!secs)
+#else
+       if (sscanf(time_str, "%lf", &secs) != 1)
+#endif
+               bb_error_msg_and_die (bb_msg_invalid_arg, time_str, "timespec");
+       tv->tv_sec = secs;
+       tv->tv_usec = 1000000 * (secs - tv->tv_sec);
+}
+
+static ALWAYS_INLINE unsigned long __tv_to_jiffies(const struct timeval *tv)
+{
+       unsigned long long jif;
+
+       jif = 1000000ULL * tv->tv_sec + tv->tv_usec;
+
+       return jif/10000;
+}
+# if 0
+static void __jiffies_to_tv(struct timeval *tv, unsigned long jiffies)
+{
+       unsigned long long tvusec;
+
+       tvusec = 10000ULL*jiffies;
+       tv->tv_sec = tvusec/1000000;
+       tv->tv_usec = tvusec - 1000000 * tv->tv_sec;
+}
+# endif
+static unsigned long str_to_jiffies(const char *time_str)
+{
+       struct timeval tv;
+       strtotimeval(&tv, time_str);
+       return __tv_to_jiffies(&tv);
+}
+
+static void arm_ioctl(unsigned long *args,
+               unsigned long arg0, unsigned long arg1, unsigned long arg2)
+{
+       args[0] = arg0;
+       args[1] = arg1;
+       args[2] = arg2;
+       args[3] = 0;
+}
+#endif
+
+
+int brctl_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int brctl_main(int argc UNUSED_PARAM, char **argv)
+{
+       static const char keywords[] ALIGN1 =
+               "addbr\0" "delbr\0" "addif\0" "delif\0"
+       USE_FEATURE_BRCTL_FANCY(
+               "stp\0"
+               "setageing\0" "setfd\0" "sethello\0" "setmaxage\0"
+               "setpathcost\0" "setportprio\0" "setbridgeprio\0"
+       )
+       USE_FEATURE_BRCTL_SHOW("showmacs\0" "show\0");
+
+       enum { ARG_addbr = 0, ARG_delbr, ARG_addif, ARG_delif
+               USE_FEATURE_BRCTL_FANCY(,
+                  ARG_stp,
+                  ARG_setageing, ARG_setfd, ARG_sethello, ARG_setmaxage,
+                  ARG_setpathcost, ARG_setportprio, ARG_setbridgeprio
+               )
+               USE_FEATURE_BRCTL_SHOW(, ARG_showmacs, ARG_show)
+       };
+
+       int fd;
+       smallint key;
+       struct ifreq ifr;
+       char *br, *brif;
+
+       argv++;
+       while (*argv) {
+#if ENABLE_FEATURE_BRCTL_FANCY
+               int ifidx[MAX_PORTS];
+               unsigned long args[4];
+               ifr.ifr_data = (char *) &args;
+#endif
+
+               key = index_in_strings(keywords, *argv);
+               if (key == -1) /* no match found in keywords array, bail out. */
+                       bb_error_msg_and_die(bb_msg_invalid_arg, *argv, applet_name);
+               argv++;
+               fd = xsocket(AF_INET, SOCK_STREAM, 0);
+
+#if ENABLE_FEATURE_BRCTL_SHOW
+               if (key == ARG_show) { /* show */
+                       char brname[IFNAMSIZ];
+                       int bridx[MAX_PORTS];
+                       int i, num;
+                       arm_ioctl(args, BRCTL_GET_BRIDGES,
+                                               (unsigned long) bridx, MAX_PORTS);
+                       num = xioctl(fd, SIOCGIFBR, args);
+                       printf("bridge name\tbridge id\t\tSTP enabled\tinterfaces\n");
+                       for (i = 0; i < num; i++) {
+                               char ifname[IFNAMSIZ];
+                               int j, tabs;
+                               struct __bridge_info bi;
+                               unsigned char *x;
+
+                               if (!if_indextoname(bridx[i], brname))
+                                       bb_perror_msg_and_die("can't get bridge name for index %d", i);
+                               strncpy_IFNAMSIZ(ifr.ifr_name, brname);
+
+                               arm_ioctl(args, BRCTL_GET_BRIDGE_INFO,
+                                                       (unsigned long) &bi, 0);
+                               xioctl(fd, SIOCDEVPRIVATE, &ifr);
+                               printf("%s\t\t", brname);
+
+                               /* print bridge id */
+                               x = (unsigned char *) &bi.bridge_id;
+                               for (j = 0; j < 8; j++) {
+                                       printf("%.2x", x[j]);
+                                       if (j == 1)
+                                               bb_putchar('.');
+                               }
+                               printf(bi.stp_enabled ? "\tyes" : "\tno");
+
+                               /* print interface list */
+                               arm_ioctl(args, BRCTL_GET_PORT_LIST,
+                                                       (unsigned long) ifidx, MAX_PORTS);
+                               xioctl(fd, SIOCDEVPRIVATE, &ifr);
+                               tabs = 0;
+                               for (j = 0; j < MAX_PORTS; j++) {
+                                       if (!ifidx[j])
+                                               continue;
+                                       if (!if_indextoname(ifidx[j], ifname))
+                                               bb_perror_msg_and_die("can't get interface name for index %d", j);
+                                       if (tabs)
+                                               printf("\t\t\t\t\t");
+                                       else
+                                               tabs = 1;
+                                       printf("\t\t%s\n", ifname);
+                               }
+                               if (!tabs)      /* bridge has no interfaces */
+                                       bb_putchar('\n');
+                       }
+                       goto done;
+               }
+#endif
+
+               if (!*argv) /* all but 'show' need at least one argument */
+                       bb_show_usage();
+
+               br = *argv++;
+
+               if (key == ARG_addbr || key == ARG_delbr) { /* addbr or delbr */
+                       ioctl_or_perror_and_die(fd,
+                                       key == ARG_addbr ? SIOCBRADDBR : SIOCBRDELBR,
+                                       br, "bridge %s", br);
+                       goto done;
+               }
+
+               if (!*argv) /* all but 'addif/delif' need at least two arguments */
+                       bb_show_usage();
+
+               strncpy_IFNAMSIZ(ifr.ifr_name, br);
+               if (key == ARG_addif || key == ARG_delif) { /* addif or delif */
+                       brif = *argv;
+                       ifr.ifr_ifindex = if_nametoindex(brif);
+                       if (!ifr.ifr_ifindex) {
+                               bb_perror_msg_and_die("iface %s", brif);
+                       }
+                       ioctl_or_perror_and_die(fd,
+                                       key == ARG_addif ? SIOCBRADDIF : SIOCBRDELIF,
+                                       &ifr, "bridge %s", br);
+                       goto done_next_argv;
+               }
+#if ENABLE_FEATURE_BRCTL_FANCY
+               if (key == ARG_stp) { /* stp */
+                       /* FIXME: parsing yes/y/on/1 versus no/n/off/0 is too involved */
+                       arm_ioctl(args, BRCTL_SET_BRIDGE_STP_STATE,
+                                         (unsigned)(**argv - '0'), 0);
+                       goto fire;
+               }
+               if ((unsigned)(key - ARG_setageing) < 4) { /* time related ops */
+                       static const uint8_t ops[] ALIGN1 = {
+                               BRCTL_SET_AGEING_TIME,          /* ARG_setageing */
+                               BRCTL_SET_BRIDGE_FORWARD_DELAY, /* ARG_setfd     */
+                               BRCTL_SET_BRIDGE_HELLO_TIME,    /* ARG_sethello  */
+                               BRCTL_SET_BRIDGE_MAX_AGE        /* ARG_setmaxage */
+                       };
+                       arm_ioctl(args, ops[key - ARG_setageing], str_to_jiffies(*argv), 0);
+                       goto fire;
+               }
+               if (key == ARG_setpathcost
+                || key == ARG_setportprio
+                || key == ARG_setbridgeprio
+               ) {
+                       static const uint8_t ops[] ALIGN1 = {
+                               BRCTL_SET_PATH_COST,      /* ARG_setpathcost   */
+                               BRCTL_SET_PORT_PRIORITY,  /* ARG_setportprio   */
+                               BRCTL_SET_BRIDGE_PRIORITY /* ARG_setbridgeprio */
+                       };
+                       int port = -1;
+                       unsigned arg1, arg2;
+
+                       if (key != ARG_setbridgeprio) {
+                               /* get portnum */
+                               unsigned i;
+
+                               port = if_nametoindex(*argv++);
+                               if (!port)
+                                       bb_error_msg_and_die(bb_msg_invalid_arg, *argv, "port");
+                               memset(ifidx, 0, sizeof ifidx);
+                               arm_ioctl(args, BRCTL_GET_PORT_LIST, (unsigned long)ifidx,
+                                                 MAX_PORTS);
+                               xioctl(fd, SIOCDEVPRIVATE, &ifr);
+                               for (i = 0; i < MAX_PORTS; i++) {
+                                       if (ifidx[i] == port) {
+                                               port = i;
+                                               break;
+                                       }
+                               }
+                       }
+                       arg1 = port;
+                       arg2 = xatoi_u(*argv);
+                       if (key == ARG_setbridgeprio) {
+                               arg1 = arg2;
+                               arg2 = 0;
+                       }
+                       arm_ioctl(args, ops[key - ARG_setpathcost], arg1, arg2);
+               }
+ fire:
+               /* Execute the previously set command */
+               xioctl(fd, SIOCDEVPRIVATE, &ifr);
+#endif
+ done_next_argv:
+               argv++;
+ done:
+               close(fd);
+       }
+
+       return EXIT_SUCCESS;
+}
diff --git a/networking/dnsd.c b/networking/dnsd.c
new file mode 100644 (file)
index 0000000..56ede3f
--- /dev/null
@@ -0,0 +1,518 @@
+/* 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 "libbb.h"
+#include <syslog.h>
+
+//#define DEBUG 1
+#define DEBUG 0
+
+enum {
+       /* can tweak this */
+       DEFAULT_TTL = 120,
+
+       /* cannot get bigger packets than 512 per RFC1035. */
+       MAX_PACK_LEN = 512,
+       IP_STRING_LEN = sizeof(".xxx.xxx.xxx.xxx"),
+       MAX_NAME_LEN = IP_STRING_LEN - 1 + sizeof(".in-addr.arpa"),
+       REQ_A = 1,
+       REQ_PTR = 12,
+};
+
+/* the message from client and first part of response msg */
+struct dns_head {
+       uint16_t id;
+       uint16_t flags;
+       uint16_t nquer;
+       uint16_t nansw;
+       uint16_t nauth;
+       uint16_t nadd;
+};
+struct dns_prop {
+       uint16_t type;
+       uint16_t class;
+};
+/* element of known name, ip address and reversed ip address */
+struct dns_entry {
+       struct dns_entry *next;
+       uint32_t ip;
+       char rip[IP_STRING_LEN]; /* length decimal reversed IP */
+       char name[1];
+};
+
+#define OPT_verbose (option_mask32)
+
+
+/*
+ * Insert length of substrings instead of dots
+ */
+static void undot(char *rip)
+{
+       int i = 0;
+       int s = 0;
+
+       while (rip[i])
+               i++;
+       for (--i; i >= 0; i--) {
+               if (rip[i] == '.') {
+                       rip[i] = s;
+                       s = 0;
+               } else {
+                       s++;
+               }
+       }
+}
+
+/*
+ * Read hostname/IP records from file
+ */
+static struct dns_entry *parse_conf_file(const char *fileconf)
+{
+       char *token[2];
+       parser_t *parser;
+       struct dns_entry *m, *conf_data;
+       struct dns_entry **nextp;
+
+       conf_data = NULL;
+       nextp = &conf_data;
+
+       parser = config_open(fileconf);
+       while (config_read(parser, token, 2, 2, "# \t", PARSE_NORMAL)) {
+               struct in_addr ip;
+               uint32_t v32;
+
+               if (inet_aton(token[1], &ip) == 0) {
+                       bb_error_msg("error at line %u, skipping", parser->lineno);
+                       continue;
+               }
+
+               if (OPT_verbose)
+                       bb_error_msg("name:%s, ip:%s", token[0], token[1]);
+
+               /* sizeof(*m) includes 1 byte for m->name[0] */
+               m = xzalloc(sizeof(*m) + strlen(token[0]) + 1);
+               /*m->next = NULL;*/
+               *nextp = m;
+               nextp = &m->next;
+
+               m->name[0] = '.';
+               strcpy(m->name + 1, token[0]);
+               undot(m->name);
+               m->ip = ip.s_addr; /* in network order */
+               v32 = ntohl(m->ip);
+               /* inverted order */
+               sprintf(m->rip, ".%u.%u.%u.%u",
+                       (uint8_t)(v32),
+                       (uint8_t)(v32 >> 8),
+                       (uint8_t)(v32 >> 16),
+                       (v32 >> 24)
+               );
+               undot(m->rip);
+       }
+       config_close(parser);
+       return conf_data;
+}
+
+/*
+ * Look query up in dns records and return answer if found.
+ */
+static char *table_lookup(struct dns_entry *d,
+               uint16_t type,
+               char* query_string)
+{
+       while (d) {
+               unsigned len = d->name[0];
+               /* d->name[len] is the last (non NUL) char */
+#if DEBUG
+               char *p, *q;
+               q = query_string + 1;
+               p = d->name + 1;
+               fprintf(stderr, "%d/%d p:%s q:%s %d\n",
+                       (int)strlen(p), len,
+                       p, q, (int)strlen(q)
+               );
+#endif
+               if (type == htons(REQ_A)) {
+                       /* search by host name */
+                       if (len != 1 || d->name[1] != '*') {
+/* we are lax, hope no name component is ever >64 so that length
+ * (which will be represented as 'A','B'...) matches a lowercase letter.
+ * Actually, I think false matches are hard to construct.
+ * Example.
+ * [31] len is represented as '1', [65] as 'A', [65+32] as 'a'.
+ * [65]   <65 same chars>[31]<31 same chars>NUL
+ * [65+32]<65 same chars>1   <31 same chars>NUL
+ * This example seems to be the minimal case when false match occurs.
+ */
+                               if (strcasecmp(d->name, query_string) != 0)
+                                       goto next;
+                       }
+                       return (char *)&d->ip;
+#if DEBUG
+                       fprintf(stderr, "Found IP:%x\n", (int)d->ip);
+#endif
+                       return 0;
+               }
+               /* search by IP-address */
+               if ((len != 1 || d->name[1] != '*')
+               /* we assume (do not check) that query_string
+                * ends in ".in-addr.arpa" */
+                && strncmp(d->rip, query_string, strlen(d->rip)) == 0
+               ) {
+#if DEBUG
+                       fprintf(stderr, "Found name:%s\n", d->name);
+#endif
+                       return d->name;
+               }
+ next:
+               d = d->next;
+       }
+
+       return NULL;
+}
+
+/*
+ * Decode message and generate answer
+ */
+/* RFC 1035
+...
+Whenever an octet represents a numeric quantity, the left most bit
+in the diagram is the high order or most significant bit.
+That is, the bit labeled 0 is the most significant bit.
+...
+
+4.1.1. Header section format
+      0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15
+    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+    |                      ID                       |
+    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+    |QR|   OPCODE  |AA|TC|RD|RA| 0  0  0|   RCODE   |
+    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+    |                    QDCOUNT                    |
+    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+    |                    ANCOUNT                    |
+    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+    |                    NSCOUNT                    |
+    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+    |                    ARCOUNT                    |
+    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ID      16 bit random identifier assigned by querying peer.
+        Used to match query/response.
+QR      message is a query (0), or a response (1).
+OPCODE  0   standard query (QUERY)
+        1   inverse query (IQUERY)
+        2   server status request (STATUS)
+AA      Authoritative Answer - this bit is valid in responses.
+        Responding name server is an authority for the domain name
+        in question section. Answer section may have multiple owner names
+        because of aliases.  The AA bit corresponds to the name which matches
+        the query name, or the first owner name in the answer section.
+TC      TrunCation - this message was truncated.
+RD      Recursion Desired - this bit may be set in a query and
+        is copied into the response.  If RD is set, it directs
+        the name server to pursue the query recursively.
+        Recursive query support is optional.
+RA      Recursion Available - this be is set or cleared in a
+        response, and denotes whether recursive query support is
+        available in the name server.
+RCODE   Response code.
+        0   No error condition
+        1   Format error
+        2   Server failure - server was unable to process the query
+            due to a problem with the name server.
+        3   Name Error - meaningful only for responses from
+            an authoritative name server. The referenced domain name
+            does not exist.
+        4   Not Implemented.
+        5   Refused.
+QDCOUNT number of entries in the question section.
+ANCOUNT number of records in the answer section.
+NSCOUNT number of records in the authority records section.
+ARCOUNT number of records in the additional records section.
+
+4.1.2. Question section format
+
+The section contains QDCOUNT (usually 1) entries, each of this format:
+      0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15
+    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+    /                     QNAME                     /
+    /                                               /
+    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+    |                     QTYPE                     |
+    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+    |                     QCLASS                    |
+    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+QNAME   a domain name represented as a sequence of labels, where
+        each label consists of a length octet followed by that
+        number of octets. The domain name terminates with the
+        zero length octet for the null label of the root. Note
+        that this field may be an odd number of octets; no
+        padding is used.
+QTYPE   a two octet type of the query.
+          1 a host address [REQ_A const]
+          2 an authoritative name server
+          3 a mail destination (Obsolete - use MX)
+          4 a mail forwarder (Obsolete - use MX)
+          5 the canonical name for an alias
+          6 marks the start of a zone of authority
+          7 a mailbox domain name (EXPERIMENTAL)
+          8 a mail group member (EXPERIMENTAL)
+          9 a mail rename domain name (EXPERIMENTAL)
+         10 a null RR (EXPERIMENTAL)
+         11 a well known service description
+         12 a domain name pointer [REQ_PTR const]
+         13 host information
+         14 mailbox or mail list information
+         15 mail exchange
+         16 text strings
+       0x1c IPv6?
+        252 a request for a transfer of an entire zone
+        253 a request for mailbox-related records (MB, MG or MR)
+        254 a request for mail agent RRs (Obsolete - see MX)
+        255 a request for all records
+QCLASS  a two octet code that specifies the class of the query.
+          1 the Internet
+        (others are historic only)
+        255 any class
+
+4.1.3. Resource record format
+
+The answer, authority, and additional sections all share the same format:
+a variable number of resource records, where the number of records
+is specified in the corresponding count field in the header.
+Each resource record has this format:
+      0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15
+    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+    /                                               /
+    /                      NAME                     /
+    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+    |                      TYPE                     |
+    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+    |                     CLASS                     |
+    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+    |                      TTL                      |
+    |                                               |
+    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+    |                   RDLENGTH                    |
+    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--|
+    /                     RDATA                     /
+    /                                               /
+    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+NAME    a domain name to which this resource record pertains.
+TYPE    two octets containing one of the RR type codes.  This
+        field specifies the meaning of the data in the RDATA field.
+CLASS   two octets which specify the class of the data in the RDATA field.
+TTL     a 32 bit unsigned integer that specifies the time interval
+        (in seconds) that the record may be cached.
+RDLENGTH a 16 bit integer, length in octets of the RDATA field.
+RDATA   a variable length string of octets that describes the resource.
+        The format of this information varies according to the TYPE
+        and CLASS of the resource record.
+        If the TYPE is A and the CLASS is IN, it's a 4 octet IP address.
+
+4.1.4. Message compression
+
+In order to reduce the size of messages, domain names coan be compressed.
+An entire domain name or a list of labels at the end of a domain name
+is replaced with a pointer to a prior occurance of the same name.
+
+The pointer takes the form of a two octet sequence:
+    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+    | 1  1|                OFFSET                   |
+    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+The first two bits are ones.  This allows a pointer to be distinguished
+from a label, since the label must begin with two zero bits because
+labels are restricted to 63 octets or less.  The OFFSET field specifies
+an offset from the start of the message (i.e., the first octet
+of the ID field in the domain header).
+A zero offset specifies the first byte of the ID field, etc.
+Domain name in a message can be represented as either:
+   - a sequence of labels ending in a zero octet
+   - a pointer
+   - a sequence of labels ending with a pointer
+ */
+static int process_packet(struct dns_entry *conf_data,
+               uint32_t conf_ttl,
+               uint8_t *buf)
+{
+       char *answstr;
+       struct dns_head *head;
+       struct dns_prop *unaligned_qprop;
+       char *query_string;
+       uint8_t *answb;
+       uint16_t outr_rlen;
+       uint16_t outr_flags;
+       uint16_t type;
+       uint16_t class;
+       int querystr_len;
+
+       head = (struct dns_head *)buf;
+       if (head->nquer == 0) {
+               bb_error_msg("packet has 0 queries, ignored");
+               return -1;
+       }
+
+       if (head->flags & htons(0x8000)) { /* QR bit */
+               bb_error_msg("response packet, ignored");
+               return -1;
+       }
+
+       /* start of query string */
+       query_string = (void *)(head + 1);
+       /* caller guarantees strlen is <= MAX_PACK_LEN */
+       querystr_len = strlen(query_string) + 1;
+       /* may be unaligned! */
+       unaligned_qprop = (void *)(query_string + querystr_len);
+       querystr_len += sizeof(unaligned_qprop);
+       /* where to append answer block */
+       answb = (void *)(unaligned_qprop + 1);
+
+       /* QR = 1 "response", RCODE = 4 "Not Implemented" */
+       outr_flags = htons(0x8000 | 4);
+
+       move_from_unaligned16(type, &unaligned_qprop->type);
+       if (type != htons(REQ_A) && type != htons(REQ_PTR)) {
+               /* we can't handle the query type */
+               goto empty_packet;
+       }
+       move_from_unaligned16(class, &unaligned_qprop->class);
+       if (class != htons(1)) { /* not class INET? */
+               goto empty_packet;
+       }
+       /* OPCODE != 0 "standard query" ? */
+       if ((head->flags & htons(0x7800)) != 0) {
+               goto empty_packet;
+       }
+
+       /* look up the name */
+#if DEBUG
+       /* need to convert lengths to dots before we can use it in non-debug */
+       bb_info_msg("%s", query_string);
+#endif
+       answstr = table_lookup(conf_data, type, query_string);
+       outr_rlen = 4;
+       if (answstr && type == htons(REQ_PTR)) {
+               /* return a host name */
+               outr_rlen = strlen(answstr) + 1;
+       }
+       if (!answstr
+        || (unsigned)(answb - buf) + querystr_len + 4 + 2 + outr_rlen > MAX_PACK_LEN
+       ) {
+               /* QR = 1 "response"
+                * AA = 1 "Authoritative Answer"
+                * RCODE = 3 "Name Error" */
+               outr_flags = htons(0x8000 | 0x0400 | 3);
+               goto empty_packet;
+       }
+
+       /* copy query block to answer block */
+       memcpy(answb, query_string, querystr_len);
+       answb += querystr_len;
+       /* append answer Resource Record */
+       move_to_unaligned32((uint32_t *)answb, htonl(conf_ttl));
+       answb += 4;
+       move_to_unaligned32((uint16_t *)answb, htons(outr_rlen));
+       answb += 2;
+       memcpy(answb, answstr, outr_rlen);
+       answb += outr_rlen;
+
+       /* QR = 1 "response",
+        * AA = 1 "Authoritative Answer",
+        * RCODE = 0 "success" */
+       outr_flags = htons(0x8000 | 0x0400 | 0);
+       /* we have one answer */
+       head->nansw = htons(1);
+
+ empty_packet:
+       head->flags |= outr_flags;
+       head->nauth = head->nadd = 0;
+       head->nquer = htons(1); // why???
+
+       return answb - buf;
+}
+
+int dnsd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int dnsd_main(int argc UNUSED_PARAM, char **argv)
+{
+       const char *listen_interface = "0.0.0.0";
+       const char *fileconf = "/etc/dnsd.conf";
+       struct dns_entry *conf_data;
+       uint32_t conf_ttl = DEFAULT_TTL;
+       char *sttl, *sport;
+       len_and_sockaddr *lsa, *from, *to;
+       unsigned lsa_size;
+       int udps, opts;
+       uint16_t port = 53;
+       uint8_t buf[MAX_PACK_LEN + 1];
+
+       opts = getopt32(argv, "vi:c:t:p:d", &listen_interface, &fileconf, &sttl, &sport);
+       //if (opts & 0x1) // -v
+       //if (opts & 0x2) // -i
+       //if (opts & 0x4) // -c
+       if (opts & 0x8) // -t
+               conf_ttl = xatou_range(sttl, 1, 0xffffffff);
+       if (opts & 0x10) // -p
+               port = xatou_range(sport, 1, 0xffff);
+       if (opts & 0x20) { // -d
+               bb_daemonize_or_rexec(DAEMON_CLOSE_EXTRA_FDS, argv);
+               openlog(applet_name, LOG_PID, LOG_DAEMON);
+               logmode = LOGMODE_SYSLOG;
+       }
+       /* Clear all except "verbose" bit */
+       option_mask32 &= 1;
+
+       conf_data = parse_conf_file(fileconf);
+
+       lsa = xdotted2sockaddr(listen_interface, port);
+       udps = xsocket(lsa->u.sa.sa_family, SOCK_DGRAM, 0);
+       xbind(udps, &lsa->u.sa, lsa->len);
+       socket_want_pktinfo(udps); /* needed for recv_from_to to work */
+       lsa_size = LSA_LEN_SIZE + lsa->len;
+       from = xzalloc(lsa_size);
+       to = xzalloc(lsa_size);
+
+       {
+               char *p = xmalloc_sockaddr2dotted(&lsa->u.sa);
+               bb_info_msg("Accepting UDP packets on %s", p);
+               free(p);
+       }
+
+       while (1) {
+               int r;
+               /* Try to get *DEST* address (to which of our addresses
+                * this query was directed), and reply from the same address.
+                * Or else we can exhibit usual UDP ugliness:
+                * [ip1.multihomed.ip2] <=  query to ip1  <= peer
+                * [ip1.multihomed.ip2] => reply from ip2 => peer (confused) */
+               memcpy(to, lsa, lsa_size);
+               r = recv_from_to(udps, buf, MAX_PACK_LEN + 1, 0, &from->u.sa, &to->u.sa, lsa->len);
+               if (r < 12 || r > MAX_PACK_LEN) {
+                       bb_error_msg("packet size %d, ignored", r);
+                       continue;
+               }
+               if (OPT_verbose)
+                       bb_info_msg("Got UDP packet");
+               buf[r] = '\0'; /* paranoia */
+               r = process_packet(conf_data, conf_ttl, buf);
+               if (r <= 0)
+                       continue;
+               send_to_from(udps, buf, r, 0, &from->u.sa, &to->u.sa, lsa->len);
+       }
+       return 0;
+}
diff --git a/networking/ether-wake.c b/networking/ether-wake.c
new file mode 100644 (file)
index 0000000..882429d
--- /dev/null
@@ -0,0 +1,276 @@
+/* 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 <haveaniceday@online.de>
+ *               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 <ifname>] [-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 <pw>         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 <netpacket/packet.h>
+#include <net/ethernet.h>
+#include <netinet/ether.h>
+#include <linux/if.h>
+
+#include "libbb.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) bb_putchar('\n');
+       }
+       printf("\n\n");
+}
+#else
+# define bb_debug_msg(fmt, args...)             ((void)0)
+# define bb_debug_dump_packet(outpack, pktsize) ((void)0)
+#endif
+
+/* Convert the host ID string to a MAC address.
+ * The string may be a:
+ *    Host name
+ *    IP address string
+ *    MAC address string
+*/
+static 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));
+#endif
+       } else
+               bb_show_usage();
+}
+
+static int get_fill(unsigned char *pkt, struct ether_addr *eaddr, int broadcast)
+{
+       int i;
+       unsigned char *station_addr = eaddr->ether_addr_octet;
+
+       memset(pkt, 0xff, 6);
+       if (!broadcast)
+               memcpy(pkt, station_addr, 6);
+       pkt += 6;
+
+       memcpy(pkt, station_addr, 6); /* 6 */
+       pkt += 6;
+
+       *pkt++ = 0x08; /* 12 */ /* Or 0x0806 for ARP, 0x8035 for RARP */
+       *pkt++ = 0x42; /* 13 */
+
+       memset(pkt, 0xff, 6); /* 14 */
+
+       for (i = 0; i < 16; ++i) {
+               pkt += 6;
+               memcpy(pkt, station_addr, 6); /* 20,26,32,... */
+       }
+
+       return 20 + 16*6; /* length of packet */
+}
+
+static int get_wol_pw(const char *ethoptarg, unsigned char *wol_passwd)
+{
+       unsigned 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 */
+// FIXME: why < 4?? should it be < 6?
+       if (byte_cnt < 4)
+               byte_cnt = sscanf(ethoptarg, "%u.%u.%u.%u",
+                                 &passwd[0], &passwd[1], &passwd[2], &passwd[3]);
+       if (byte_cnt < 4) {
+               bb_error_msg("cannot read Wake-On-LAN pass");
+               return 0;
+       }
+// TODO: check invalid numbers >255??
+       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;
+}
+
+int ether_wake_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int ether_wake_main(int argc UNUSED_PARAM, char **argv)
+{
+       const char *ifname = "eth0";
+       char *pass;
+       unsigned 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 */
+       opt_complementary = "=1";
+       flags = getopt32(argv, "bi:p:", &ifname, &pass);
+       if (flags & 4) /* -p */
+               wol_passwd_sz = get_wol_pw(pass, wol_passwd);
+       flags &= 1; /* we further interested only in -b [bcast] flag */
+
+       /* create the raw socket */
+       s = make_socket();
+
+       /* now that we have a raw socket we can drop root */
+       /* xsetuid(getuid()); - but save on code size... */
+
+       /* 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_IFNAMSIZ(if_hwaddr.ifr_name, ifname);
+               ioctl_or_perror_and_die(s, SIOCGIFHWADDR, &if_hwaddr, "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_IFNAMSIZ(ifr.ifr_name, ifname);
+               xioctl(s, SIOCGIFINDEX, &ifr);
+               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
+       xsendto(s, outpack, pktsize, (struct sockaddr *)&whereto, sizeof(whereto));
+       if (ENABLE_FEATURE_CLEAN_UP)
+               close(s);
+       return EXIT_SUCCESS;
+}
diff --git a/networking/ftpd.c b/networking/ftpd.c
new file mode 100644 (file)
index 0000000..e85e4f8
--- /dev/null
@@ -0,0 +1,1351 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Simple FTP daemon, based on vsftpd 2.0.7 (written by Chris Evans)
+ *
+ * Author: Adam Tkac <vonsch@gmail.com>
+ *
+ * Licensed under GPLv2, see file LICENSE in this tarball for details.
+ *
+ * Only subset of FTP protocol is implemented but vast majority of clients
+ * should not have any problem.
+ *
+ * You have to run this daemon via inetd.
+ */
+
+#include "libbb.h"
+#include <syslog.h>
+#include <netinet/tcp.h>
+
+#define FTP_DATACONN            150
+#define FTP_NOOPOK              200
+#define FTP_TYPEOK              200
+#define FTP_PORTOK              200
+#define FTP_STRUOK              200
+#define FTP_MODEOK              200
+#define FTP_ALLOOK              202
+#define FTP_STATOK              211
+#define FTP_STATFILE_OK         213
+#define FTP_HELP                214
+#define FTP_SYSTOK              215
+#define FTP_GREET               220
+#define FTP_GOODBYE             221
+#define FTP_TRANSFEROK          226
+#define FTP_PASVOK              227
+/*#define FTP_EPRTOK              228*/
+#define FTP_EPSVOK              229
+#define FTP_LOGINOK             230
+#define FTP_CWDOK               250
+#define FTP_RMDIROK             250
+#define FTP_DELEOK              250
+#define FTP_RENAMEOK            250
+#define FTP_PWDOK               257
+#define FTP_MKDIROK             257
+#define FTP_GIVEPWORD           331
+#define FTP_RESTOK              350
+#define FTP_RNFROK              350
+#define FTP_TIMEOUT             421
+#define FTP_BADSENDCONN         425
+#define FTP_BADSENDNET          426
+#define FTP_BADSENDFILE         451
+#define FTP_BADCMD              500
+#define FTP_COMMANDNOTIMPL      502
+#define FTP_NEEDUSER            503
+#define FTP_NEEDRNFR            503
+#define FTP_BADSTRU             504
+#define FTP_BADMODE             504
+#define FTP_LOGINERR            530
+#define FTP_FILEFAIL            550
+#define FTP_NOPERM              550
+#define FTP_UPLOADFAIL          553
+
+#define STR1(s) #s
+#define STR(s) STR1(s)
+
+/* Convert a constant to 3-digit string, packed into uint32_t */
+enum {
+       /* Shift for Nth decimal digit */
+       SHIFT2  =  0 * BB_LITTLE_ENDIAN + 24 * BB_BIG_ENDIAN,
+       SHIFT1  =  8 * BB_LITTLE_ENDIAN + 16 * BB_BIG_ENDIAN,
+       SHIFT0  = 16 * BB_LITTLE_ENDIAN + 8 * BB_BIG_ENDIAN,
+       /* And for 4th position (space) */
+       SHIFTsp = 24 * BB_LITTLE_ENDIAN + 0 * BB_BIG_ENDIAN,
+};
+#define STRNUM32(s) (uint32_t)(0 \
+       | (('0' + ((s) / 1 % 10)) << SHIFT0) \
+       | (('0' + ((s) / 10 % 10)) << SHIFT1) \
+       | (('0' + ((s) / 100 % 10)) << SHIFT2) \
+)
+#define STRNUM32sp(s) (uint32_t)(0 \
+       | (' ' << SHIFTsp) \
+       | (('0' + ((s) / 1 % 10)) << SHIFT0) \
+       | (('0' + ((s) / 10 % 10)) << SHIFT1) \
+       | (('0' + ((s) / 100 % 10)) << SHIFT2) \
+)
+
+#define MSG_OK "Operation successful\r\n"
+#define MSG_ERR "Error\r\n"
+
+struct globals {
+       int pasv_listen_fd;
+#if !BB_MMU
+       int root_fd;
+#endif
+       int local_file_fd;
+       unsigned end_time;
+       unsigned timeout;
+       unsigned verbose;
+       off_t local_file_pos;
+       off_t restart_pos;
+       len_and_sockaddr *local_addr;
+       len_and_sockaddr *port_addr;
+       char *ftp_cmd;
+       char *ftp_arg;
+#if ENABLE_FEATURE_FTP_WRITE
+       char *rnfr_filename;
+#endif
+       /* We need these aligned to uint32_t */
+       char msg_ok [(sizeof("NNN " MSG_OK ) + 3) & 0xfffc];
+       char msg_err[(sizeof("NNN " MSG_ERR) + 3) & 0xfffc];
+};
+#define G (*(struct globals*)&bb_common_bufsiz1)
+#define INIT_G() do { \
+       /* Moved to main */ \
+       /*strcpy(G.msg_ok  + 4, MSG_OK );*/ \
+       /*strcpy(G.msg_err + 4, MSG_ERR);*/ \
+} while (0)
+
+
+static char *
+escape_text(const char *prepend, const char *str, unsigned escapee)
+{
+       unsigned retlen, remainlen, chunklen;
+       char *ret, *found;
+       char append;
+
+       append = (char)escapee;
+       escapee >>= 8;
+
+       remainlen = strlen(str);
+       retlen = strlen(prepend);
+       ret = xmalloc(retlen + remainlen * 2 + 1 + 1);
+       strcpy(ret, prepend);
+
+       for (;;) {
+               found = strchrnul(str, escapee);
+               chunklen = found - str + 1;
+
+               /* Copy chunk up to and including escapee (or NUL) to ret */
+               memcpy(ret + retlen, str, chunklen);
+               retlen += chunklen;
+
+               if (*found == '\0') {
+                       /* It wasn't escapee, it was NUL! */
+                       ret[retlen - 1] = append; /* replace NUL */
+                       ret[retlen] = '\0'; /* add NUL */
+                       break;
+               }
+               ret[retlen++] = escapee; /* duplicate escapee */
+               str = found + 1;
+       }
+       return ret;
+}
+
+/* Returns strlen as a bonus */
+static unsigned
+replace_char(char *str, char from, char to)
+{
+       char *p = str;
+       while (*p) {
+               if (*p == from)
+                       *p = to;
+               p++;
+       }
+       return p - str;
+}
+
+static void
+verbose_log(const char *str)
+{
+       bb_error_msg("%.*s", (int)strcspn(str, "\r\n"), str);
+}
+
+/* NB: status_str is char[4] packed into uint32_t */
+static void
+cmdio_write(uint32_t status_str, const char *str)
+{
+       char *response;
+       int len;
+
+       /* FTP uses telnet protocol for command link.
+        * In telnet, 0xff is an escape char, and needs to be escaped: */
+       response = escape_text((char *) &status_str, str, (0xff << 8) + '\r');
+
+       /* FTP sends embedded LFs as NULs */
+       len = replace_char(response, '\n', '\0');
+
+       response[len++] = '\n'; /* tack on trailing '\n' */
+       xwrite(STDOUT_FILENO, response, len);
+       if (G.verbose > 1)
+               verbose_log(response);
+       free(response);
+}
+
+static void
+cmdio_write_ok(unsigned status)
+{
+       *(uint32_t *) G.msg_ok = status;
+       xwrite(STDOUT_FILENO, G.msg_ok, sizeof("NNN " MSG_OK) - 1);
+       if (G.verbose > 1)
+               verbose_log(G.msg_ok);
+}
+#define WRITE_OK(a) cmdio_write_ok(STRNUM32sp(a))
+
+/* TODO: output strerr(errno) if errno != 0? */
+static void
+cmdio_write_error(unsigned status)
+{
+       *(uint32_t *) G.msg_err = status;
+       xwrite(STDOUT_FILENO, G.msg_err, sizeof("NNN " MSG_ERR) - 1);
+       if (G.verbose > 1)
+               verbose_log(G.msg_err);
+}
+#define WRITE_ERR(a) cmdio_write_error(STRNUM32sp(a))
+
+static void
+cmdio_write_raw(const char *p_text)
+{
+       xwrite_str(STDOUT_FILENO, p_text);
+       if (G.verbose > 1)
+               verbose_log(p_text);
+}
+
+static void
+timeout_handler(int sig UNUSED_PARAM)
+{
+       off_t pos;
+       int sv_errno = errno;
+
+       if ((int)(monotonic_sec() - G.end_time) >= 0)
+               goto timed_out;
+
+       if (!G.local_file_fd)
+               goto timed_out;
+
+       pos = xlseek(G.local_file_fd, 0, SEEK_CUR);
+       if (pos == G.local_file_pos)
+               goto timed_out;
+       G.local_file_pos = pos;
+
+       alarm(G.timeout);
+       errno = sv_errno;
+       return;
+
+ timed_out:
+       cmdio_write_raw(STR(FTP_TIMEOUT)" Timeout\r\n");
+/* TODO: do we need to abort (as opposed to usual shutdown) data transfer? */
+       exit(1);
+}
+
+/* Simple commands */
+
+static void
+handle_pwd(void)
+{
+       char *cwd, *response;
+
+       cwd = xrealloc_getcwd_or_warn(NULL);
+       if (cwd == NULL)
+               cwd = xstrdup("");
+
+       /* We have to promote each " to "" */
+       response = escape_text(" \"", cwd, ('"' << 8) + '"');
+       free(cwd);
+       cmdio_write(STRNUM32(FTP_PWDOK), response);
+       free(response);
+}
+
+static void
+handle_cwd(void)
+{
+       if (!G.ftp_arg || chdir(G.ftp_arg) != 0) {
+               WRITE_ERR(FTP_FILEFAIL);
+               return;
+       }
+       WRITE_OK(FTP_CWDOK);
+}
+
+static void
+handle_cdup(void)
+{
+       G.ftp_arg = (char*)"..";
+       handle_cwd();
+}
+
+static void
+handle_stat(void)
+{
+       cmdio_write_raw(STR(FTP_STATOK)"-Server status:\r\n"
+                       " TYPE: BINARY\r\n"
+                       STR(FTP_STATOK)" Ok\r\n");
+}
+
+/* Examples of HELP and FEAT:
+# nc -vvv ftp.kernel.org 21
+ftp.kernel.org (130.239.17.4:21) open
+220 Welcome to ftp.kernel.org.
+FEAT
+211-Features:
+ EPRT
+ EPSV
+ MDTM
+ PASV
+ REST STREAM
+ SIZE
+ TVFS
+ UTF8
+211 End
+HELP
+214-The following commands are recognized.
+ ABOR ACCT ALLO APPE CDUP CWD  DELE EPRT EPSV FEAT HELP LIST MDTM MKD
+ MODE NLST NOOP OPTS PASS PASV PORT PWD  QUIT REIN REST RETR RMD  RNFR
+ RNTO SITE SIZE SMNT STAT STOR STOU STRU SYST TYPE USER XCUP XCWD XMKD
+ XPWD XRMD
+214 Help OK.
+*/
+static void
+handle_feat(unsigned status)
+{
+       cmdio_write(status, "-Features:");
+       cmdio_write_raw(" EPSV\r\n"
+                       " PASV\r\n"
+                       " REST STREAM\r\n"
+                       " MDTM\r\n"
+                       " SIZE\r\n");
+       cmdio_write(status, " Ok");
+}
+
+/* Download commands */
+
+static inline int
+port_active(void)
+{
+       return (G.port_addr != NULL);
+}
+
+static inline int
+pasv_active(void)
+{
+       return (G.pasv_listen_fd > STDOUT_FILENO);
+}
+
+static void
+port_pasv_cleanup(void)
+{
+       free(G.port_addr);
+       G.port_addr = NULL;
+       if (G.pasv_listen_fd > STDOUT_FILENO)
+               close(G.pasv_listen_fd);
+       G.pasv_listen_fd = -1;
+}
+
+/* On error, emits error code to the peer */
+static int
+ftpdataio_get_pasv_fd(void)
+{
+       int remote_fd;
+
+       remote_fd = accept(G.pasv_listen_fd, NULL, 0);
+
+       if (remote_fd < 0) {
+               WRITE_ERR(FTP_BADSENDCONN);
+               return remote_fd;
+       }
+
+       setsockopt(remote_fd, SOL_SOCKET, SO_KEEPALIVE, &const_int_1, sizeof(const_int_1));
+       return remote_fd;
+}
+
+/* Clears port/pasv data.
+ * This means we dont waste resources, for example, keeping
+ * PASV listening socket open when it is no longer needed.
+ * On error, emits error code to the peer (or exits).
+ * On success, emits p_status_msg to the peer.
+ */
+static int
+get_remote_transfer_fd(const char *p_status_msg)
+{
+       int remote_fd;
+
+       if (pasv_active())
+               /* On error, emits error code to the peer */
+               remote_fd = ftpdataio_get_pasv_fd();
+       else
+               /* Exits on error */
+               remote_fd = xconnect_stream(G.port_addr);
+
+       port_pasv_cleanup();
+
+       if (remote_fd < 0)
+               return remote_fd;
+
+       cmdio_write(STRNUM32(FTP_DATACONN), p_status_msg);
+       return remote_fd;
+}
+
+/* If there were neither PASV nor PORT, emits error code to the peer */
+static int
+port_or_pasv_was_seen(void)
+{
+       if (!pasv_active() && !port_active()) {
+               cmdio_write_raw(STR(FTP_BADSENDCONN)" Use PORT/PASV first\r\n");
+               return 0;
+       }
+
+       return 1;
+}
+
+/* Exits on error */
+static unsigned
+bind_for_passive_mode(void)
+{
+       int fd;
+       unsigned port;
+
+       port_pasv_cleanup();
+
+       G.pasv_listen_fd = fd = xsocket(G.local_addr->u.sa.sa_family, SOCK_STREAM, 0);
+       setsockopt_reuseaddr(fd);
+
+       set_nport(G.local_addr, 0);
+       xbind(fd, &G.local_addr->u.sa, G.local_addr->len);
+       xlisten(fd, 1);
+       getsockname(fd, &G.local_addr->u.sa, &G.local_addr->len);
+
+       port = get_nport(&G.local_addr->u.sa);
+       port = ntohs(port);
+       return port;
+}
+
+/* Exits on error */
+static void
+handle_pasv(void)
+{
+       unsigned port;
+       char *addr, *response;
+
+       port = bind_for_passive_mode();
+
+       if (G.local_addr->u.sa.sa_family == AF_INET)
+               addr = xmalloc_sockaddr2dotted_noport(&G.local_addr->u.sa);
+       else /* seen this in the wild done by other ftp servers: */
+               addr = xstrdup("0.0.0.0");
+       replace_char(addr, '.', ',');
+
+       response = xasprintf(STR(FTP_PASVOK)" PASV ok (%s,%u,%u)\r\n",
+                       addr, (int)(port >> 8), (int)(port & 255));
+       free(addr);
+       cmdio_write_raw(response);
+       free(response);
+}
+
+/* Exits on error */
+static void
+handle_epsv(void)
+{
+       unsigned port;
+       char *response;
+
+       port = bind_for_passive_mode();
+       response = xasprintf(STR(FTP_EPSVOK)" EPSV ok (|||%u|)\r\n", port);
+       cmdio_write_raw(response);
+       free(response);
+}
+
+/* libbb candidate */
+static
+len_and_sockaddr* get_peer_lsa(int fd)
+{
+       len_and_sockaddr *lsa;
+       socklen_t len = 0;
+
+       if (getpeername(fd, NULL, &len) != 0)
+               return NULL;
+       lsa = xzalloc(LSA_LEN_SIZE + len);
+       lsa->len = len;
+       getpeername(fd, &lsa->u.sa, &lsa->len);
+       return lsa;
+}
+
+static void
+handle_port(void)
+{
+       unsigned port, port_hi;
+       char *raw, *comma;
+#ifdef WHY_BOTHER_WE_CAN_ASSUME_IP_MATCHES
+       socklen_t peer_ipv4_len;
+       struct sockaddr_in peer_ipv4;
+       struct in_addr port_ipv4_sin_addr;
+#endif
+
+       port_pasv_cleanup();
+
+       raw = G.ftp_arg;
+
+       /* PORT command format makes sense only over IPv4 */
+       if (!raw
+#ifdef WHY_BOTHER_WE_CAN_ASSUME_IP_MATCHES
+        || G.local_addr->u.sa.sa_family != AF_INET
+#endif
+       ) {
+ bail:
+               WRITE_ERR(FTP_BADCMD);
+               return;
+       }
+
+       comma = strrchr(raw, ',');
+       if (comma == NULL)
+               goto bail;
+       *comma = '\0';
+       port = bb_strtou(&comma[1], NULL, 10);
+       if (errno || port > 0xff)
+               goto bail;
+
+       comma = strrchr(raw, ',');
+       if (comma == NULL)
+               goto bail;
+       *comma = '\0';
+       port_hi = bb_strtou(&comma[1], NULL, 10);
+       if (errno || port_hi > 0xff)
+               goto bail;
+       port |= port_hi << 8;
+
+#ifdef WHY_BOTHER_WE_CAN_ASSUME_IP_MATCHES
+       replace_char(raw, ',', '.');
+
+       /* We are verifying that PORT's IP matches getpeername().
+        * Otherwise peer can make us open data connections
+        * to other hosts (security problem!)
+        * This code would be too simplistic:
+        * lsa = xdotted2sockaddr(raw, port);
+        * if (lsa == NULL) goto bail;
+        */
+       if (!inet_aton(raw, &port_ipv4_sin_addr))
+               goto bail;
+       peer_ipv4_len = sizeof(peer_ipv4);
+       if (getpeername(STDIN_FILENO, &peer_ipv4, &peer_ipv4_len) != 0)
+               goto bail;
+       if (memcmp(&port_ipv4_sin_addr, &peer_ipv4.sin_addr, sizeof(struct in_addr)) != 0)
+               goto bail;
+
+       G.port_addr = xdotted2sockaddr(raw, port);
+#else
+       G.port_addr = get_peer_lsa(STDIN_FILENO);
+       set_nport(G.port_addr, htons(port));
+#endif
+       WRITE_OK(FTP_PORTOK);
+}
+
+static void
+handle_rest(void)
+{
+       /* When ftp_arg == NULL simply restart from beginning */
+       G.restart_pos = G.ftp_arg ? xatoi_u(G.ftp_arg) : 0;
+       WRITE_OK(FTP_RESTOK);
+}
+
+static void
+handle_retr(void)
+{
+       struct stat statbuf;
+       off_t bytes_transferred;
+       int remote_fd;
+       int local_file_fd;
+       off_t offset = G.restart_pos;
+       char *response;
+
+       G.restart_pos = 0;
+
+       if (!port_or_pasv_was_seen())
+               return; /* port_or_pasv_was_seen emitted error response */
+
+       /* O_NONBLOCK is useful if file happens to be a device node */
+       local_file_fd = G.ftp_arg ? open(G.ftp_arg, O_RDONLY | O_NONBLOCK) : -1;
+       if (local_file_fd < 0) {
+               WRITE_ERR(FTP_FILEFAIL);
+               return;
+       }
+
+       if (fstat(local_file_fd, &statbuf) != 0 || !S_ISREG(statbuf.st_mode)) {
+               /* Note - pretend open failed */
+               WRITE_ERR(FTP_FILEFAIL);
+               goto file_close_out;
+       }
+       G.local_file_fd = local_file_fd;
+
+       /* Now deactive O_NONBLOCK, otherwise we have a problem
+        * on DMAPI filesystems such as XFS DMAPI.
+        */
+       ndelay_off(local_file_fd);
+
+       /* Set the download offset (from REST) if any */
+       if (offset != 0)
+               xlseek(local_file_fd, offset, SEEK_SET);
+
+       response = xasprintf(
+               " Opening BINARY connection for %s (%"OFF_FMT"u bytes)",
+               G.ftp_arg, statbuf.st_size);
+       remote_fd = get_remote_transfer_fd(response);
+       free(response);
+       if (remote_fd < 0)
+               goto file_close_out;
+
+       bytes_transferred = bb_copyfd_eof(local_file_fd, remote_fd);
+       close(remote_fd);
+       if (bytes_transferred < 0)
+               WRITE_ERR(FTP_BADSENDFILE);
+       else
+               WRITE_OK(FTP_TRANSFEROK);
+
+ file_close_out:
+       close(local_file_fd);
+       G.local_file_fd = 0;
+}
+
+/* List commands */
+
+static int
+popen_ls(const char *opt)
+{
+       char *cwd;
+       const char *argv[] = {
+                       "ftpd",
+                       opt,
+                       BB_MMU ? "--" : NULL,
+                       G.ftp_arg,
+                       NULL
+       };
+       struct fd_pair outfd;
+       pid_t pid;
+
+       cwd = xrealloc_getcwd_or_warn(NULL);
+       xpiped_pair(outfd);
+
+       /*fflush(NULL); - so far we dont use stdio on output */
+       pid = BB_MMU ? fork() : vfork();
+       if (pid < 0)
+               bb_perror_msg_and_die(BB_MMU ? "fork" : "vfork");
+
+       if (pid == 0) {
+               /* child */
+#if !BB_MMU
+               if (fchdir(G.root_fd) != 0)
+                       _exit(127);
+               close(G.root_fd);
+#endif
+               /* NB: close _first_, then move fd! */
+               close(outfd.rd);
+               xmove_fd(outfd.wr, STDOUT_FILENO);
+               /* Opening /dev/null in chroot is hard.
+                * Just making sure STDIN_FILENO is opened
+                * to something harmless. Paranoia,
+                * ls won't read it anyway */
+               close(STDIN_FILENO);
+               dup(STDOUT_FILENO); /* copy will become STDIN_FILENO */
+#if !BB_MMU
+               /* ftpd ls helper chdirs to argv[2],
+                * preventing peer from seeing real root we are in now
+                */
+               argv[2] = cwd;
+               /* + 1: we must use relative path here if in chroot.
+                * For example, execv("/proc/self/exe") will fail, since
+                * it looks for "/proc/self/exe" _relative to chroot!_ */
+               execv(bb_busybox_exec_path + 1, (char**) argv);
+               _exit(127);
+#else
+               memset(&G, 0, sizeof(G));
+               exit(ls_main(ARRAY_SIZE(argv) - 1, (char**) argv));
+#endif
+       }
+
+       /* parent */
+       close(outfd.wr);
+       free(cwd);
+       return outfd.rd;
+}
+
+enum {
+       USE_CTRL_CONN = 1,
+       LONG_LISTING = 2,
+};
+
+static void
+handle_dir_common(int opts)
+{
+       FILE *ls_fp;
+       char *line;
+       int ls_fd;
+
+       if (!(opts & USE_CTRL_CONN) && !port_or_pasv_was_seen())
+               return; /* port_or_pasv_was_seen emitted error response */
+
+       /* -n prevents user/groupname display,
+        * which can be problematic in chroot */
+       ls_fd = popen_ls((opts & LONG_LISTING) ? "-l" : "-1");
+       ls_fp = fdopen(ls_fd, "r");
+       if (!ls_fp) /* never happens. paranoia */
+               bb_perror_msg_and_die("fdopen");
+
+       if (opts & USE_CTRL_CONN) {
+               /* STAT <filename> */
+               cmdio_write_raw(STR(FTP_STATFILE_OK)"-File status:\r\n");
+               while (1) {
+                       line = xmalloc_fgetline(ls_fp);
+                       if (!line)
+                               break;
+                       /* Hack: 0 results in no status at all */
+                       /* Note: it's ok that we don't prepend space,
+                        * ftp.kernel.org doesn't do that too */
+                       cmdio_write(0, line);
+                       free(line);
+               }
+               WRITE_OK(FTP_STATFILE_OK);
+       } else {
+               /* LIST/NLST [<filename>] */
+               int remote_fd = get_remote_transfer_fd(" Directory listing");
+               if (remote_fd >= 0) {
+                       while (1) {
+                               line = xmalloc_fgetline(ls_fp);
+                               if (!line)
+                                       break;
+                               /* I've seen clients complaining when they
+                                * are fed with ls output with bare '\n'.
+                                * Pity... that would be much simpler.
+                                */
+/* TODO: need to s/LF/NUL/g here */
+                               xwrite_str(remote_fd, line);
+                               xwrite(remote_fd, "\r\n", 2);
+                               free(line);
+                       }
+               }
+               close(remote_fd);
+               WRITE_OK(FTP_TRANSFEROK);
+       }
+       fclose(ls_fp); /* closes ls_fd too */
+}
+static void
+handle_list(void)
+{
+       handle_dir_common(LONG_LISTING);
+}
+static void
+handle_nlst(void)
+{
+       /* NLST returns list of names, "\r\n" terminated without regard
+        * to the current binary flag. Names may start with "/",
+        * then they represent full names (we don't produce such names),
+        * otherwise names are relative to current directory.
+        * Embedded "\n" are replaced by NULs. This is safe since names
+        * can never contain NUL.
+        */
+       handle_dir_common(0);
+}
+static void
+handle_stat_file(void)
+{
+       handle_dir_common(LONG_LISTING + USE_CTRL_CONN);
+}
+
+/* This can be extended to handle MLST, as all info is available
+ * in struct stat for that:
+ * MLST file_name
+ * 250-Listing file_name
+ *  type=file;size=4161;modify=19970214165800; /dir/dir/file_name
+ * 250 End
+ * Nano-doc:
+ * MLST [<file or dir name, "." assumed if not given>]
+ * Returned name should be either the same as requested, or fully qualified.
+ * If there was no parameter, return "" or (preferred) fully-qualified name.
+ * Returned "facts" (case is not important):
+ *  size    - size in octets
+ *  modify  - last modification time
+ *  type    - entry type (file,dir,OS.unix=block)
+ *            (+ cdir and pdir types for MLSD)
+ *  unique  - unique id of file/directory (inode#)
+ *  perm    -
+ *      a: can be appended to (APPE)
+ *      d: can be deleted (RMD/DELE)
+ *      f: can be renamed (RNFR)
+ *      r: can be read (RETR)
+ *      w: can be written (STOR)
+ *      e: can CWD into this dir
+ *      l: this dir can be listed (dir only!)
+ *      c: can create files in this dir
+ *      m: can create dirs in this dir (MKD)
+ *      p: can delete files in this dir
+ *  UNIX.mode - unix file mode
+ */
+static void
+handle_size_or_mdtm(int need_size)
+{
+       struct stat statbuf;
+       struct tm broken_out;
+       char buf[(sizeof("NNN %"OFF_FMT"u\r\n") + sizeof(off_t) * 3)
+               | sizeof("NNN YYYYMMDDhhmmss\r\n")
+       ];
+
+       if (!G.ftp_arg
+        || stat(G.ftp_arg, &statbuf) != 0
+        || !S_ISREG(statbuf.st_mode)
+       ) {
+               WRITE_ERR(FTP_FILEFAIL);
+               return;
+       }
+       if (need_size) {
+               sprintf(buf, STR(FTP_STATFILE_OK)" %"OFF_FMT"u\r\n", statbuf.st_size);
+       } else {
+               gmtime_r(&statbuf.st_mtime, &broken_out);
+               sprintf(buf, STR(FTP_STATFILE_OK)" %04u%02u%02u%02u%02u%02u\r\n",
+                       broken_out.tm_year + 1900,
+                       broken_out.tm_mon,
+                       broken_out.tm_mday,
+                       broken_out.tm_hour,
+                       broken_out.tm_min,
+                       broken_out.tm_sec);
+       }
+       cmdio_write_raw(buf);
+}
+
+/* Upload commands */
+
+#if ENABLE_FEATURE_FTP_WRITE
+static void
+handle_mkd(void)
+{
+       if (!G.ftp_arg || mkdir(G.ftp_arg, 0777) != 0) {
+               WRITE_ERR(FTP_FILEFAIL);
+               return;
+       }
+       WRITE_OK(FTP_MKDIROK);
+}
+
+static void
+handle_rmd(void)
+{
+       if (!G.ftp_arg || rmdir(G.ftp_arg) != 0) {
+               WRITE_ERR(FTP_FILEFAIL);
+               return;
+       }
+       WRITE_OK(FTP_RMDIROK);
+}
+
+static void
+handle_dele(void)
+{
+       if (!G.ftp_arg || unlink(G.ftp_arg) != 0) {
+               WRITE_ERR(FTP_FILEFAIL);
+               return;
+       }
+       WRITE_OK(FTP_DELEOK);
+}
+
+static void
+handle_rnfr(void)
+{
+       free(G.rnfr_filename);
+       G.rnfr_filename = xstrdup(G.ftp_arg);
+       WRITE_OK(FTP_RNFROK);
+}
+
+static void
+handle_rnto(void)
+{
+       int retval;
+
+       /* If we didn't get a RNFR, throw a wobbly */
+       if (G.rnfr_filename == NULL || G.ftp_arg == NULL) {
+               cmdio_write_raw(STR(FTP_NEEDRNFR)" Use RNFR first\r\n");
+               return;
+       }
+
+       retval = rename(G.rnfr_filename, G.ftp_arg);
+       free(G.rnfr_filename);
+       G.rnfr_filename = NULL;
+
+       if (retval) {
+               WRITE_ERR(FTP_FILEFAIL);
+               return;
+       }
+       WRITE_OK(FTP_RENAMEOK);
+}
+
+static void
+handle_upload_common(int is_append, int is_unique)
+{
+       struct stat statbuf;
+       char *tempname;
+       off_t bytes_transferred;
+       off_t offset;
+       int local_file_fd;
+       int remote_fd;
+
+       offset = G.restart_pos;
+       G.restart_pos = 0;
+
+       if (!port_or_pasv_was_seen())
+               return; /* port_or_pasv_was_seen emitted error response */
+
+       tempname = NULL;
+       local_file_fd = -1;
+       if (is_unique) {
+               tempname = xstrdup(" FILE: uniq.XXXXXX");
+               local_file_fd = mkstemp(tempname + 7);
+       } else if (G.ftp_arg) {
+               int flags = O_WRONLY | O_CREAT | O_TRUNC;
+               if (is_append)
+                       flags = O_WRONLY | O_CREAT | O_APPEND;
+               if (offset)
+                       flags = O_WRONLY | O_CREAT;
+               local_file_fd = open(G.ftp_arg, flags, 0666);
+       }
+
+       if (local_file_fd < 0
+        || fstat(local_file_fd, &statbuf) != 0
+        || !S_ISREG(statbuf.st_mode)
+       ) {
+               WRITE_ERR(FTP_UPLOADFAIL);
+               if (local_file_fd >= 0)
+                       goto close_local_and_bail;
+               return;
+       }
+       G.local_file_fd = local_file_fd;
+
+       if (offset)
+               xlseek(local_file_fd, offset, SEEK_SET);
+
+       remote_fd = get_remote_transfer_fd(tempname ? tempname : " Ok to send data");
+       free(tempname);
+
+       if (remote_fd < 0)
+               goto close_local_and_bail;
+
+       bytes_transferred = bb_copyfd_eof(remote_fd, local_file_fd);
+       close(remote_fd);
+       if (bytes_transferred < 0)
+               WRITE_ERR(FTP_BADSENDFILE);
+       else
+               WRITE_OK(FTP_TRANSFEROK);
+
+ close_local_and_bail:
+       close(local_file_fd);
+       G.local_file_fd = 0;
+}
+
+static void
+handle_stor(void)
+{
+       handle_upload_common(0, 0);
+}
+
+static void
+handle_appe(void)
+{
+       G.restart_pos = 0;
+       handle_upload_common(1, 0);
+}
+
+static void
+handle_stou(void)
+{
+       G.restart_pos = 0;
+       handle_upload_common(0, 1);
+}
+#endif /* ENABLE_FEATURE_FTP_WRITE */
+
+static uint32_t
+cmdio_get_cmd_and_arg(void)
+{
+       size_t len;
+       uint32_t cmdval;
+       char *cmd;
+
+       alarm(G.timeout);
+
+       free(G.ftp_cmd);
+       len = 8 * 1024; /* Paranoia. Peer may send 1 gigabyte long cmd... */
+       G.ftp_cmd = cmd = xmalloc_fgets_str_len(stdin, "\r\n", &len);
+       if (!cmd)
+               exit(0);
+
+       /* De-escape telnet: 0xff,0xff => 0xff */
+       /* RFC959 says that ABOR, STAT, QUIT may be sent even during
+        * data transfer, and may be preceded by telnet's "Interrupt Process"
+        * code (two-byte sequence 255,244) and then by telnet "Synch" code
+        * 255,242 (byte 242 is sent with TCP URG bit using send(MSG_OOB)
+        * and may generate SIGURG on our side. See RFC854).
+        * So far we don't support that (may install SIGURG handler if we'd want to),
+        * but we need to at least remove 255,xxx pairs. lftp sends those. */
+       /* Then de-escape FTP: NUL => '\n' */
+       /* Testing for \xff:
+        * Create file named '\xff': echo Hello >`echo -ne "\xff"`
+        * Try to get it:            ftpget -v 127.0.0.1 Eff `echo -ne "\xff\xff"`
+        * (need "\xff\xff" until ftpget applet is fixed to do escaping :)
+        * Testing for embedded LF:
+        * LF_HERE=`echo -ne "LF\nHERE"`
+        * echo Hello >"$LF_HERE"
+        * ftpget -v 127.0.0.1 LF_HERE "$LF_HERE"
+        */
+       {
+               int dst, src;
+
+               /* Strip "\r\n" if it is there */
+               if (len != 0 && cmd[len - 1] == '\n') {
+                       len--;
+                       if (len != 0 && cmd[len - 1] == '\r')
+                               len--;
+                       cmd[len] = '\0';
+               }
+               src = strchrnul(cmd, 0xff) - cmd;
+               /* 99,99% there are neither NULs nor 255s and src == len */
+               if (src < len) {
+                       dst = src;
+                       do {
+                               if ((unsigned char)(cmd[src]) == 255) {
+                                       src++;
+                                       /* 255,xxx - skip 255 */
+                                       if ((unsigned char)(cmd[src]) != 255) {
+                                               /* 255,!255 - skip both */
+                                               src++;
+                                               continue;
+                                       }
+                                       /* 255,255 - retain one 255 */
+                               }
+                               /* NUL => '\n' */
+                               cmd[dst++] = cmd[src] ? cmd[src] : '\n';
+                               src++;
+                       } while (src < len);
+                       cmd[dst] = '\0';
+               }
+       }
+
+       if (G.verbose > 1)
+               verbose_log(cmd);
+
+       G.ftp_arg = strchr(cmd, ' ');
+       if (G.ftp_arg != NULL)
+               *G.ftp_arg++ = '\0';
+
+       /* Uppercase and pack into uint32_t first word of the command */
+       cmdval = 0;
+       while (*cmd)
+               cmdval = (cmdval << 8) + ((unsigned char)*cmd++ & (unsigned char)~0x20);
+
+       return cmdval;
+}
+
+#define mk_const4(a,b,c,d) (((a * 0x100 + b) * 0x100 + c) * 0x100 + d)
+#define mk_const3(a,b,c)    ((a * 0x100 + b) * 0x100 + c)
+enum {
+       const_ALLO = mk_const4('A', 'L', 'L', 'O'),
+       const_APPE = mk_const4('A', 'P', 'P', 'E'),
+       const_CDUP = mk_const4('C', 'D', 'U', 'P'),
+       const_CWD  = mk_const3('C', 'W', 'D'),
+       const_DELE = mk_const4('D', 'E', 'L', 'E'),
+       const_EPSV = mk_const4('E', 'P', 'S', 'V'),
+       const_FEAT = mk_const4('F', 'E', 'A', 'T'),
+       const_HELP = mk_const4('H', 'E', 'L', 'P'),
+       const_LIST = mk_const4('L', 'I', 'S', 'T'),
+       const_MDTM = mk_const4('M', 'D', 'T', 'M'),
+       const_MKD  = mk_const3('M', 'K', 'D'),
+       const_MODE = mk_const4('M', 'O', 'D', 'E'),
+       const_NLST = mk_const4('N', 'L', 'S', 'T'),
+       const_NOOP = mk_const4('N', 'O', 'O', 'P'),
+       const_PASS = mk_const4('P', 'A', 'S', 'S'),
+       const_PASV = mk_const4('P', 'A', 'S', 'V'),
+       const_PORT = mk_const4('P', 'O', 'R', 'T'),
+       const_PWD  = mk_const3('P', 'W', 'D'),
+       const_QUIT = mk_const4('Q', 'U', 'I', 'T'),
+       const_REST = mk_const4('R', 'E', 'S', 'T'),
+       const_RETR = mk_const4('R', 'E', 'T', 'R'),
+       const_RMD  = mk_const3('R', 'M', 'D'),
+       const_RNFR = mk_const4('R', 'N', 'F', 'R'),
+       const_RNTO = mk_const4('R', 'N', 'T', 'O'),
+       const_SIZE = mk_const4('S', 'I', 'Z', 'E'),
+       const_STAT = mk_const4('S', 'T', 'A', 'T'),
+       const_STOR = mk_const4('S', 'T', 'O', 'R'),
+       const_STOU = mk_const4('S', 'T', 'O', 'U'),
+       const_STRU = mk_const4('S', 'T', 'R', 'U'),
+       const_SYST = mk_const4('S', 'Y', 'S', 'T'),
+       const_TYPE = mk_const4('T', 'Y', 'P', 'E'),
+       const_USER = mk_const4('U', 'S', 'E', 'R'),
+
+#if !BB_MMU
+       OPT_l = (1 << 0),
+       OPT_1 = (1 << 1),
+#endif
+       OPT_v = (1 << ((!BB_MMU) * 2 + 0)),
+       OPT_S = (1 << ((!BB_MMU) * 2 + 1)),
+       OPT_w = (1 << ((!BB_MMU) * 2 + 2)) * ENABLE_FEATURE_FTP_WRITE,
+};
+
+int ftpd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+#if !BB_MMU
+int ftpd_main(int argc, char **argv)
+#else
+int ftpd_main(int argc UNUSED_PARAM, char **argv)
+#endif
+{
+       unsigned abs_timeout;
+       smallint opts;
+
+       INIT_G();
+
+       abs_timeout = 1 * 60 * 60;
+       G.timeout = 2 * 60;
+       opt_complementary = "t+:T+:vv";
+#if BB_MMU
+       opts = getopt32(argv,   "vS" USE_FEATURE_FTP_WRITE("w") "t:T:", &G.timeout, &abs_timeout, &G.verbose);
+#else
+       opts = getopt32(argv, "l1vS" USE_FEATURE_FTP_WRITE("w") "t:T:", &G.timeout, &abs_timeout, &G.verbose);
+       if (opts & (OPT_l|OPT_1)) {
+               /* Our secret backdoor to ls */
+/* TODO: pass -n too? */
+/* --group-directories-first would be nice, but ls don't do that yet */
+               xchdir(argv[2]);
+               argv[2] = (char*)"--";
+               memset(&G, 0, sizeof(G));
+               return ls_main(argc, argv);
+       }
+#endif
+       if (abs_timeout | G.timeout) {
+               if (abs_timeout == 0)
+                       abs_timeout = INT_MAX;
+               G.end_time = monotonic_sec() + abs_timeout;
+               if (G.timeout > abs_timeout)
+                       G.timeout = abs_timeout;
+       }
+       strcpy(G.msg_ok  + 4, MSG_OK );
+       strcpy(G.msg_err + 4, MSG_ERR);
+
+       G.local_addr = get_sock_lsa(STDIN_FILENO);
+       if (!G.local_addr) {
+               /* This is confusing:
+                * bb_error_msg_and_die("stdin is not a socket");
+                * Better: */
+               bb_show_usage();
+               /* Help text says that ftpd must be used as inetd service,
+                * which is by far the most usual cause of get_sock_lsa
+                * failure */
+       }
+
+       if (!(opts & OPT_v))
+               logmode = LOGMODE_NONE;
+       if (opts & OPT_S) {
+               /* LOG_NDELAY is needed since we may chroot later */
+               openlog(applet_name, LOG_PID | LOG_NDELAY, LOG_DAEMON);
+               logmode |= LOGMODE_SYSLOG;
+       }
+       if (logmode)
+               applet_name = xasprintf("%s[%u]", applet_name, (int)getpid());
+
+#if !BB_MMU
+       G.root_fd = xopen("/", O_RDONLY | O_DIRECTORY);
+#endif
+
+       if (argv[optind]) {
+               xchdir(argv[optind]);
+               chroot(".");
+       }
+
+       //umask(077); - admin can set umask before starting us
+
+       /* Signals. We'll always take -EPIPE rather than a rude signal, thanks */
+       signal(SIGPIPE, SIG_IGN);
+
+       /* Set up options on the command socket (do we need these all? why?) */
+       setsockopt(STDIN_FILENO, IPPROTO_TCP, TCP_NODELAY, &const_int_1, sizeof(const_int_1));
+       setsockopt(STDIN_FILENO, SOL_SOCKET, SO_KEEPALIVE, &const_int_1, sizeof(const_int_1));
+       /* Telnet protocol over command link may send "urgent" data,
+        * we prefer it to be received in the "normal" data stream: */
+       setsockopt(STDIN_FILENO, SOL_SOCKET, SO_OOBINLINE, &const_int_1, sizeof(const_int_1));
+
+       WRITE_OK(FTP_GREET);
+       signal(SIGALRM, timeout_handler);
+
+#ifdef IF_WE_WANT_TO_REQUIRE_LOGIN
+       {
+               smallint user_was_specified = 0;
+               while (1) {
+                       uint32_t cmdval = cmdio_get_cmd_and_arg();
+
+                       if (cmdval == const_USER) {
+                               if (G.ftp_arg == NULL || strcasecmp(G.ftp_arg, "anonymous") != 0)
+                                       cmdio_write_raw(STR(FTP_LOGINERR)" Server is anonymous only\r\n");
+                               else {
+                                       user_was_specified = 1;
+                                       cmdio_write_raw(STR(FTP_GIVEPWORD)" Please specify the password\r\n");
+                               }
+                       } else if (cmdval == const_PASS) {
+                               if (user_was_specified)
+                                       break;
+                               cmdio_write_raw(STR(FTP_NEEDUSER)" Login with USER\r\n");
+                       } else if (cmdval == const_QUIT) {
+                               WRITE_OK(FTP_GOODBYE);
+                               return 0;
+                       } else {
+                               cmdio_write_raw(STR(FTP_LOGINERR)" Login with USER and PASS\r\n");
+                       }
+               }
+       }
+       WRITE_OK(FTP_LOGINOK);
+#endif
+
+       /* RFC-959 Section 5.1
+        * The following commands and options MUST be supported by every
+        * server-FTP and user-FTP, except in cases where the underlying
+        * file system or operating system does not allow or support
+        * a particular command.
+        * Type: ASCII Non-print, IMAGE, LOCAL 8
+        * Mode: Stream
+        * Structure: File, Record*
+        * (Record structure is REQUIRED only for hosts whose file
+        *  systems support record structure).
+        * Commands:
+        * USER, PASS, ACCT, [bbox: ACCT not supported]
+        * PORT, PASV,
+        * TYPE, MODE, STRU,
+        * RETR, STOR, APPE,
+        * RNFR, RNTO, DELE,
+        * CWD,  CDUP, RMD,  MKD,  PWD,
+        * LIST, NLST,
+        * SYST, STAT,
+        * HELP, NOOP, QUIT.
+        */
+       /* ACCOUNT (ACCT)
+        * "The argument field is a Telnet string identifying the user's account.
+        * The command is not necessarily related to the USER command, as some
+        * sites may require an account for login and others only for specific
+        * access, such as storing files. In the latter case the command may
+        * arrive at any time.
+        * There are reply codes to differentiate these cases for the automation:
+        * when account information is required for login, the response to
+        * a successful PASSword command is reply code 332. On the other hand,
+        * if account information is NOT required for login, the reply to
+        * a successful PASSword command is 230; and if the account information
+        * is needed for a command issued later in the dialogue, the server
+        * should return a 332 or 532 reply depending on whether it stores
+        * (pending receipt of the ACCounT command) or discards the command,
+        * respectively."
+        */
+
+       while (1) {
+               uint32_t cmdval = cmdio_get_cmd_and_arg();
+
+               if (cmdval == const_QUIT) {
+                       WRITE_OK(FTP_GOODBYE);
+                       return 0;
+               }
+               else if (cmdval == const_USER)
+                       /* This would mean "ok, now give me PASS". */
+                       /*WRITE_OK(FTP_GIVEPWORD);*/
+                       /* vsftpd can be configured to not require that,
+                        * and this also saves one roundtrip:
+                        */
+                       WRITE_OK(FTP_LOGINOK);
+               else if (cmdval == const_PASS)
+                       WRITE_OK(FTP_LOGINOK);
+               else if (cmdval == const_NOOP)
+                       WRITE_OK(FTP_NOOPOK);
+               else if (cmdval == const_TYPE)
+                       WRITE_OK(FTP_TYPEOK);
+               else if (cmdval == const_STRU)
+                       WRITE_OK(FTP_STRUOK);
+               else if (cmdval == const_MODE)
+                       WRITE_OK(FTP_MODEOK);
+               else if (cmdval == const_ALLO)
+                       WRITE_OK(FTP_ALLOOK);
+               else if (cmdval == const_SYST)
+                       cmdio_write_raw(STR(FTP_SYSTOK)" UNIX Type: L8\r\n");
+               else if (cmdval == const_PWD)
+                       handle_pwd();
+               else if (cmdval == const_CWD)
+                       handle_cwd();
+               else if (cmdval == const_CDUP) /* cd .. */
+                       handle_cdup();
+               /* HELP is nearly useless, but we can reuse FEAT for it */
+               /* lftp uses FEAT */
+               else if (cmdval == const_HELP || cmdval == const_FEAT)
+                       handle_feat(cmdval == const_HELP
+                                       ? STRNUM32(FTP_HELP)
+                                       : STRNUM32(FTP_STATOK)
+                       );
+               else if (cmdval == const_LIST) /* ls -l */
+                       handle_list();
+               else if (cmdval == const_NLST) /* "name list", bare ls */
+                       handle_nlst();
+               /* SIZE is crucial for wget's download indicator etc */
+               /* Mozilla, lftp use MDTM (presumably for caching) */
+               else if (cmdval == const_SIZE || cmdval == const_MDTM)
+                       handle_size_or_mdtm(cmdval == const_SIZE);
+               else if (cmdval == const_STAT) {
+                       if (G.ftp_arg == NULL)
+                               handle_stat();
+                       else
+                               handle_stat_file();
+               }
+               else if (cmdval == const_PASV)
+                       handle_pasv();
+               else if (cmdval == const_EPSV)
+                       handle_epsv();
+               else if (cmdval == const_RETR)
+                       handle_retr();
+               else if (cmdval == const_PORT)
+                       handle_port();
+               else if (cmdval == const_REST)
+                       handle_rest();
+#if ENABLE_FEATURE_FTP_WRITE
+               else if (opts & OPT_w) {
+                       if (cmdval == const_STOR)
+                               handle_stor();
+                       else if (cmdval == const_MKD)
+                               handle_mkd();
+                       else if (cmdval == const_RMD)
+                               handle_rmd();
+                       else if (cmdval == const_DELE)
+                               handle_dele();
+                       else if (cmdval == const_RNFR) /* "rename from" */
+                               handle_rnfr();
+                       else if (cmdval == const_RNTO) /* "rename to" */
+                               handle_rnto();
+                       else if (cmdval == const_APPE)
+                               handle_appe();
+                       else if (cmdval == const_STOU) /* "store unique" */
+                               handle_stou();
+                       else
+                               goto bad_cmd;
+               }
+#endif
+#if 0
+               else if (cmdval == const_STOR
+                || cmdval == const_MKD
+                || cmdval == const_RMD
+                || cmdval == const_DELE
+                || cmdval == const_RNFR
+                || cmdval == const_RNTO
+                || cmdval == const_APPE
+                || cmdval == const_STOU
+               ) {
+                       cmdio_write_raw(STR(FTP_NOPERM)" Permission denied\r\n");
+               }
+#endif
+               else {
+                       /* Which unsupported commands were seen in the wild?
+                        * (doesn't necessarily mean "we must support them")
+                        * foo 1.2.3: XXXX - comment
+                        */
+#if ENABLE_FEATURE_FTP_WRITE
+ bad_cmd:
+#endif
+                       cmdio_write_raw(STR(FTP_BADCMD)" Unknown command\r\n");
+               }
+       }
+}
diff --git a/networking/ftpgetput.c b/networking/ftpgetput.c
new file mode 100644 (file)
index 0000000..d39b73e
--- /dev/null
@@ -0,0 +1,325 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * ftpget
+ *
+ * Mini implementation of FTP to retrieve a remote file.
+ *
+ * Copyright (C) 2002 Jeff Angielski, The PTR Group <jeff@theptrgroup.com>
+ * Copyright (C) 2002 Glenn McGrath
+ *
+ * Based on wget.c by Chip Rosenthal Covad Communications
+ * <chip@laserlink.net>
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.h"
+
+struct globals {
+       const char *user;
+       const char *password;
+       struct len_and_sockaddr *lsa;
+       FILE *control_stream;
+       int verbose_flag;
+       int do_continue;
+       char buf[1]; /* actually [BUFSZ] */
+};
+#define G (*(struct globals*)&bb_common_bufsiz1)
+enum { BUFSZ = COMMON_BUFSIZE - offsetof(struct globals, buf) };
+struct BUG_G_too_big {
+       char BUG_G_too_big[sizeof(G) <= COMMON_BUFSIZE ? 1 : -1];
+};
+#define user           (G.user          )
+#define password       (G.password      )
+#define lsa            (G.lsa           )
+#define control_stream (G.control_stream)
+#define verbose_flag   (G.verbose_flag  )
+#define do_continue    (G.do_continue   )
+#define buf            (G.buf           )
+#define INIT_G() do { } while (0)
+
+
+static void ftp_die(const char *msg) NORETURN;
+static void ftp_die(const char *msg)
+{
+       char *cp = buf; /* buf holds peer's response */
+
+       /* Guard against garbage from remote server */
+       while (*cp >= ' ' && *cp < '\x7f')
+               cp++;
+       *cp = '\0';
+       bb_error_msg_and_die("unexpected server response%s%s: %s",
+                       (msg ? " to " : ""), (msg ? msg : ""), buf);
+}
+
+static int ftpcmd(const char *s1, const char *s2)
+{
+       unsigned n;
+
+       if (verbose_flag) {
+               bb_error_msg("cmd %s %s", s1, s2);
+       }
+
+       if (s1) {
+               fprintf(control_stream, (s2 ? "%s %s\r\n" : "%s %s\r\n"+3),
+                                               s1, s2);
+               fflush(control_stream);
+       }
+
+       do {
+               strcpy(buf, "EOF");
+               if (fgets(buf, BUFSZ - 2, control_stream) == NULL) {
+                       ftp_die(NULL);
+               }
+       } while (!isdigit(buf[0]) || buf[3] != ' ');
+
+       buf[3] = '\0';
+       n = xatou(buf);
+       buf[3] = ' ';
+       return n;
+}
+
+static void ftp_login(void)
+{
+       /* Connect to the command socket */
+       control_stream = fdopen(xconnect_stream(lsa), "r+");
+       if (control_stream == NULL) {
+               /* fdopen failed - extremely unlikely */
+               bb_perror_nomsg_and_die();
+       }
+
+       if (ftpcmd(NULL, NULL) != 220) {
+               ftp_die(NULL);
+       }
+
+       /*  Login to the server */
+       switch (ftpcmd("USER", user)) {
+       case 230:
+               break;
+       case 331:
+               if (ftpcmd("PASS", password) != 230) {
+                       ftp_die("PASS");
+               }
+               break;
+       default:
+               ftp_die("USER");
+       }
+
+       ftpcmd("TYPE I", NULL);
+}
+
+static int xconnect_ftpdata(void)
+{
+       char *buf_ptr;
+       unsigned port_num;
+
+/*
+TODO: PASV command will not work for IPv6. RFC2428 describes
+IPv6-capable "extended PASV" - EPSV.
+
+"EPSV [protocol]" asks server to bind to and listen on a data port
+in specified protocol. Protocol is 1 for IPv4, 2 for IPv6.
+If not specified, defaults to "same as used for control connection".
+If server understood you, it should answer "229 <some text>(|||port|)"
+where "|" are literal pipe chars and "port" is ASCII decimal port#.
+
+There is also an IPv6-capable replacement for PORT (EPRT),
+but we don't need that.
+
+NB: PASV may still work for some servers even over IPv6.
+For example, vsftp happily answers
+"227 Entering Passive Mode (0,0,0,0,n,n)" and proceeds as usual.
+
+TODO2: need to stop ignoring IP address in PASV response.
+*/
+
+       if (ftpcmd("PASV", NULL) != 227) {
+               ftp_die("PASV");
+       }
+
+       /* Response is "NNN 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 */
+       buf_ptr = strrchr(buf, ')');
+       if (buf_ptr) *buf_ptr = '\0';
+
+       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;
+
+       set_nport(lsa, htons(port_num));
+       return xconnect_stream(lsa);
+}
+
+static int pump_data_and_QUIT(int from, int to)
+{
+       /* copy the file */
+       if (bb_copyfd_eof(from, to) == -1) {
+               /* error msg is already printed by bb_copyfd_eof */
+               return EXIT_FAILURE;
+       }
+
+       /* close data connection */
+       close(from); /* don't know which one is that, so we close both */
+       close(to);
+
+       /* does server confirm that transfer is finished? */
+       if (ftpcmd(NULL, NULL) != 226) {
+               ftp_die(NULL);
+       }
+       ftpcmd("QUIT", NULL);
+
+       return EXIT_SUCCESS;
+}
+
+#if !ENABLE_FTPGET
+int ftp_receive(const char *local_path, char *server_path);
+#else
+static
+int ftp_receive(const char *local_path, char *server_path)
+{
+       int fd_data;
+       int fd_local = -1;
+       off_t beg_range = 0;
+
+       /* connect to the data socket */
+       fd_data = xconnect_ftpdata();
+
+       if (ftpcmd("SIZE", server_path) != 213) {
+               do_continue = 0;
+       }
+
+       if (LONE_DASH(local_path)) {
+               fd_local = STDOUT_FILENO;
+               do_continue = 0;
+       }
+
+       if (do_continue) {
+               struct stat sbuf;
+               /* lstat would be wrong here! */
+               if (stat(local_path, &sbuf) < 0) {
+                       bb_perror_msg_and_die("stat");
+               }
+               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) != 350) {
+                       do_continue = 0;
+               }
+       }
+
+       if (ftpcmd("RETR", server_path) > 150) {
+               ftp_die("RETR");
+       }
+
+       /* create local file _after_ we know that remote file exists */
+       if (fd_local == -1) {
+               fd_local = xopen(local_path,
+                       do_continue ? (O_APPEND | O_WRONLY)
+                                   : (O_CREAT | O_TRUNC | O_WRONLY)
+               );
+       }
+
+       return pump_data_and_QUIT(fd_data, fd_local);
+}
+#endif
+
+#if !ENABLE_FTPPUT
+int ftp_send(const char *server_path, char *local_path);
+#else
+static
+int ftp_send(const char *server_path, char *local_path)
+{
+       int fd_data;
+       int fd_local;
+       int response;
+
+       /* connect to the data socket */
+       fd_data = xconnect_ftpdata();
+
+       /* get the local file */
+       fd_local = STDIN_FILENO;
+       if (NOT_LONE_DASH(local_path))
+               fd_local = xopen(local_path, O_RDONLY);
+
+       response = ftpcmd("STOR", server_path);
+       switch (response) {
+       case 125:
+       case 150:
+               break;
+       default:
+               ftp_die("STOR");
+       }
+
+       return pump_data_and_QUIT(fd_local, fd_data);
+}
+#endif
+
+#if ENABLE_FEATURE_FTPGETPUT_LONG_OPTIONS
+static const char ftpgetput_longopts[] ALIGN1 =
+       "continue\0" Required_argument "c"
+       "verbose\0"  No_argument       "v"
+       "username\0" Required_argument "u"
+       "password\0" Required_argument "p"
+       "port\0"     Required_argument "P"
+       ;
+#endif
+
+int ftpgetput_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int ftpgetput_main(int argc UNUSED_PARAM, char **argv)
+{
+       unsigned opt;
+       const char *port = "ftp";
+       /* socket to ftp server */
+
+#if ENABLE_FTPPUT && !ENABLE_FTPGET
+# define ftp_action ftp_send
+#elif ENABLE_FTPGET && !ENABLE_FTPPUT
+# define ftp_action ftp_receive
+#else
+       int (*ftp_action)(const char *, char *) = ftp_send;
+
+       /* Check to see if the command is ftpget or ftput */
+       if (applet_name[3] == 'g') {
+               ftp_action = ftp_receive;
+       }
+#endif
+
+       INIT_G();
+       /* Set default values */
+       user = "anonymous";
+       password = "busybox@";
+
+       /*
+        * Decipher the command line
+        */
+#if ENABLE_FEATURE_FTPGETPUT_LONG_OPTIONS
+       applet_long_options = ftpgetput_longopts;
+#endif
+       opt_complementary = "=3:vv:cc"; /* must have 3 params; -v and -c count */
+       opt = getopt32(argv, "cvu:p:P:", &user, &password, &port,
+                                       &verbose_flag, &do_continue);
+       argv += optind;
+
+       /* 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... */
+       lsa = xhost2sockaddr(argv[0], bb_lookup_port(port, "tcp", 21));
+       if (verbose_flag) {
+               printf("Connecting to %s (%s)\n", argv[0],
+                       xmalloc_sockaddr2dotted(&lsa->u.sa));
+       }
+
+       ftp_login();
+       return ftp_action(argv[1], argv[2]);
+}
diff --git a/networking/hostname.c b/networking/hostname.c
new file mode 100644 (file)
index 0000000..48e70db
--- /dev/null
@@ -0,0 +1,93 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Mini hostname implementation for busybox
+ *
+ * Copyright (C) 1999 by Randolph Chung <tausq@debian.org>
+ *
+ * adjusted by Erik Andersen <andersen@codepoet.org> 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 "libbb.h"
+
+static void do_sethostname(char *s, int isfile)
+{
+       if (!s)
+               return;
+       if (isfile) {
+               parser_t *parser = config_open2(s, xfopen_for_read);
+               while (config_read(parser, &s, 1, 1, "# \t", PARSE_NORMAL & ~PARSE_GREEDY)) {
+                       do_sethostname(s, 0);
+               }
+               if (ENABLE_FEATURE_CLEAN_UP)
+                       config_close(parser);
+       } else if (sethostname(s, strlen(s)) < 0) {
+               if (errno == EPERM)
+                       bb_error_msg_and_die(bb_msg_perm_denied_are_you_root);
+               bb_perror_msg_and_die("sethostname");
+       }
+}
+
+int hostname_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int hostname_main(int argc, char **argv)
+{
+       enum {
+               OPT_d = 0x1,
+               OPT_f = 0x2,
+               OPT_i = 0x4,
+               OPT_s = 0x8,
+               OPT_F = 0x10,
+               OPT_dfis = 0xf,
+       };
+
+       char *buf;
+       char *hostname_str;
+
+       if (argc < 1)
+               bb_show_usage();
+
+       getopt32(argv, "dfisF:", &hostname_str);
+       argv += optind;
+       buf = safe_gethostname();
+
+       /* Output in desired format */
+       if (option_mask32 & OPT_dfis) {
+               struct hostent *hp;
+               char *p;
+               hp = xgethostbyname(buf);
+               p = strchr(hp->h_name, '.');
+               if (option_mask32 & OPT_f) {
+                       puts(hp->h_name);
+               } else if (option_mask32 & OPT_s) {
+                       if (p)
+                               *p = '\0';
+                       puts(hp->h_name);
+               } else if (option_mask32 & OPT_d) {
+                       if (p)
+                               puts(p + 1);
+               } else if (option_mask32 & OPT_i) {
+                       while (hp->h_addr_list[0]) {
+                               printf("%s ", inet_ntoa(*(struct in_addr *) (*hp->h_addr_list++)));
+                       }
+                       bb_putchar('\n');
+               }
+       }
+       /* Set the hostname */
+       else if (option_mask32 & OPT_F) {
+               do_sethostname(hostname_str, 1);
+       } else if (argv[0]) {
+               do_sethostname(argv[0], 0);
+       }
+       /* Or if all else fails,
+        * just print the current hostname */
+       else {
+               puts(buf);
+       }
+       if (ENABLE_FEATURE_CLEAN_UP)
+               free(buf);
+       return EXIT_SUCCESS;
+}
diff --git a/networking/httpd.c b/networking/httpd.c
new file mode 100644 (file)
index 0000000..de4fb9b
--- /dev/null
@@ -0,0 +1,2405 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * httpd implementation for busybox
+ *
+ * Copyright (C) 2002,2003 Glenn Engel <glenne@engel.org>
+ * Copyright (C) 2003-2006 Vladimir Oleynik <dzo@simtreas.ru>
+ *
+ * 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 starts by "/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 applet 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 "<Hello World>"`  # encode as "&#60Hello&#32World&#62"
+ * Note that url encoding for arguments is not the same as html encoding for
+ * presentation.  -d decodes an url-encoded argument while -e encodes in html
+ * for page display.
+ *
+ * httpd.conf has the following format:
+ *
+ * H:/serverroot     # define the server root. It will override -h
+ * 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
+ * E404:/path/e404.html # /path/e404.html is the 404 (not found) error page
+ * I:index.html      # Show index.html when a directory is requested
+ *
+ * P:/url:[http://]hostname[:port]/new/path
+ *                   # When /urlXXXXXX is requested, reverse proxy
+ *                   # it to http://hostname[:port]/new/pathXXXXXX
+ *
+ * /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   # run xxx.php through an interpreter
+ *
+ * A/D may be as a/d or allow/deny - only first char matters.
+ * Deny/Allow IP logic:
+ *  - Default is to allow all (Allow all (A:*) is a no-op).
+ *  - Deny rules take precedence over allow rules.
+ *  - "Deny all" rule (D:*) is applied last.
+ *
+ * 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.
+ *
+ * Custom error pages can contain an absolute path or be relative to
+ * 'home_httpd'. Error pages are to be static files (no CGI or script). Error
+ * page can only be defined in the root configuration file and are not taken
+ * into account in local (directories) config files.
+ *
+ * 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.
+ *
+ */
+ /* TODO: use TCP_CORK, parse_config() */
+
+#include "libbb.h"
+#if ENABLE_FEATURE_HTTPD_USE_SENDFILE
+# include <sys/sendfile.h>
+#endif
+
+#define DEBUG 0
+
+#define IOBUF_SIZE 8192    /* IO buffer */
+
+/* amount of buffering in a pipe */
+#ifndef PIPE_BUF
+# define PIPE_BUF 4096
+#endif
+#if PIPE_BUF >= IOBUF_SIZE
+# error "PIPE_BUF >= IOBUF_SIZE"
+#endif
+
+#define HEADER_READ_TIMEOUT 60
+
+static const char DEFAULT_PATH_HTTPD_CONF[] ALIGN1 = "/etc";
+static const char HTTPD_CONF[] ALIGN1 = "httpd.conf";
+static const char HTTP_200[] ALIGN1 = "HTTP/1.0 200 OK\r\n";
+
+typedef struct has_next_ptr {
+       struct has_next_ptr *next;
+} has_next_ptr;
+
+/* Must have "next" as a first member */
+typedef struct Htaccess {
+       struct Htaccess *next;
+       char *after_colon;
+       char before_colon[1];  /* really bigger, must be last */
+} Htaccess;
+
+/* Must have "next" as a first member */
+typedef struct Htaccess_IP {
+       struct Htaccess_IP *next;
+       unsigned ip;
+       unsigned mask;
+       int allow_deny;
+} Htaccess_IP;
+
+/* Must have "next" as a first member */
+typedef struct Htaccess_Proxy {
+       struct Htaccess_Proxy *next;
+       char *url_from;
+       char *host_port;
+       char *url_to;
+} Htaccess_Proxy;
+
+enum {
+       HTTP_OK = 200,
+       HTTP_PARTIAL_CONTENT = 206,
+       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,
+       HTTP_CONTINUE = 100,
+#if 0   /* future use */
+       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
+};
+
+static const uint16_t http_response_type[] ALIGN2 = {
+       HTTP_OK,
+#if ENABLE_FEATURE_HTTPD_RANGES
+       HTTP_PARTIAL_CONTENT,
+#endif
+       HTTP_MOVED_TEMPORARILY,
+       HTTP_REQUEST_TIMEOUT,
+       HTTP_NOT_IMPLEMENTED,
+#if ENABLE_FEATURE_HTTPD_BASIC_AUTH
+       HTTP_UNAUTHORIZED,
+#endif
+       HTTP_NOT_FOUND,
+       HTTP_BAD_REQUEST,
+       HTTP_FORBIDDEN,
+       HTTP_INTERNAL_SERVER_ERROR,
+#if 0   /* not implemented */
+       HTTP_CREATED,
+       HTTP_ACCEPTED,
+       HTTP_NO_CONTENT,
+       HTTP_MULTIPLE_CHOICES,
+       HTTP_MOVED_PERMANENTLY,
+       HTTP_NOT_MODIFIED,
+       HTTP_BAD_GATEWAY,
+       HTTP_SERVICE_UNAVAILABLE,
+#endif
+};
+
+static const struct {
+       const char *name;
+       const char *info;
+} http_response[ARRAY_SIZE(http_response_type)] = {
+       { "OK", NULL },
+#if ENABLE_FEATURE_HTTPD_RANGES
+       { "Partial Content", NULL },
+#endif
+       { "Found", NULL },
+       { "Request Timeout", "No request appeared within 60 seconds" },
+       { "Not Implemented", "The requested method is not recognized" },
+#if ENABLE_FEATURE_HTTPD_BASIC_AUTH
+       { "Unauthorized", "" },
+#endif
+       { "Not Found", "The requested URL was not found" },
+       { "Bad Request", "Unsupported method" },
+       { "Forbidden", ""  },
+       { "Internal Server Error", "Internal Server Error" },
+#if 0   /* not implemented */
+       { "Created" },
+       { "Accepted" },
+       { "No Content" },
+       { "Multiple Choices" },
+       { "Moved Permanently" },
+       { "Not Modified" },
+       { "Bad Gateway", "" },
+       { "Service Unavailable", "" },
+#endif
+};
+
+
+struct globals {
+       int verbose;            /* must be int (used by getopt32) */
+       smallint flg_deny_all;
+
+       unsigned rmt_ip;        /* used for IP-based allow/deny rules */
+       time_t last_mod;
+       char *rmt_ip_str;       /* for $REMOTE_ADDR and $REMOTE_PORT */
+       const char *bind_addr_or_port;
+
+       const char *g_query;
+       const char *opt_c_configFile;
+       const char *home_httpd;
+       const char *index_page;
+
+       const char *found_mime_type;
+       const char *found_moved_temporarily;
+       Htaccess_IP *ip_a_d;    /* config allow/deny lines */
+
+       USE_FEATURE_HTTPD_BASIC_AUTH(const char *g_realm;)
+       USE_FEATURE_HTTPD_BASIC_AUTH(char *remoteuser;)
+       USE_FEATURE_HTTPD_CGI(char *referer;)
+       USE_FEATURE_HTTPD_CGI(char *user_agent;)
+       USE_FEATURE_HTTPD_CGI(char *host;)
+       USE_FEATURE_HTTPD_CGI(char *http_accept;)
+       USE_FEATURE_HTTPD_CGI(char *http_accept_language;)
+
+       off_t file_size;        /* -1 - unknown */
+#if ENABLE_FEATURE_HTTPD_RANGES
+       off_t range_start;
+       off_t range_end;
+       off_t range_len;
+#endif
+
+#if ENABLE_FEATURE_HTTPD_BASIC_AUTH
+       Htaccess *g_auth;       /* config user:password lines */
+#endif
+       Htaccess *mime_a;       /* config mime types */
+#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR
+       Htaccess *script_i;     /* config script interpreters */
+#endif
+       char *iobuf;            /* [IOBUF_SIZE] */
+#define hdr_buf bb_common_bufsiz1
+       char *hdr_ptr;
+       int hdr_cnt;
+#if ENABLE_FEATURE_HTTPD_ERROR_PAGES
+       const char *http_error_page[ARRAY_SIZE(http_response_type)];
+#endif
+#if ENABLE_FEATURE_HTTPD_PROXY
+       Htaccess_Proxy *proxy;
+#endif
+};
+#define G (*ptr_to_globals)
+#define verbose           (G.verbose          )
+#define flg_deny_all      (G.flg_deny_all     )
+#define rmt_ip            (G.rmt_ip           )
+#define bind_addr_or_port (G.bind_addr_or_port)
+#define g_query           (G.g_query          )
+#define opt_c_configFile  (G.opt_c_configFile )
+#define home_httpd        (G.home_httpd       )
+#define index_page        (G.index_page       )
+#define found_mime_type   (G.found_mime_type  )
+#define found_moved_temporarily (G.found_moved_temporarily)
+#define last_mod          (G.last_mod         )
+#define ip_a_d            (G.ip_a_d           )
+#define g_realm           (G.g_realm          )
+#define remoteuser        (G.remoteuser       )
+#define referer           (G.referer          )
+#define user_agent        (G.user_agent       )
+#define host              (G.host             )
+#define http_accept       (G.http_accept      )
+#define http_accept_language (G.http_accept_language)
+#define file_size         (G.file_size        )
+#if ENABLE_FEATURE_HTTPD_RANGES
+#define range_start       (G.range_start      )
+#define range_end         (G.range_end        )
+#define range_len         (G.range_len        )
+#else
+enum {
+       range_start = 0,
+       range_end = MAXINT(off_t) - 1,
+       range_len = MAXINT(off_t),
+};
+#endif
+#define rmt_ip_str        (G.rmt_ip_str       )
+#define g_auth            (G.g_auth           )
+#define mime_a            (G.mime_a           )
+#define script_i          (G.script_i         )
+#define iobuf             (G.iobuf            )
+#define hdr_ptr           (G.hdr_ptr          )
+#define hdr_cnt           (G.hdr_cnt          )
+#define http_error_page   (G.http_error_page  )
+#define proxy             (G.proxy            )
+#define INIT_G() do { \
+       SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
+       USE_FEATURE_HTTPD_BASIC_AUTH(g_realm = "Web Server Authentication";) \
+       bind_addr_or_port = "80"; \
+       index_page = "index.html"; \
+       file_size = -1; \
+} while (0)
+
+
+#define STRNCASECMP(a, str) strncasecmp((a), (str), sizeof(str)-1)
+
+/* Prototypes */
+enum {
+       SEND_HEADERS     = (1 << 0),
+       SEND_BODY        = (1 << 1),
+       SEND_HEADERS_AND_BODY = SEND_HEADERS + SEND_BODY,
+};
+static void send_file_and_exit(const char *url, int what) NORETURN;
+
+static void free_llist(has_next_ptr **pptr)
+{
+       has_next_ptr *cur = *pptr;
+       while (cur) {
+               has_next_ptr *t = cur;
+               cur = cur->next;
+               free(t);
+       }
+       *pptr = NULL;
+}
+
+static ALWAYS_INLINE void free_Htaccess_list(Htaccess **pptr)
+{
+       free_llist((has_next_ptr**)pptr);
+}
+
+static ALWAYS_INLINE void free_Htaccess_IP_list(Htaccess_IP **pptr)
+{
+       free_llist((has_next_ptr**)pptr);
+}
+
+/* Returns presumed mask width in bits or < 0 on error.
+ * Updates strp, stores IP at provided pointer */
+static int scan_ip(const char **strp, unsigned *ipp, unsigned char endc)
+{
+       const char *p = *strp;
+       int auto_mask = 8;
+       unsigned ip = 0;
+       int j;
+
+       if (*p == '/')
+               return -auto_mask;
+
+       for (j = 0; j < 4; j++) {
+               unsigned octet;
+
+               if ((*p < '0' || *p > '9') && *p != '/' && *p)
+                       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)
+                       auto_mask += 8;
+               ip = (ip << 8) | octet;
+       }
+       if (*p) {
+               if (*p != endc)
+                       return -auto_mask;
+               p++;
+               if (*p == '\0')
+                       return -auto_mask;
+       }
+       *ipp = ip;
+       *strp = p;
+       return auto_mask;
+}
+
+/* Returns 0 on success. Stores IP and mask at provided pointers */
+static int scan_ip_mask(const char *str, unsigned *ipp, unsigned *maskp)
+{
+       int i;
+       unsigned mask;
+       char *p;
+
+       i = scan_ip(&str, ipp, '/');
+       if (i < 0)
+               return i;
+
+       if (*str) {
+               /* there is /xxx after dotted-IP address */
+               i = bb_strtou(str, &p, 10);
+               if (*p == '.') {
+                       /* 'xxx' itself is dotted-IP mask, parse it */
+                       /* (return 0 (success) only if it has N.N.N.N form) */
+                       return scan_ip(&str, maskp, '\0') - 32;
+               }
+               if (*p)
+                       return -1;
+       }
+
+       if (i > 32)
+               return -1;
+
+       if (sizeof(unsigned) == 4 && i == 32) {
+               /* mask >>= 32 below may not work */
+               mask = 0;
+       } else {
+               mask = 0xffffffff;
+               mask >>= i;
+       }
+       /* i == 0 -> *maskp = 0x00000000
+        * i == 1 -> *maskp = 0x80000000
+        * i == 4 -> *maskp = 0xf0000000
+        * i == 31 -> *maskp = 0xfffffffe
+        * i == 32 -> *maskp = 0xffffffff */
+       *maskp = (uint32_t)(~mask);
+       return 0;
+}
+
+/*
+ * Parse configuration file into in-memory linked list.
+ *
+ * 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.
+ * Error pages are only parsed on the main config file.
+ *
+ * path   Path where to look for httpd.conf (without filename).
+ * flag   Type of the parse request.
+ */
+/* flag param: */
+enum {
+       FIRST_PARSE    = 0, /* path will be "/etc" */
+       SIGNALED_PARSE = 1, /* path will be "/etc" */
+       SUBDIR_PARSE   = 2, /* path will be derived from URL */
+};
+static void parse_conf(const char *path, int flag)
+{
+       /* internally used extra flag state */
+       enum { TRY_CURDIR_PARSE = 3 };
+
+       FILE *f;
+       const char *filename;
+       char buf[160];
+
+       /* discard old rules */
+       free_Htaccess_IP_list(&ip_a_d);
+       flg_deny_all = 0;
+       /* retain previous auth and mime config only for subdir parse */
+       if (flag != SUBDIR_PARSE) {
+               free_Htaccess_list(&mime_a);
+#if ENABLE_FEATURE_HTTPD_BASIC_AUTH
+               free_Htaccess_list(&g_auth);
+#endif
+#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR
+               free_Htaccess_list(&script_i);
+#endif
+       }
+
+       filename = opt_c_configFile;
+       if (flag == SUBDIR_PARSE || filename == NULL) {
+               filename = alloca(strlen(path) + sizeof(HTTPD_CONF) + 2);
+               sprintf((char *)filename, "%s/%s", path, HTTPD_CONF);
+       }
+
+       while ((f = fopen_for_read(filename)) == NULL) {
+               if (flag >= SUBDIR_PARSE) { /* SUBDIR or TRY_CURDIR */
+                       /* config file not found, no changes to config */
+                       return;
+               }
+               if (flag == FIRST_PARSE) {
+                       /* -c CONFFILE given, but CONFFILE doesn't exist? */
+                       if (opt_c_configFile)
+                               bb_simple_perror_msg_and_die(opt_c_configFile);
+                       /* else: no -c, thus we looked at /etc/httpd.conf,
+                        * and it's not there. try ./httpd.conf: */
+               }
+               flag = TRY_CURDIR_PARSE;
+               filename = HTTPD_CONF;
+       }
+
+#if ENABLE_FEATURE_HTTPD_BASIC_AUTH
+       /* in "/file:user:pass" lines, we prepend path in subdirs */
+       if (flag != SUBDIR_PARSE)
+               path = "";
+#endif
+       /* The lines can be:
+        *
+        * I:default_index_file
+        * H:http_home
+        * [AD]:IP[/mask]   # allow/deny, * for wildcard
+        * Ennn:error.html  # error page for status nnn
+        * P:/url:[http://]hostname[:port]/new/path # reverse proxy
+        * .ext:mime/type   # mime type
+        * *.php:/path/php  # run xxx.php through an interpreter
+        * /file:user:pass  # username and password
+        */
+       while (fgets(buf, sizeof(buf), f) != NULL) {
+               unsigned strlen_buf;
+               unsigned char ch;
+               char *after_colon;
+
+               { /* remove all whitespace, and # comments */
+                       char *p, *p0;
+
+                       p0 = buf;
+                       /* skip non-whitespace beginning. Often the whole line
+                        * is non-whitespace. We want this case to work fast,
+                        * without needless copying, therefore we don't merge
+                        * this operation into next while loop. */
+                       while ((ch = *p0) != '\0' && ch != '\n' && ch != '#'
+                        && ch != ' ' && ch != '\t'
+                       ) {
+                               p0++;
+                       }
+                       p = p0;
+                       /* if we enter this loop, we have some whitespace.
+                        * discard it */
+                       while (ch != '\0' && ch != '\n' && ch != '#') {
+                               if (ch != ' ' && ch != '\t') {
+                                       *p++ = ch;
+                               }
+                               ch = *++p0;
+                       }
+                       *p = '\0';
+                       strlen_buf = p - buf;
+                       if (strlen_buf == 0)
+                               continue; /* empty line */
+               }
+
+               after_colon = strchr(buf, ':');
+               /* strange line? */
+               if (after_colon == NULL || *++after_colon == '\0')
+                       goto config_error;
+
+               ch = (buf[0] & ~0x20); /* toupper if it's a letter */
+
+               if (ch == 'I') {
+                       index_page = xstrdup(after_colon);
+                       continue;
+               }
+
+               /* do not allow jumping around using H in subdir's configs */
+               if (flag == FIRST_PARSE && ch == 'H') {
+                       home_httpd = xstrdup(after_colon);
+                       xchdir(home_httpd);
+                       continue;
+               }
+
+               if (ch == 'A' || ch == 'D') {
+                       Htaccess_IP *pip;
+
+                       if (*after_colon == '*') {
+                               if (ch == 'D') {
+                                       /* memorize "deny all" */
+                                       flg_deny_all = 1;
+                               }
+                               /* skip assumed "A:*", it is a default anyway */
+                               continue;
+                       }
+                       /* store "allow/deny IP/mask" line */
+                       pip = xzalloc(sizeof(*pip));
+                       if (scan_ip_mask(after_colon, &pip->ip, &pip->mask)) {
+                               /* IP{/mask} syntax error detected, protect all */
+                               ch = 'D';
+                               pip->mask = 0;
+                       }
+                       pip->allow_deny = ch;
+                       if (ch == 'D') {
+                               /* Deny:from_IP - prepend */
+                               pip->next = ip_a_d;
+                               ip_a_d = pip;
+                       } else {
+                               /* A:from_IP - append (thus all D's precedes A's) */
+                               Htaccess_IP *prev_IP = ip_a_d;
+                               if (prev_IP == NULL) {
+                                       ip_a_d = pip;
+                               } else {
+                                       while (prev_IP->next)
+                                               prev_IP = prev_IP->next;
+                                       prev_IP->next = pip;
+                               }
+                       }
+                       continue;
+               }
+
+#if ENABLE_FEATURE_HTTPD_ERROR_PAGES
+               if (flag == FIRST_PARSE && ch == 'E') {
+                       unsigned i;
+                       int status = atoi(buf + 1); /* error status code */
+
+                       if (status < HTTP_CONTINUE) {
+                               goto config_error;
+                       }
+                       /* then error page; find matching status */
+                       for (i = 0; i < ARRAY_SIZE(http_response_type); i++) {
+                               if (http_response_type[i] == status) {
+                                       /* We chdir to home_httpd, thus no need to
+                                        * concat_path_file(home_httpd, after_colon)
+                                        * here */
+                                       http_error_page[i] = xstrdup(after_colon);
+                                       break;
+                               }
+                       }
+                       continue;
+               }
+#endif
+
+#if ENABLE_FEATURE_HTTPD_PROXY
+               if (flag == FIRST_PARSE && ch == 'P') {
+                       /* P:/url:[http://]hostname[:port]/new/path */
+                       char *url_from, *host_port, *url_to;
+                       Htaccess_Proxy *proxy_entry;
+
+                       url_from = after_colon;
+                       host_port = strchr(after_colon, ':');
+                       if (host_port == NULL) {
+                               goto config_error;
+                       }
+                       *host_port++ = '\0';
+                       if (strncmp(host_port, "http://", 7) == 0)
+                               host_port += 7;
+                       if (*host_port == '\0') {
+                               goto config_error;
+                       }
+                       url_to = strchr(host_port, '/');
+                       if (url_to == NULL) {
+                               goto config_error;
+                       }
+                       *url_to = '\0';
+                       proxy_entry = xzalloc(sizeof(*proxy_entry));
+                       proxy_entry->url_from = xstrdup(url_from);
+                       proxy_entry->host_port = xstrdup(host_port);
+                       *url_to = '/';
+                       proxy_entry->url_to = xstrdup(url_to);
+                       proxy_entry->next = proxy;
+                       proxy = proxy_entry;
+                       continue;
+               }
+#endif
+               /* the rest of directives are non-alphabetic,
+                * must avoid using "toupper'ed" ch */
+               ch = buf[0];
+
+               if (ch == '.' /* ".ext:mime/type" */
+#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR
+                || (ch == '*' && buf[1] == '.') /* "*.php:/path/php" */
+#endif
+               ) {
+                       char *p;
+                       Htaccess *cur;
+
+                       cur = xzalloc(sizeof(*cur) /* includes space for NUL */ + strlen_buf);
+                       strcpy(cur->before_colon, buf);
+                       p = cur->before_colon + (after_colon - buf);
+                       p[-1] = '\0';
+                       cur->after_colon = p;
+                       if (ch == '.') {
+                               /* .mime line: prepend to mime_a list */
+                               cur->next = mime_a;
+                               mime_a = cur;
+                       }
+#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR
+                       else {
+                               /* script interpreter line: prepend to script_i list */
+                               cur->next = script_i;
+                               script_i = cur;
+                       }
+#endif
+                       continue;
+               }
+
+#if ENABLE_FEATURE_HTTPD_BASIC_AUTH
+               if (ch == '/') { /* "/file:user:pass" */
+                       char *p;
+                       Htaccess *cur;
+                       unsigned file_len;
+
+                       /* note: path is "" unless we are in SUBDIR parse,
+                        * otherwise it does NOT start with "/" */
+                       cur = xzalloc(sizeof(*cur) /* includes space for NUL */
+                               + 1 + strlen(path)
+                               + strlen_buf
+                               );
+                       /* form "/path/file" */
+                       sprintf(cur->before_colon, "/%s%.*s",
+                               path,
+                               (int) (after_colon - buf - 1), /* includes "/", but not ":" */
+                               buf);
+                       /* canonicalize it */
+                       p = bb_simplify_abs_path_inplace(cur->before_colon);
+                       file_len = p - cur->before_colon;
+                       /* add "user:pass" after NUL */
+                       strcpy(++p, after_colon);
+                       cur->after_colon = p;
+
+                       /* insert cur into g_auth */
+                       /* g_auth is sorted by decreased filename length */
+                       {
+                               Htaccess *auth, **authp;
+
+                               authp = &g_auth;
+                               while ((auth = *authp) != NULL) {
+                                       if (file_len >= strlen(auth->before_colon)) {
+                                               /* insert cur before auth */
+                                               cur->next = auth;
+                                               break;
+                                       }
+                                       authp = &auth->next;
+                               }
+                               *authp = cur;
+                       }
+                       continue;
+               }
+#endif /* BASIC_AUTH */
+
+               /* the line is not recognized */
+ config_error:
+               bb_error_msg("config error '%s' in '%s'", buf, filename);
+        } /* while (fgets) */
+
+        fclose(f);
+}
+
+#if ENABLE_FEATURE_HTTPD_ENCODE_URL_STR
+/*
+ * 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().
+ *
+ * Returns a pointer to the encoded string (malloced).
+ */
+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 = xmalloc(len * 6 + 1);
+       char *p = out;
+       char ch;
+
+       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 */
+
+/*
+ * 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 xstrdup() 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.
+ *
+ * string    The first string to decode.
+ * option_d  1 if called for httpd -d
+ *
+ * Returns a pointer to the decoded string (same as input).
+ */
+static unsigned hex_to_bin(unsigned char c)
+{
+       unsigned v;
+
+       v = c - '0';
+       if (v <= 9)
+               return v;
+       /* c | 0x20: letters to lower case, non-letters
+        * to (potentially different) non-letters */
+       v = (unsigned)(c | 0x20) - 'a';
+       if (v <= 5)
+               return v + 10;
+       return ~0;
+}
+/* For testing:
+void t(char c) { printf("'%c'(%u) %u\n", c, c, hex_to_bin(c)); }
+int main() { t(0x10); t(0x20); t('0'); t('9'); t('A'); t('F'); t('a'); t('f');
+t('0'-1); t('9'+1); t('A'-1); t('F'+1); t('a'-1); t('f'+1); return 0; }
+*/
+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 v;
+
+               if (option_d && c == '+') {
+                       *string++ = ' ';
+                       continue;
+               }
+               if (c != '%') {
+                       *string++ = c;
+                       continue;
+               }
+               v = hex_to_bin(ptr[0]);
+               if (v > 15) {
+ bad_hex:
+                       if (!option_d)
+                               return NULL;
+                       *string++ = '%';
+                       continue;
+               }
+               v = (v * 16) | hex_to_bin(ptr[1]);
+               if (v > 255)
+                       goto bad_hex;
+               if (!option_d && (v == '/' || v == '\0')) {
+                       /* caller takes it as indication of invalid
+                        * (dangerous wrt exploits) chars */
+                       return orig + 1;
+               }
+               *string++ = v;
+               ptr += 2;
+       }
+       *string = '\0';
+       return orig;
+}
+
+#if ENABLE_FEATURE_HTTPD_BASIC_AUTH
+/*
+ * Decode a base64 data stream as per rfc1521.
+ * Note that the rfc states that non 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: a pointer to a base64 encoded string.
+ * Decoded data is stored in-place.
+ */
+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 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
+
+/*
+ * Create a listen server socket on the designated port.
+ */
+static int openServer(void)
+{
+       unsigned n = bb_strtou(bind_addr_or_port, NULL, 10);
+       if (!errno && n && n <= 0xffff)
+               n = create_and_bind_stream_or_die(NULL, n);
+       else
+               n = create_and_bind_stream_or_die(bind_addr_or_port, 80);
+       xlisten(n, 9);
+       return n;
+}
+
+/*
+ * Log the connection closure and exit.
+ */
+static void log_and_exit(void) NORETURN;
+static void log_and_exit(void)
+{
+       /* Paranoia. IE said to be buggy. It may send some extra data
+        * or be confused by us just exiting without SHUT_WR. Oh well. */
+       shutdown(1, SHUT_WR);
+       /* Why??
+       (this also messes up stdin when user runs httpd -i from terminal)
+       ndelay_on(0);
+       while (read(STDIN_FILENO, iobuf, IOBUF_SIZE) > 0)
+               continue;
+       */
+
+       if (verbose > 2)
+               bb_error_msg("closed");
+       _exit(xfunc_error_retval);
+}
+
+/*
+ * 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.
+ * responseNum - the result code to send.
+ */
+static void send_headers(int responseNum)
+{
+       static const char RFC1123FMT[] ALIGN1 = "%a, %d %b %Y %H:%M:%S GMT";
+
+       const char *responseString = "";
+       const char *infoString = NULL;
+       const char *mime_type;
+#if ENABLE_FEATURE_HTTPD_ERROR_PAGES
+       const char *error_page = NULL;
+#endif
+       unsigned i;
+       time_t timer = time(NULL);
+       char tmp_str[80];
+       int len;
+
+       for (i = 0; i < ARRAY_SIZE(http_response_type); i++) {
+               if (http_response_type[i] == responseNum) {
+                       responseString = http_response[i].name;
+                       infoString = http_response[i].info;
+#if ENABLE_FEATURE_HTTPD_ERROR_PAGES
+                       error_page = http_error_page[i];
+#endif
+                       break;
+               }
+       }
+       /* error message is HTML */
+       mime_type = responseNum == HTTP_OK ?
+                               found_mime_type : "text/html";
+
+       if (verbose)
+               bb_error_msg("response:%u", responseNum);
+
+       /* emit the current date */
+       strftime(tmp_str, sizeof(tmp_str), RFC1123FMT, gmtime(&timer));
+       len = sprintf(iobuf,
+                       "HTTP/1.0 %d %s\r\nContent-type: %s\r\n"
+                       "Date: %s\r\nConnection: close\r\n",
+                       responseNum, responseString, mime_type, tmp_str);
+
+#if ENABLE_FEATURE_HTTPD_BASIC_AUTH
+       if (responseNum == HTTP_UNAUTHORIZED) {
+               len += sprintf(iobuf + len,
+                               "WWW-Authenticate: Basic realm=\"%s\"\r\n",
+                               g_realm);
+       }
+#endif
+       if (responseNum == HTTP_MOVED_TEMPORARILY) {
+               len += sprintf(iobuf + len, "Location: %s/%s%s\r\n",
+                               found_moved_temporarily,
+                               (g_query ? "?" : ""),
+                               (g_query ? g_query : ""));
+       }
+
+#if ENABLE_FEATURE_HTTPD_ERROR_PAGES
+       if (error_page && access(error_page, R_OK) == 0) {
+               strcat(iobuf, "\r\n");
+               len += 2;
+
+               if (DEBUG)
+                       fprintf(stderr, "headers: '%s'\n", iobuf);
+               full_write(STDOUT_FILENO, iobuf, len);
+               if (DEBUG)
+                       fprintf(stderr, "writing error page: '%s'\n", error_page);
+               return send_file_and_exit(error_page, SEND_BODY);
+       }
+#endif
+
+       if (file_size != -1) {    /* file */
+               strftime(tmp_str, sizeof(tmp_str), RFC1123FMT, gmtime(&last_mod));
+#if ENABLE_FEATURE_HTTPD_RANGES
+               if (responseNum == HTTP_PARTIAL_CONTENT) {
+                       len += sprintf(iobuf + len, "Content-Range: bytes %"OFF_FMT"d-%"OFF_FMT"d/%"OFF_FMT"d\r\n",
+                                       range_start,
+                                       range_end,
+                                       file_size);
+                       file_size = range_end - range_start + 1;
+               }
+#endif
+               len += sprintf(iobuf + len,
+#if ENABLE_FEATURE_HTTPD_RANGES
+                       "Accept-Ranges: bytes\r\n"
+#endif
+                       "Last-Modified: %s\r\n%s %"OFF_FMT"d\r\n",
+                               tmp_str,
+                               "Content-length:",
+                               file_size
+               );
+       }
+       iobuf[len++] = '\r';
+       iobuf[len++] = '\n';
+       if (infoString) {
+               len += sprintf(iobuf + len,
+                               "<HTML><HEAD><TITLE>%d %s</TITLE></HEAD>\n"
+                               "<BODY><H1>%d %s</H1>\n%s\n</BODY></HTML>\n",
+                               responseNum, responseString,
+                               responseNum, responseString, infoString);
+       }
+       if (DEBUG)
+               fprintf(stderr, "headers: '%s'\n", iobuf);
+       if (full_write(STDOUT_FILENO, iobuf, len) != len) {
+               if (verbose > 1)
+                       bb_perror_msg("error");
+               log_and_exit();
+       }
+}
+
+static void send_headers_and_exit(int responseNum) NORETURN;
+static void send_headers_and_exit(int responseNum)
+{
+       send_headers(responseNum);
+       log_and_exit();
+}
+
+/*
+ * Read from the socket until '\n' or EOF. '\r' chars are removed.
+ * '\n' is replaced with NUL.
+ * Return number of characters read or 0 if nothing is read
+ * ('\r' and '\n' are not counted).
+ * Data is returned in iobuf.
+ */
+static int get_line(void)
+{
+       int count = 0;
+       char c;
+
+       alarm(HEADER_READ_TIMEOUT);
+       while (1) {
+               if (hdr_cnt <= 0) {
+                       hdr_cnt = safe_read(STDIN_FILENO, hdr_buf, sizeof(hdr_buf));
+                       if (hdr_cnt <= 0)
+                               break;
+                       hdr_ptr = hdr_buf;
+               }
+               iobuf[count] = c = *hdr_ptr++;
+               hdr_cnt--;
+
+               if (c == '\r')
+                       continue;
+               if (c == '\n') {
+                       iobuf[count] = '\0';
+                       break;
+               }
+               if (count < (IOBUF_SIZE - 1))      /* check overflow */
+                       count++;
+       }
+       return count;
+}
+
+#if ENABLE_FEATURE_HTTPD_CGI || ENABLE_FEATURE_HTTPD_PROXY
+
+/* gcc 4.2.1 fares better with NOINLINE */
+static NOINLINE void cgi_io_loop_and_exit(int fromCgi_rd, int toCgi_wr, int post_len) NORETURN;
+static NOINLINE void cgi_io_loop_and_exit(int fromCgi_rd, int toCgi_wr, int post_len)
+{
+       enum { FROM_CGI = 1, TO_CGI = 2 }; /* indexes in pfd[] */
+       struct pollfd pfd[3];
+       int out_cnt; /* we buffer a bit of initial CGI output */
+       int count;
+
+       /* iobuf is used for CGI -> network data,
+        * hdr_buf is for network -> CGI data (POSTDATA) */
+
+       /* If CGI dies, we still want to correctly finish reading its output
+        * and send it to the peer. So please no SIGPIPEs! */
+       signal(SIGPIPE, SIG_IGN);
+
+       // We inconsistently handle a case when more POSTDATA from network
+       // is coming than we expected. We may give *some part* of that
+       // extra data to CGI.
+
+       //if (hdr_cnt > post_len) {
+       //      /* We got more POSTDATA from network than we expected */
+       //      hdr_cnt = post_len;
+       //}
+       post_len -= hdr_cnt;
+       /* post_len - number of POST bytes not yet read from network */
+
+       /* NB: breaking out of this loop jumps to log_and_exit() */
+       out_cnt = 0;
+       while (1) {
+               memset(pfd, 0, sizeof(pfd));
+
+               pfd[FROM_CGI].fd = fromCgi_rd;
+               pfd[FROM_CGI].events = POLLIN;
+
+               if (toCgi_wr) {
+                       pfd[TO_CGI].fd = toCgi_wr;
+                       if (hdr_cnt > 0) {
+                               pfd[TO_CGI].events = POLLOUT;
+                       } else if (post_len > 0) {
+                               pfd[0].events = POLLIN;
+                       } else {
+                               /* post_len <= 0 && hdr_cnt <= 0:
+                                * no more POST data to CGI,
+                                * let CGI see EOF on CGI's stdin */
+                               close(toCgi_wr);
+                               toCgi_wr = 0;
+                       }
+               }
+
+               /* Now wait on the set of sockets */
+               count = safe_poll(pfd, 3, -1);
+               if (count <= 0) {
+#if 0
+                       if (safe_waitpid(pid, &status, WNOHANG) <= 0) {
+                               /* Weird. CGI didn't exit and no fd's
+                                * are ready, yet poll returned?! */
+                               continue;
+                       }
+                       if (DEBUG && WIFEXITED(status))
+                               bb_error_msg("CGI exited, status=%d", WEXITSTATUS(status));
+                       if (DEBUG && WIFSIGNALED(status))
+                               bb_error_msg("CGI killed, signal=%d", WTERMSIG(status));
+#endif
+                       break;
+               }
+
+               if (pfd[TO_CGI].revents) {
+                       /* hdr_cnt > 0 here due to the way pfd[TO_CGI].events set */
+                       /* Have data from peer and can write to CGI */
+                       count = safe_write(toCgi_wr, hdr_ptr, hdr_cnt);
+                       /* Doesn't happen, we dont use nonblocking IO here
+                        *if (count < 0 && errno == EAGAIN) {
+                        *      ...
+                        *} else */
+                       if (count > 0) {
+                               hdr_ptr += count;
+                               hdr_cnt -= count;
+                       } else {
+                               /* EOF/broken pipe to CGI, stop piping POST data */
+                               hdr_cnt = post_len = 0;
+                       }
+               }
+
+               if (pfd[0].revents) {
+                       /* post_len > 0 && hdr_cnt == 0 here */
+                       /* We expect data, prev data portion is eaten by CGI
+                        * and there *is* data to read from the peer
+                        * (POSTDATA) */
+                       //count = post_len > (int)sizeof(hdr_buf) ? (int)sizeof(hdr_buf) : post_len;
+                       //count = safe_read(STDIN_FILENO, hdr_buf, count);
+                       count = safe_read(STDIN_FILENO, hdr_buf, sizeof(hdr_buf));
+                       if (count > 0) {
+                               hdr_cnt = count;
+                               hdr_ptr = hdr_buf;
+                               post_len -= count;
+                       } else {
+                               /* no more POST data can be read */
+                               post_len = 0;
+                       }
+               }
+
+               if (pfd[FROM_CGI].revents) {
+                       /* There is something to read from CGI */
+                       char *rbuf = iobuf;
+
+                       /* Are we still buffering CGI output? */
+                       if (out_cnt >= 0) {
+                               /* HTTP_200[] has single "\r\n" at the end.
+                                * According to http://hoohoo.ncsa.uiuc.edu/cgi/out.html,
+                                * CGI scripts MUST send their own header terminated by
+                                * empty line, then data. That's why we have only one
+                                * <cr><lf> pair here. We will output "200 OK" line
+                                * if needed, but CGI still has to provide blank line
+                                * between header and body */
+
+                               /* Must use safe_read, not full_read, because
+                                * CGI may output a few first bytes and then wait
+                                * for POSTDATA without closing stdout.
+                                * With full_read we may wait here forever. */
+                               count = safe_read(fromCgi_rd, rbuf + out_cnt, PIPE_BUF - 8);
+                               if (count <= 0) {
+                                       /* eof (or error) and there was no "HTTP",
+                                        * so write it, then write received data */
+                                       if (out_cnt) {
+                                               full_write(STDOUT_FILENO, HTTP_200, sizeof(HTTP_200)-1);
+                                               full_write(STDOUT_FILENO, rbuf, out_cnt);
+                                       }
+                                       break; /* CGI stdout is closed, exiting */
+                               }
+                               out_cnt += count;
+                               count = 0;
+                               /* "Status" header format is: "Status: 302 Redirected\r\n" */
+                               if (out_cnt >= 8 && memcmp(rbuf, "Status: ", 8) == 0) {
+                                       /* send "HTTP/1.0 " */
+                                       if (full_write(STDOUT_FILENO, HTTP_200, 9) != 9)
+                                               break;
+                                       rbuf += 8; /* skip "Status: " */
+                                       count = out_cnt - 8;
+                                       out_cnt = -1; /* buffering off */
+                               } else if (out_cnt >= 4) {
+                                       /* Did CGI add "HTTP"? */
+                                       if (memcmp(rbuf, HTTP_200, 4) != 0) {
+                                               /* there is no "HTTP", do it ourself */
+                                               if (full_write(STDOUT_FILENO, HTTP_200, sizeof(HTTP_200)-1) != sizeof(HTTP_200)-1)
+                                                       break;
+                                       }
+                                       /* Commented out:
+                                       if (!strstr(rbuf, "ontent-")) {
+                                               full_write(s, "Content-type: text/plain\r\n\r\n", 28);
+                                       }
+                                        * Counter-example of valid CGI without Content-type:
+                                        * echo -en "HTTP/1.0 302 Found\r\n"
+                                        * echo -en "Location: http://www.busybox.net\r\n"
+                                        * echo -en "\r\n"
+                                        */
+                                       count = out_cnt;
+                                       out_cnt = -1; /* buffering off */
+                               }
+                       } else {
+                               count = safe_read(fromCgi_rd, rbuf, PIPE_BUF);
+                               if (count <= 0)
+                                       break;  /* eof (or error) */
+                       }
+                       if (full_write(STDOUT_FILENO, rbuf, count) != count)
+                               break;
+                       if (DEBUG)
+                               fprintf(stderr, "cgi read %d bytes: '%.*s'\n", count, count, rbuf);
+               } /* if (pfd[FROM_CGI].revents) */
+       } /* while (1) */
+       log_and_exit();
+}
+#endif
+
+#if ENABLE_FEATURE_HTTPD_CGI
+
+static void setenv1(const char *name, const char *value)
+{
+       setenv(name, value ? value : "", 1);
+}
+
+/*
+ * Spawn CGI script, forward CGI's stdin/out <=> network
+ *
+ * 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 post_len                 Length of the POST body.
+ * const char *cookie           For set HTTP_COOKIE.
+ * const char *content_type     For set CONTENT_TYPE.
+ */
+static void send_cgi_and_exit(
+               const char *url,
+               const char *request,
+               int post_len,
+               const char *cookie,
+               const char *content_type) NORETURN;
+static void send_cgi_and_exit(
+               const char *url,
+               const char *request,
+               int post_len,
+               const char *cookie,
+               const char *content_type)
+{
+       struct fd_pair fromCgi;  /* CGI -> httpd pipe */
+       struct fd_pair toCgi;    /* httpd -> CGI pipe */
+       char *script;
+       int pid;
+
+       /* Make a copy. NB: caller guarantees:
+        * url[0] == '/', url[1] != '/' */
+       url = xstrdup(url);
+
+       /*
+        * We are mucking with environment _first_ and then vfork/exec,
+        * this allows us to use vfork safely. Parent doesn't care about
+        * these environment changes anyway.
+        */
+
+       /* Check for [dirs/]script.cgi/PATH_INFO */
+       script = (char*)url;
+       while ((script = strchr(script + 1, '/')) != NULL) {
+               struct stat sb;
+
+               *script = '\0';
+               if (!is_directory(url + 1, 1, &sb)) {
+                       /* not directory, found script.cgi/PATH_INFO */
+                       *script = '/';
+                       break;
+               }
+               *script = '/'; /* is directory, find next '/' */
+       }
+       setenv1("PATH_INFO", script);   /* set to /PATH_INFO or "" */
+       setenv1("REQUEST_METHOD", request);
+       if (g_query) {
+               putenv(xasprintf("%s=%s?%s", "REQUEST_URI", url, g_query));
+       } else {
+               setenv1("REQUEST_URI", url);
+       }
+       if (script != NULL)
+               *script = '\0';         /* cut off /PATH_INFO */
+
+       /* SCRIPT_FILENAME is required by PHP in CGI mode */
+       if (home_httpd[0] == '/') {
+               char *fullpath = concat_path_file(home_httpd, url);
+               setenv1("SCRIPT_FILENAME", fullpath);
+       }
+       /* set SCRIPT_NAME as full path: /cgi-bin/dirs/script.cgi */
+       setenv1("SCRIPT_NAME", url);
+       /* 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 seem to do some decoding) */
+       setenv1("QUERY_STRING", g_query);
+       putenv((char*)"SERVER_SOFTWARE=busybox httpd/"BB_VER);
+       putenv((char*)"SERVER_PROTOCOL=HTTP/1.0");
+       putenv((char*)"GATEWAY_INTERFACE=CGI/1.1");
+       /* Having _separate_ variables for IP and port defeats
+        * the purpose of having socket abstraction. Which "port"
+        * are you using on Unix domain socket?
+        * IOW - REMOTE_PEER="1.2.3.4:56" makes much more sense.
+        * Oh well... */
+       {
+               char *p = rmt_ip_str ? rmt_ip_str : (char*)"";
+               char *cp = strrchr(p, ':');
+               if (ENABLE_FEATURE_IPV6 && cp && strchr(cp, ']'))
+                       cp = NULL;
+               if (cp) *cp = '\0'; /* delete :PORT */
+               setenv1("REMOTE_ADDR", p);
+               if (cp) {
+                       *cp = ':';
+#if ENABLE_FEATURE_HTTPD_SET_REMOTE_PORT_TO_ENV
+                       setenv1("REMOTE_PORT", cp + 1);
+#endif
+               }
+       }
+       setenv1("HTTP_USER_AGENT", user_agent);
+       if (http_accept)
+               setenv1("HTTP_ACCEPT", http_accept);
+       if (http_accept_language)
+               setenv1("HTTP_ACCEPT_LANGUAGE", http_accept_language);
+       if (post_len)
+               putenv(xasprintf("CONTENT_LENGTH=%d", post_len));
+       if (cookie)
+               setenv1("HTTP_COOKIE", cookie);
+       if (content_type)
+               setenv1("CONTENT_TYPE", content_type);
+#if ENABLE_FEATURE_HTTPD_BASIC_AUTH
+       if (remoteuser) {
+               setenv1("REMOTE_USER", remoteuser);
+               putenv((char*)"AUTH_TYPE=Basic");
+       }
+#endif
+       if (referer)
+               setenv1("HTTP_REFERER", referer);
+       setenv1("HTTP_HOST", host); /* set to "" if NULL */
+       /* setenv1("SERVER_NAME", safe_gethostname()); - don't do this,
+        * just run "env SERVER_NAME=xyz httpd ..." instead */
+
+       xpiped_pair(fromCgi);
+       xpiped_pair(toCgi);
+
+       pid = vfork();
+       if (pid < 0) {
+               /* TODO: log perror? */
+               log_and_exit();
+       }
+
+       if (!pid) {
+               /* Child process */
+               char *argv[3];
+
+               xfunc_error_retval = 242;
+
+               /* NB: close _first_, then move fds! */
+               close(toCgi.wr);
+               close(fromCgi.rd);
+               xmove_fd(toCgi.rd, 0);  /* replace stdin with the pipe */
+               xmove_fd(fromCgi.wr, 1);  /* replace stdout with the pipe */
+               /* User seeing stderr output can be a security problem.
+                * If CGI really wants that, it can always do dup itself. */
+               /* dup2(1, 2); */
+
+               /* Chdiring to script's dir */
+               script = strrchr(url, '/');
+               if (script != url) { /* paranoia */
+                       *script = '\0';
+                       if (chdir(url + 1) != 0) {
+                               bb_perror_msg("chdir %s", url + 1);
+                               goto error_execing_cgi;
+                       }
+                       // not needed: *script = '/';
+               }
+               script++;
+
+               /* set argv[0] to name without path */
+               argv[0] = script;
+               argv[1] = NULL;
+
+#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR
+               {
+                       char *suffix = strrchr(script, '.');
+
+                       if (suffix) {
+                               Htaccess *cur;
+                               for (cur = script_i; cur; cur = cur->next) {
+                                       if (strcmp(cur->before_colon + 1, suffix) == 0) {
+                                               /* found interpreter name */
+                                               argv[0] = cur->after_colon;
+                                               argv[1] = script;
+                                               argv[2] = NULL;
+                                               break;
+                                       }
+                               }
+                       }
+               }
+#endif
+               /* restore default signal dispositions for CGI process */
+               bb_signals(0
+                       | (1 << SIGCHLD)
+                       | (1 << SIGPIPE)
+                       | (1 << SIGHUP)
+                       , SIG_DFL);
+
+               /* _NOT_ execvp. We do not search PATH. argv[0] is a filename
+                * without any dir components and will only match a file
+                * in the current directory */
+               execv(argv[0], argv);
+               if (verbose)
+                       bb_perror_msg("exec %s", argv[0]);
+ error_execing_cgi:
+               /* send to stdout
+                * (we are CGI here, our stdout is pumped to the net) */
+               send_headers_and_exit(HTTP_NOT_FOUND);
+       } /* end child */
+
+       /* Parent process */
+
+       /* Restore variables possibly changed by child */
+       xfunc_error_retval = 0;
+
+       /* Pump data */
+       close(fromCgi.wr);
+       close(toCgi.rd);
+       cgi_io_loop_and_exit(fromCgi.rd, toCgi.wr, post_len);
+}
+
+#endif          /* FEATURE_HTTPD_CGI */
+
+/*
+ * Send a file response to a HTTP request, and exit
+ *
+ * Parameters:
+ * const char *url  The requested URL (with leading /).
+ * what             What to send (headers/body/both).
+ */
+static NOINLINE void send_file_and_exit(const char *url, int what)
+{
+       static const char *const suffixTable[] = {
+       /* Warning: shorter 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
+               NULL
+       };
+
+       char *suffix;
+       int fd;
+       const char *const *table;
+       const char *try_suffix;
+       ssize_t count;
+
+       fd = open(url, O_RDONLY);
+       if (fd < 0) {
+               if (DEBUG)
+                       bb_perror_msg("can't open '%s'", url);
+               /* Error pages are sent by using send_file_and_exit(SEND_BODY).
+                * IOW: it is unsafe to call send_headers_and_exit
+                * if what is SEND_BODY! Can recurse! */
+               if (what != SEND_BODY)
+                       send_headers_and_exit(HTTP_NOT_FOUND);
+               log_and_exit();
+       }
+       /* If you want to know about EPIPE below
+        * (happens if you abort downloads from local httpd): */
+       signal(SIGPIPE, SIG_IGN);
+
+       suffix = strrchr(url, '.');
+
+       /* If not found, set default as "application/octet-stream";  */
+       found_mime_type = "application/octet-stream";
+       if (suffix) {
+               Htaccess *cur;
+               for (table = suffixTable; *table; table += 2) {
+                       try_suffix = strstr(table[0], suffix);
+                       if (try_suffix) {
+                               try_suffix += strlen(suffix);
+                               if (*try_suffix == '\0' || *try_suffix == '.') {
+                                       found_mime_type = table[1];
+                                       break;
+                               }
+                       }
+               }
+               for (cur = mime_a; cur; cur = cur->next) {
+                       if (strcmp(cur->before_colon, suffix) == 0) {
+                               found_mime_type = cur->after_colon;
+                               break;
+                       }
+               }
+       }
+
+       if (DEBUG)
+               bb_error_msg("sending file '%s' content-type: %s",
+                       url, found_mime_type);
+
+#if ENABLE_FEATURE_HTTPD_RANGES
+       if (what == SEND_BODY)
+               range_start = 0; /* err pages and ranges don't mix */
+       range_len = MAXINT(off_t);
+       if (range_start) {
+               if (!range_end) {
+                       range_end = file_size - 1;
+               }
+               if (range_end < range_start
+                || lseek(fd, range_start, SEEK_SET) != range_start
+               ) {
+                       lseek(fd, 0, SEEK_SET);
+                       range_start = 0;
+               } else {
+                       range_len = range_end - range_start + 1;
+                       send_headers(HTTP_PARTIAL_CONTENT);
+                       what = SEND_BODY;
+               }
+       }
+#endif
+       if (what & SEND_HEADERS)
+               send_headers(HTTP_OK);
+#if ENABLE_FEATURE_HTTPD_USE_SENDFILE
+       {
+               off_t offset = range_start;
+               while (1) {
+                       /* sz is rounded down to 64k */
+                       ssize_t sz = MAXINT(ssize_t) - 0xffff;
+                       USE_FEATURE_HTTPD_RANGES(if (sz > range_len) sz = range_len;)
+                       count = sendfile(STDOUT_FILENO, fd, &offset, sz);
+                       if (count < 0) {
+                               if (offset == range_start)
+                                       break; /* fall back to read/write loop */
+                               goto fin;
+                       }
+                       USE_FEATURE_HTTPD_RANGES(range_len -= sz;)
+                       if (count == 0 || range_len == 0)
+                               log_and_exit();
+               }
+       }
+#endif
+       while ((count = safe_read(fd, iobuf, IOBUF_SIZE)) > 0) {
+               ssize_t n;
+               USE_FEATURE_HTTPD_RANGES(if (count > range_len) count = range_len;)
+               n = full_write(STDOUT_FILENO, iobuf, count);
+               if (count != n)
+                       break;
+               USE_FEATURE_HTTPD_RANGES(range_len -= count;)
+               if (range_len == 0)
+                       break;
+       }
+       if (count < 0) {
+ USE_FEATURE_HTTPD_USE_SENDFILE(fin:)
+               if (verbose > 1)
+                       bb_perror_msg("error");
+       }
+       log_and_exit();
+}
+
+static int checkPermIP(void)
+{
+       Htaccess_IP *cur;
+
+       for (cur = ip_a_d; cur; cur = cur->next) {
+#if DEBUG
+               fprintf(stderr,
+                       "checkPermIP: '%s' ? '%u.%u.%u.%u/%u.%u.%u.%u'\n",
+                       rmt_ip_str,
+                       (unsigned char)(cur->ip >> 24),
+                       (unsigned char)(cur->ip >> 16),
+                       (unsigned char)(cur->ip >> 8),
+                       (unsigned char)(cur->ip),
+                       (unsigned char)(cur->mask >> 24),
+                       (unsigned char)(cur->mask >> 16),
+                       (unsigned char)(cur->mask >> 8),
+                       (unsigned char)(cur->mask)
+               );
+#endif
+               if ((rmt_ip & cur->mask) == cur->ip)
+                       return (cur->allow_deny == 'A'); /* A -> 1 */
+       }
+
+       return !flg_deny_all; /* depends on whether we saw "D:*" */
+}
+
+#if ENABLE_FEATURE_HTTPD_BASIC_AUTH
+/*
+ * Config file entries are of the form "/<path>:<user>:<passwd>".
+ * If config file has no prefix match for path, access is allowed.
+ *
+ * path                 The file path
+ * user_and_passwd      "user:passwd" to validate
+ *
+ * Returns 1 if user_and_passwd is OK.
+ */
+static int check_user_passwd(const char *path, const char *user_and_passwd)
+{
+       Htaccess *cur;
+       const char *prev = NULL;
+
+       for (cur = g_auth; cur; cur = cur->next) {
+               const char *dir_prefix;
+               size_t len;
+
+               dir_prefix = cur->before_colon;
+
+               /* WHY? */
+               /* If already saw a match, don't accept other different matches */
+               if (prev && strcmp(prev, dir_prefix) != 0)
+                       continue;
+
+               if (DEBUG)
+                       fprintf(stderr, "checkPerm: '%s' ? '%s'\n", dir_prefix, user_and_passwd);
+
+               /* If it's not a prefix match, continue searching */
+               len = strlen(dir_prefix);
+               if (len != 1 /* dir_prefix "/" matches all, don't need to check */
+                && (strncmp(dir_prefix, path, len) != 0
+                   || (path[len] != '/' && path[len] != '\0'))
+               ) {
+                       continue;
+               }
+
+               /* Path match found */
+               prev = dir_prefix;
+
+               if (ENABLE_FEATURE_HTTPD_AUTH_MD5) {
+                       char *md5_passwd;
+
+                       md5_passwd = strchr(cur->after_colon, ':');
+                       if (md5_passwd && md5_passwd[1] == '$' && md5_passwd[2] == '1'
+                        && md5_passwd[3] == '$' && md5_passwd[4]
+                       ) {
+                               char *encrypted;
+                               int r, user_len_p1;
+
+                               md5_passwd++;
+                               user_len_p1 = md5_passwd - cur->after_colon;
+                               /* comparing "user:" */
+                               if (strncmp(cur->after_colon, user_and_passwd, user_len_p1) != 0) {
+                                       continue;
+                               }
+
+                               encrypted = pw_encrypt(
+                                       user_and_passwd + user_len_p1 /* cleartext pwd from user */,
+                                       md5_passwd /*salt */, 1 /* cleanup */);
+                               r = strcmp(encrypted, md5_passwd);
+                               free(encrypted);
+                               if (r == 0)
+                                       goto set_remoteuser_var; /* Ok */
+                               continue;
+                       }
+               }
+
+               /* Comparing plaintext "user:pass" in one go */
+               if (strcmp(cur->after_colon, user_and_passwd) == 0) {
+ set_remoteuser_var:
+                       remoteuser = xstrndup(user_and_passwd,
+                                       strchrnul(user_and_passwd, ':') - user_and_passwd);
+                       return 1; /* Ok */
+               }
+       } /* for */
+
+       /* 0(bad) if prev is set: matches were found but passwd was wrong */
+       return (prev == NULL);
+}
+#endif  /* FEATURE_HTTPD_BASIC_AUTH */
+
+#if ENABLE_FEATURE_HTTPD_PROXY
+static Htaccess_Proxy *find_proxy_entry(const char *url)
+{
+       Htaccess_Proxy *p;
+       for (p = proxy; p; p = p->next) {
+               if (strncmp(url, p->url_from, strlen(p->url_from)) == 0)
+                       return p;
+       }
+       return NULL;
+}
+#endif
+
+/*
+ * Handle timeouts
+ */
+static void send_REQUEST_TIMEOUT_and_exit(int sig) NORETURN;
+static void send_REQUEST_TIMEOUT_and_exit(int sig UNUSED_PARAM)
+{
+       send_headers_and_exit(HTTP_REQUEST_TIMEOUT);
+}
+
+/*
+ * Handle an incoming http request and exit.
+ */
+static void handle_incoming_and_exit(const len_and_sockaddr *fromAddr) NORETURN;
+static void handle_incoming_and_exit(const len_and_sockaddr *fromAddr)
+{
+       static const char request_GET[] ALIGN1 = "GET";
+       struct stat sb;
+       char *urlcopy;
+       char *urlp;
+       char *tptr;
+#if ENABLE_FEATURE_HTTPD_CGI
+       static const char request_HEAD[] ALIGN1 = "HEAD";
+       const char *prequest;
+       char *cookie = NULL;
+       char *content_type = NULL;
+       unsigned long length = 0;
+#elif ENABLE_FEATURE_HTTPD_PROXY
+#define prequest request_GET
+       unsigned long length = 0;
+#endif
+#if ENABLE_FEATURE_HTTPD_BASIC_AUTH
+       smallint authorized = -1;
+#endif
+       smallint ip_allowed;
+       char http_major_version;
+#if ENABLE_FEATURE_HTTPD_PROXY
+       char http_minor_version;
+       char *header_buf = header_buf; /* for gcc */
+       char *header_ptr = header_ptr;
+       Htaccess_Proxy *proxy_entry;
+#endif
+
+       /* Allocation of iobuf is postponed until now
+        * (IOW, server process doesn't need to waste 8k) */
+       iobuf = xmalloc(IOBUF_SIZE);
+
+       rmt_ip = 0;
+       if (fromAddr->u.sa.sa_family == AF_INET) {
+               rmt_ip = ntohl(fromAddr->u.sin.sin_addr.s_addr);
+       }
+#if ENABLE_FEATURE_IPV6
+       if (fromAddr->u.sa.sa_family == AF_INET6
+        && fromAddr->u.sin6.sin6_addr.s6_addr32[0] == 0
+        && fromAddr->u.sin6.sin6_addr.s6_addr32[1] == 0
+        && ntohl(fromAddr->u.sin6.sin6_addr.s6_addr32[2]) == 0xffff)
+               rmt_ip = ntohl(fromAddr->u.sin6.sin6_addr.s6_addr32[3]);
+#endif
+       if (ENABLE_FEATURE_HTTPD_CGI || DEBUG || verbose) {
+               /* NB: can be NULL (user runs httpd -i by hand?) */
+               rmt_ip_str = xmalloc_sockaddr2dotted(&fromAddr->u.sa);
+       }
+       if (verbose) {
+               /* this trick makes -v logging much simpler */
+               if (rmt_ip_str)
+                       applet_name = rmt_ip_str;
+               if (verbose > 2)
+                       bb_error_msg("connected");
+       }
+
+       /* Install timeout handler. get_line() needs it. */
+       signal(SIGALRM, send_REQUEST_TIMEOUT_and_exit);
+
+       if (!get_line()) /* EOF or error or empty line */
+               send_headers_and_exit(HTTP_BAD_REQUEST);
+
+       /* Determine type of request (GET/POST) */
+       urlp = strpbrk(iobuf, " \t");
+       if (urlp == NULL)
+               send_headers_and_exit(HTTP_BAD_REQUEST);
+       *urlp++ = '\0';
+#if ENABLE_FEATURE_HTTPD_CGI
+       prequest = request_GET;
+       if (strcasecmp(iobuf, prequest) != 0) {
+               prequest = request_HEAD;
+               if (strcasecmp(iobuf, prequest) != 0) {
+                       prequest = "POST";
+                       if (strcasecmp(iobuf, prequest) != 0)
+                               send_headers_and_exit(HTTP_NOT_IMPLEMENTED);
+               }
+       }
+#else
+       if (strcasecmp(iobuf, request_GET) != 0)
+               send_headers_and_exit(HTTP_NOT_IMPLEMENTED);
+#endif
+       urlp = skip_whitespace(urlp);
+       if (urlp[0] != '/')
+               send_headers_and_exit(HTTP_BAD_REQUEST);
+
+       /* Find end of URL and parse HTTP version, if any */
+       http_major_version = '0';
+       USE_FEATURE_HTTPD_PROXY(http_minor_version = '0';)
+       tptr = strchrnul(urlp, ' ');
+       /* Is it " HTTP/"? */
+       if (tptr[0] && strncmp(tptr + 1, HTTP_200, 5) == 0) {
+               http_major_version = tptr[6];
+               USE_FEATURE_HTTPD_PROXY(http_minor_version = tptr[8];)
+       }
+       *tptr = '\0';
+
+       /* Copy URL from after "GET "/"POST " to stack-allocated char[] */
+       urlcopy = alloca((tptr - urlp) + 2 + strlen(index_page));
+       /*if (urlcopy == NULL)
+        *      send_headers_and_exit(HTTP_INTERNAL_SERVER_ERROR);*/
+       strcpy(urlcopy, urlp);
+       /* NB: urlcopy ptr is never changed after this */
+
+       /* Extract url args if present */
+       g_query = NULL;
+       tptr = strchr(urlcopy, '?');
+       if (tptr) {
+               *tptr++ = '\0';
+               g_query = tptr;
+       }
+
+       /* Decode URL escape sequences */
+       tptr = decodeString(urlcopy, 0);
+       if (tptr == NULL)
+               send_headers_and_exit(HTTP_BAD_REQUEST);
+       if (tptr == urlcopy + 1) {
+               /* '/' or NUL is encoded */
+               send_headers_and_exit(HTTP_NOT_FOUND);
+       }
+
+       /* Canonicalize path */
+       /* Algorithm stolen from libbb bb_simplify_path(),
+        * but don't strdup, retain trailing slash, protect root */
+       urlp = tptr = urlcopy;
+       do {
+               if (*urlp == '/') {
+                       /* skip duplicate (or initial) slash */
+                       if (*tptr == '/') {
+                               continue;
+                       }
+                       if (*tptr == '.') {
+                               /* skip extra "/./" */
+                               if (tptr[1] == '/' || !tptr[1]) {
+                                       continue;
+                               }
+                               /* "..": be careful */
+                               if (tptr[1] == '.' && (tptr[2] == '/' || !tptr[2])) {
+                                       ++tptr;
+                                       if (urlp == urlcopy) /* protect root */
+                                               send_headers_and_exit(HTTP_BAD_REQUEST);
+                                       while (*--urlp != '/') /* omit previous dir */;
+                                               continue;
+                               }
+                       }
+               }
+               *++urlp = *tptr;
+       } while (*++tptr);
+       *++urlp = '\0';       /* terminate after last character */
+
+       /* If URL is a directory, add '/' */
+       if (urlp[-1] != '/') {
+               if (is_directory(urlcopy + 1, 1, &sb)) {
+                       found_moved_temporarily = urlcopy;
+               }
+       }
+
+       /* Log it */
+       if (verbose > 1)
+               bb_error_msg("url:%s", urlcopy);
+
+       tptr = urlcopy;
+       ip_allowed = checkPermIP();
+       while (ip_allowed && (tptr = strchr(tptr + 1, '/')) != NULL) {
+               /* have path1/path2 */
+               *tptr = '\0';
+               if (is_directory(urlcopy + 1, 1, &sb)) {
+                       /* may have subdir config */
+                       parse_conf(urlcopy + 1, SUBDIR_PARSE);
+                       ip_allowed = checkPermIP();
+               }
+               *tptr = '/';
+       }
+
+#if ENABLE_FEATURE_HTTPD_PROXY
+       proxy_entry = find_proxy_entry(urlcopy);
+       if (proxy_entry)
+               header_buf = header_ptr = xmalloc(IOBUF_SIZE);
+#endif
+
+       if (http_major_version >= '0') {
+               /* Request was with "... HTTP/nXXX", and n >= 0 */
+
+               /* Read until blank line for HTTP version specified, else parse immediate */
+               while (1) {
+                       if (!get_line())
+                               break; /* EOF or error or empty line */
+                       if (DEBUG)
+                               bb_error_msg("header: '%s'", iobuf);
+
+#if ENABLE_FEATURE_HTTPD_PROXY
+                       /* We need 2 more bytes for yet another "\r\n" -
+                        * see near fdprintf(proxy_fd...) further below */
+                       if (proxy_entry && (header_ptr - header_buf) < IOBUF_SIZE - 2) {
+                               int len = strlen(iobuf);
+                               if (len > IOBUF_SIZE - (header_ptr - header_buf) - 4)
+                                       len = IOBUF_SIZE - (header_ptr - header_buf) - 4;
+                               memcpy(header_ptr, iobuf, len);
+                               header_ptr += len;
+                               header_ptr[0] = '\r';
+                               header_ptr[1] = '\n';
+                               header_ptr += 2;
+                       }
+#endif
+
+#if ENABLE_FEATURE_HTTPD_CGI || ENABLE_FEATURE_HTTPD_PROXY
+                       /* Try and do our best to parse more lines */
+                       if ((STRNCASECMP(iobuf, "Content-length:") == 0)) {
+                               /* extra read only for POST */
+                               if (prequest != request_GET
+#if ENABLE_FEATURE_HTTPD_CGI
+                                && prequest != request_HEAD
+#endif
+                               ) {
+                                       tptr = skip_whitespace(iobuf + sizeof("Content-length:") - 1);
+                                       if (!tptr[0])
+                                               send_headers_and_exit(HTTP_BAD_REQUEST);
+                                       /* not using strtoul: it ignores leading minus! */
+                                       length = bb_strtou(tptr, NULL, 10);
+                                       /* length is "ulong", but we need to pass it to int later */
+                                       if (errno || length > INT_MAX)
+                                               send_headers_and_exit(HTTP_BAD_REQUEST);
+                               }
+                       }
+#endif
+#if ENABLE_FEATURE_HTTPD_CGI
+                       else if (STRNCASECMP(iobuf, "Cookie:") == 0) {
+                               cookie = xstrdup(skip_whitespace(iobuf + sizeof("Cookie:")-1));
+                       } else if (STRNCASECMP(iobuf, "Content-Type:") == 0) {
+                               content_type = xstrdup(skip_whitespace(iobuf + sizeof("Content-Type:")-1));
+                       } else if (STRNCASECMP(iobuf, "Referer:") == 0) {
+                               referer = xstrdup(skip_whitespace(iobuf + sizeof("Referer:")-1));
+                       } else if (STRNCASECMP(iobuf, "User-Agent:") == 0) {
+                               user_agent = xstrdup(skip_whitespace(iobuf + sizeof("User-Agent:")-1));
+                       } else if (STRNCASECMP(iobuf, "Host:") == 0) {
+                               host = xstrdup(skip_whitespace(iobuf + sizeof("Host:")-1));
+                       } else if (STRNCASECMP(iobuf, "Accept:") == 0) {
+                               http_accept = xstrdup(skip_whitespace(iobuf + sizeof("Accept:")-1));
+                       } else if (STRNCASECMP(iobuf, "Accept-Language:") == 0) {
+                               http_accept_language = xstrdup(skip_whitespace(iobuf + sizeof("Accept-Language:")-1));
+                       }
+#endif
+#if ENABLE_FEATURE_HTTPD_BASIC_AUTH
+                       if (STRNCASECMP(iobuf, "Authorization:") == 0) {
+                               /* We only allow Basic credentials.
+                                * It shows up as "Authorization: Basic <user>:<passwd>" where
+                                * "<user>:<passwd>" is base64 encoded.
+                                */
+                               tptr = skip_whitespace(iobuf + sizeof("Authorization:")-1);
+                               if (STRNCASECMP(tptr, "Basic") != 0)
+                                       continue;
+                               tptr += sizeof("Basic")-1;
+                               /* decodeBase64() skips whitespace itself */
+                               decodeBase64(tptr);
+                               authorized = check_user_passwd(urlcopy, tptr);
+                       }
+#endif
+#if ENABLE_FEATURE_HTTPD_RANGES
+                       if (STRNCASECMP(iobuf, "Range:") == 0) {
+                               /* We know only bytes=NNN-[MMM] */
+                               char *s = skip_whitespace(iobuf + sizeof("Range:")-1);
+                               if (strncmp(s, "bytes=", 6) == 0) {
+                                       s += sizeof("bytes=")-1;
+                                       range_start = BB_STRTOOFF(s, &s, 10);
+                                       if (s[0] != '-' || range_start < 0) {
+                                               range_start = 0;
+                                       } else if (s[1]) {
+                                               range_end = BB_STRTOOFF(s+1, NULL, 10);
+                                               if (errno || range_end < range_start)
+                                                       range_start = 0;
+                                       }
+                               }
+                       }
+#endif
+               } /* while extra header reading */
+       }
+
+       /* We are done reading headers, disable peer timeout */
+       alarm(0);
+
+       if (strcmp(bb_basename(urlcopy), HTTPD_CONF) == 0 || !ip_allowed) {
+               /* protect listing [/path]/httpd.conf or IP deny */
+               send_headers_and_exit(HTTP_FORBIDDEN);
+       }
+
+#if ENABLE_FEATURE_HTTPD_BASIC_AUTH
+       /* Case: no "Authorization:" was seen, but page does require passwd.
+        * Check that with dummy user:pass */
+       if (authorized < 0)
+               authorized = check_user_passwd(urlcopy, ":");
+       if (!authorized)
+               send_headers_and_exit(HTTP_UNAUTHORIZED);
+#endif
+
+       if (found_moved_temporarily) {
+               send_headers_and_exit(HTTP_MOVED_TEMPORARILY);
+       }
+
+#if ENABLE_FEATURE_HTTPD_PROXY
+       if (proxy_entry != NULL) {
+               int proxy_fd;
+               len_and_sockaddr *lsa;
+
+               proxy_fd = socket(AF_INET, SOCK_STREAM, 0);
+               if (proxy_fd < 0)
+                       send_headers_and_exit(HTTP_INTERNAL_SERVER_ERROR);
+               lsa = host2sockaddr(proxy_entry->host_port, 80);
+               if (lsa == NULL)
+                       send_headers_and_exit(HTTP_INTERNAL_SERVER_ERROR);
+               if (connect(proxy_fd, &lsa->u.sa, lsa->len) < 0)
+                       send_headers_and_exit(HTTP_INTERNAL_SERVER_ERROR);
+               fdprintf(proxy_fd, "%s %s%s%s%s HTTP/%c.%c\r\n",
+                               prequest, /* GET or POST */
+                               proxy_entry->url_to, /* url part 1 */
+                               urlcopy + strlen(proxy_entry->url_from), /* url part 2 */
+                               (g_query ? "?" : ""), /* "?" (maybe) */
+                               (g_query ? g_query : ""), /* query string (maybe) */
+                               http_major_version, http_minor_version);
+               header_ptr[0] = '\r';
+               header_ptr[1] = '\n';
+               header_ptr += 2;
+               write(proxy_fd, header_buf, header_ptr - header_buf);
+               free(header_buf); /* on the order of 8k, free it */
+               /* cgi_io_loop_and_exit needs to have two distinct fds */
+               cgi_io_loop_and_exit(proxy_fd, dup(proxy_fd), length);
+       }
+#endif
+
+       tptr = urlcopy + 1;      /* skip first '/' */
+
+#if ENABLE_FEATURE_HTTPD_CGI
+       if (strncmp(tptr, "cgi-bin/", 8) == 0) {
+               if (tptr[8] == '\0') {
+                       /* protect listing "cgi-bin/" */
+                       send_headers_and_exit(HTTP_FORBIDDEN);
+               }
+               send_cgi_and_exit(urlcopy, prequest, length, cookie, content_type);
+       }
+#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR
+       {
+               char *suffix = strrchr(tptr, '.');
+               if (suffix) {
+                       Htaccess *cur;
+                       for (cur = script_i; cur; cur = cur->next) {
+                               if (strcmp(cur->before_colon + 1, suffix) == 0) {
+                                       send_cgi_and_exit(urlcopy, prequest, length, cookie, content_type);
+                               }
+                       }
+               }
+       }
+#endif
+       if (prequest != request_GET && prequest != request_HEAD) {
+               send_headers_and_exit(HTTP_NOT_IMPLEMENTED);
+       }
+#endif  /* FEATURE_HTTPD_CGI */
+
+       if (urlp[-1] == '/')
+               strcpy(urlp, index_page);
+       if (stat(tptr, &sb) == 0) {
+               file_size = sb.st_size;
+               last_mod = sb.st_mtime;
+       }
+#if ENABLE_FEATURE_HTTPD_CGI
+       else if (urlp[-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) {
+                       urlp[0] = '\0';
+                       g_query = urlcopy;
+                       send_cgi_and_exit("/cgi-bin/index.cgi", prequest, length, cookie, content_type);
+               }
+       }
+#endif
+       /* else {
+        *      fall through to send_file, it errors out if open fails
+        * }
+        */
+
+       send_file_and_exit(tptr,
+#if ENABLE_FEATURE_HTTPD_CGI
+               (prequest != request_HEAD ? SEND_HEADERS_AND_BODY : SEND_HEADERS)
+#else
+               SEND_HEADERS_AND_BODY
+#endif
+       );
+}
+
+/*
+ * The main http server function.
+ * Given a socket, listen for new connections and farm out
+ * the processing as a [v]forked process.
+ * Never returns.
+ */
+#if BB_MMU
+static void mini_httpd(int server_socket) NORETURN;
+static void mini_httpd(int server_socket)
+{
+       /* NB: it's best to not use xfuncs in this loop before fork().
+        * Otherwise server may die on transient errors (temporary
+        * out-of-memory condition, etc), which is Bad(tm).
+        * Try to do any dangerous calls after fork.
+        */
+       while (1) {
+               int n;
+               len_and_sockaddr fromAddr;
+
+               /* Wait for connections... */
+               fromAddr.len = LSA_SIZEOF_SA;
+               n = accept(server_socket, &fromAddr.u.sa, &fromAddr.len);
+
+               if (n < 0)
+                       continue;
+               /* set the KEEPALIVE option to cull dead connections */
+               setsockopt(n, SOL_SOCKET, SO_KEEPALIVE, &const_int_1, sizeof(const_int_1));
+
+               if (fork() == 0) {
+                       /* child */
+                       /* Do not reload config on HUP */
+                       signal(SIGHUP, SIG_IGN);
+                       close(server_socket);
+                       xmove_fd(n, 0);
+                       xdup2(0, 1);
+
+                       handle_incoming_and_exit(&fromAddr);
+               }
+               /* parent, or fork failed */
+               close(n);
+       } /* while (1) */
+       /* never reached */
+}
+#else
+static void mini_httpd_nommu(int server_socket, int argc, char **argv) NORETURN;
+static void mini_httpd_nommu(int server_socket, int argc, char **argv)
+{
+       char *argv_copy[argc + 2];
+
+       argv_copy[0] = argv[0];
+       argv_copy[1] = (char*)"-i";
+       memcpy(&argv_copy[2], &argv[1], argc * sizeof(argv[0]));
+
+       /* NB: it's best to not use xfuncs in this loop before vfork().
+        * Otherwise server may die on transient errors (temporary
+        * out-of-memory condition, etc), which is Bad(tm).
+        * Try to do any dangerous calls after fork.
+        */
+       while (1) {
+               int n;
+               len_and_sockaddr fromAddr;
+
+               /* Wait for connections... */
+               fromAddr.len = LSA_SIZEOF_SA;
+               n = accept(server_socket, &fromAddr.u.sa, &fromAddr.len);
+
+               if (n < 0)
+                       continue;
+               /* set the KEEPALIVE option to cull dead connections */
+               setsockopt(n, SOL_SOCKET, SO_KEEPALIVE, &const_int_1, sizeof(const_int_1));
+
+               if (vfork() == 0) {
+                       /* child */
+                       /* Do not reload config on HUP */
+                       signal(SIGHUP, SIG_IGN);
+                       close(server_socket);
+                       xmove_fd(n, 0);
+                       xdup2(0, 1);
+
+                       /* Run a copy of ourself in inetd mode */
+                       re_exec(argv_copy);
+               }
+               /* parent, or vfork failed */
+               close(n);
+       } /* while (1) */
+       /* never reached */
+}
+#endif
+
+/*
+ * Process a HTTP connection on stdin/out.
+ * Never returns.
+ */
+static void mini_httpd_inetd(void) NORETURN;
+static void mini_httpd_inetd(void)
+{
+       len_and_sockaddr fromAddr;
+
+       memset(&fromAddr, 0, sizeof(fromAddr));
+       fromAddr.len = LSA_SIZEOF_SA;
+       /* NB: can fail if user runs it by hand and types in http cmds */
+       getpeername(0, &fromAddr.u.sa, &fromAddr.len);
+       handle_incoming_and_exit(&fromAddr);
+}
+
+static void sighup_handler(int sig UNUSED_PARAM)
+{
+       parse_conf(DEFAULT_PATH_HTTPD_CONF, SIGNALED_PARSE);
+}
+
+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,
+       p_opt_verbose   ,
+       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,
+       OPT_VERBOSE     = 1 << p_opt_verbose,
+};
+
+
+int httpd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int httpd_main(int argc UNUSED_PARAM, char **argv)
+{
+       int server_socket = server_socket; /* for gcc */
+       unsigned opt;
+       char *url_for_decode;
+       USE_FEATURE_HTTPD_ENCODE_URL_STR(const char *url_for_encode;)
+       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;)
+
+       INIT_G();
+
+#if ENABLE_LOCALE_SUPPORT
+       /* Undo busybox.c: we want to speak English in http (dates etc) */
+       setlocale(LC_TIME, "C");
+#endif
+
+       home_httpd = xrealloc_getcwd_or_warn(NULL);
+       /* -v counts, -i implies -f */
+       opt_complementary = "vv:if";
+       /* We do not "absolutize" path given by -h (home) opt.
+        * If user gives relative path in -h,
+        * $SCRIPT_FILENAME will not be set. */
+       opt = getopt32(argv, "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:ifv",
+                       &opt_c_configFile, &url_for_decode, &home_httpd
+                       USE_FEATURE_HTTPD_ENCODE_URL_STR(, &url_for_encode)
+                       USE_FEATURE_HTTPD_BASIC_AUTH(, &g_realm)
+                       USE_FEATURE_HTTPD_AUTH_MD5(, &pass)
+                       USE_FEATURE_HTTPD_SETUID(, &s_ugid)
+                       , &bind_addr_or_port
+                       , &verbose
+               );
+       if (opt & OPT_DECODE_URL) {
+               fputs(decodeString(url_for_decode, 1), stdout);
+               return 0;
+       }
+#if ENABLE_FEATURE_HTTPD_ENCODE_URL_STR
+       if (opt & OPT_ENCODE_URL) {
+               fputs(encodeString(url_for_encode), stdout);
+               return 0;
+       }
+#endif
+#if ENABLE_FEATURE_HTTPD_AUTH_MD5
+       if (opt & OPT_MD5) {
+               puts(pw_encrypt(pass, "$1$", 1));
+               return 0;
+       }
+#endif
+#if ENABLE_FEATURE_HTTPD_SETUID
+       if (opt & OPT_SETUID) {
+               xget_uidgid(&ugid, s_ugid);
+       }
+#endif
+
+#if !BB_MMU
+       if (!(opt & OPT_FOREGROUND)) {
+               bb_daemonize_or_rexec(0, argv); /* don't change current directory */
+       }
+#endif
+
+       xchdir(home_httpd);
+       if (!(opt & OPT_INETD)) {
+               signal(SIGCHLD, SIG_IGN);
+               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 0
+       /* User can do it himself: 'env - PATH="$PATH" httpd'
+        * We don't do it because we don't want to screw users
+        * which want to do
+        * 'env - VAR1=val1 VAR2=val2 httpd'
+        * and have VAR1 and VAR2 values visible in their CGIs.
+        * Besides, it is also smaller. */
+       {
+               char *p = getenv("PATH");
+               /* env strings themself are not freed, no need to xstrdup(p): */
+               clearenv();
+               if (p)
+                       putenv(p - 5);
+//             if (!(opt & OPT_INETD))
+//                     setenv_long("SERVER_PORT", ???);
+       }
+#endif
+
+       parse_conf(DEFAULT_PATH_HTTPD_CONF, FIRST_PARSE);
+       if (!(opt & OPT_INETD))
+               signal(SIGHUP, sighup_handler);
+
+       xfunc_error_retval = 0;
+       if (opt & OPT_INETD)
+               mini_httpd_inetd();
+#if BB_MMU
+       if (!(opt & OPT_FOREGROUND))
+               bb_daemonize(0); /* don't change current directory */
+       mini_httpd(server_socket); /* never returns */
+#else
+       mini_httpd_nommu(server_socket, argc, argv); /* never returns */
+#endif
+       /* return 0; */
+}
diff --git a/networking/httpd_indexcgi.c b/networking/httpd_indexcgi.c
new file mode 100644 (file)
index 0000000..94c6a69
--- /dev/null
@@ -0,0 +1,342 @@
+/*
+ * Copyright (c) 2007 Denys Vlasenko <vda.linux@googlemail.com>
+ *
+ * Licensed under GPLv2, see file LICENSE in this tarball for details.
+ */
+
+/*
+ * This program is a CGI application. It outputs directory index page.
+ * Put it into cgi-bin/index.cgi and chmod 0755.
+ */
+
+/* Build a-la
+i486-linux-uclibc-gcc \
+-static -static-libgcc \
+-D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 \
+-Wall -Wshadow -Wwrite-strings -Wundef -Wstrict-prototypes -Werror \
+-Wold-style-definition -Wdeclaration-after-statement -Wno-pointer-sign \
+-Wmissing-prototypes -Wmissing-declarations \
+-Os -fno-builtin-strlen -finline-limit=0 -fomit-frame-pointer \
+-ffunction-sections -fdata-sections -fno-guess-branch-probability \
+-funsigned-char \
+-falign-functions=1 -falign-jumps=1 -falign-labels=1 -falign-loops=1 \
+-march=i386 -mpreferred-stack-boundary=2 \
+-Wl,-Map -Wl,link.map -Wl,--warn-common -Wl,--sort-common -Wl,--gc-sections \
+httpd_indexcgi.c -o index.cgi
+*/
+
+/* We don't use printf, as it pulls in >12 kb of code from uclibc (i386). */
+/* Currently malloc machinery is the biggest part of libc we pull in. */
+/* We have only one realloc and one strdup, any idea how to do without? */
+/* Size (i386, approximate):
+ *   text    data     bss     dec     hex filename
+ *  13036      44    3052   16132    3f04 index.cgi
+ *   2576       4    2048    4628    1214 index.cgi.o
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <dirent.h>
+#include <time.h>
+
+/* Appearance of the table is controlled by style sheet *ONLY*,
+ * formatting code uses <TAG class=CLASS> to apply style
+ * to elements. Edit stylesheet to your liking and recompile. */
+
+#define STYLE_STR \
+"<style>"                                               "\n"\
+"table {"                                               "\n"\
+  "width:100%;"                                         "\n"\
+  "background-color:#fff5ee;"                           "\n"\
+  "border-width:1px;" /* 1px 1px 1px 1px; */            "\n"\
+  "border-spacing:2px;"                                 "\n"\
+  "border-style:solid;" /* solid solid solid solid; */  "\n"\
+  "border-color:black;" /* black black black black; */  "\n"\
+  "border-collapse:collapse;"                           "\n"\
+"}"                                                     "\n"\
+"th {"                                                  "\n"\
+  "border-width:1px;" /* 1px 1px 1px 1px; */            "\n"\
+  "padding:1px;" /* 1px 1px 1px 1px; */                 "\n"\
+  "border-style:solid;" /* solid solid solid solid; */  "\n"\
+  "border-color:black;" /* black black black black; */  "\n"\
+"}"                                                     "\n"\
+"td {"                                                  "\n"\
+             /* top right bottom left */                    \
+  "border-width:0px 1px 0px 1px;"                       "\n"\
+  "padding:1px;" /* 1px 1px 1px 1px; */                 "\n"\
+  "border-style:solid;" /* solid solid solid solid; */  "\n"\
+  "border-color:black;" /* black black black black; */  "\n"\
+  "white-space:nowrap;"                                 "\n"\
+"}"                                                     "\n"\
+"tr.hdr { background-color:#eee5de; }"                  "\n"\
+"tr.o { background-color:#ffffff; }"                    "\n"\
+/* tr.e { ... } - for even rows (currently none) */         \
+"tr.foot { background-color:#eee5de; }"                 "\n"\
+"th.cnt { text-align:left; }"                           "\n"\
+"th.sz { text-align:right; }"                           "\n"\
+"th.dt { text-align:right; }"                           "\n"\
+"td.sz { text-align:right; }"                           "\n"\
+"td.dt { text-align:right; }"                           "\n"\
+"col.nm { width:98%; }"                                 "\n"\
+"col.sz { width:1%; }"                                  "\n"\
+"col.dt { width:1%; }"                                  "\n"\
+"</style>"                                              "\n"\
+
+typedef struct dir_list_t {
+       char  *dl_name;
+       mode_t dl_mode;
+       off_t  dl_size;
+       time_t dl_mtime;
+} dir_list_t;
+
+static int compare_dl(dir_list_t *a, dir_list_t *b)
+{
+       /* ".." is 'less than' any other dir entry */
+       if (strcmp(a->dl_name, "..") == 0) {
+               return -1;
+       }
+       if (strcmp(b->dl_name, "..") == 0) {
+               return 1;
+       }
+       if (S_ISDIR(a->dl_mode) != S_ISDIR(b->dl_mode)) {
+               /* 1 if b is a dir (and thus a is 'after' b, a > b),
+                * else -1 (a < b) */
+               return (S_ISDIR(b->dl_mode) != 0) ? 1 : -1;
+       }
+       return strcmp(a->dl_name, b->dl_name);
+}
+
+static char buffer[2*1024 > sizeof(STYLE_STR) ? 2*1024 : sizeof(STYLE_STR)];
+static char *dst = buffer;
+enum {
+       BUFFER_SIZE = sizeof(buffer),
+       HEADROOM = 64,
+};
+
+/* After this call, you have at least size + HEADROOM bytes available
+ * ahead of dst */
+static void guarantee(int size)
+{
+       if (buffer + (BUFFER_SIZE-HEADROOM) - dst >= size)
+               return;
+       write(STDOUT_FILENO, buffer, dst - buffer);
+       dst = buffer;
+}
+
+/* NB: formatters do not store terminating NUL! */
+
+/* HEADROOM bytes are available after dst after this call */
+static void fmt_str(/*char *dst,*/ const char *src)
+{
+       unsigned len = strlen(src);
+       guarantee(len);
+       memcpy(dst, src, len);
+       dst += len;
+}
+
+/* HEADROOM bytes after dst are available after this call */
+static void fmt_url(/*char *dst,*/ const char *name)
+{
+       while (*name) {
+               unsigned c = *name++;
+               guarantee(3);
+               *dst = c;
+               if ((c - '0') > 9 /* not a digit */
+                && ((c|0x20) - 'a') > 26 /* not A-Z or a-z */
+                && !strchr("._-+@", c)
+               ) {
+                       *dst++ = '%';
+                       *dst++ = "0123456789ABCDEF"[c >> 4];
+                       *dst = "0123456789ABCDEF"[c & 0xf];
+               }
+               dst++;
+       }
+}
+
+/* HEADROOM bytes are available after dst after this call */
+static void fmt_html(/*char *dst,*/ const char *name)
+{
+       while (*name) {
+               char c = *name++;
+               if (c == '<')
+                       fmt_str("&lt;");
+               else if (c == '>')
+                       fmt_str("&gt;");
+               else if (c == '&') {
+                       fmt_str("&amp;");
+               } else {
+                       guarantee(1);
+                       *dst++ = c;
+                       continue;
+               }
+       }
+}
+
+/* HEADROOM bytes are available after dst after this call */
+static void fmt_ull(/*char *dst,*/ unsigned long long n)
+{
+       char buf[sizeof(n)*3 + 2];
+       char *p;
+
+       p = buf + sizeof(buf) - 1;
+       *p = '\0';
+       do {
+               *--p = (n % 10) + '0';
+               n /= 10;
+       } while (n);
+       fmt_str(/*dst,*/ p);
+}
+
+/* Does not call guarantee - eats into headroom instead */
+static void fmt_02u(/*char *dst,*/ unsigned n)
+{
+       /* n %= 100; - not needed, callers don't pass big n */
+       dst[0] = (n / 10) + '0';
+       dst[1] = (n % 10) + '0';
+       dst += 2;
+}
+
+/* Does not call guarantee - eats into headroom instead */
+static void fmt_04u(/*char *dst,*/ unsigned n)
+{
+       /* n %= 10000; - not needed, callers don't pass big n */
+       fmt_02u(n / 100);
+       fmt_02u(n % 100);
+}
+
+int main(void)
+{
+       dir_list_t *dir_list;
+       dir_list_t *cdir;
+       unsigned dir_list_count;
+       unsigned count_dirs;
+       unsigned count_files;
+       unsigned long long size_total;
+       int odd;
+       DIR *dirp;
+       char *QUERY_STRING;
+
+       QUERY_STRING = getenv("QUERY_STRING");
+       if (!QUERY_STRING
+        || QUERY_STRING[0] != '/'
+        || strstr(QUERY_STRING, "/../")
+        || strcmp(strrchr(QUERY_STRING, '/'), "/..") == 0
+       ) {
+               return 1;
+       }
+
+       if (chdir("..")
+        || (QUERY_STRING[1] && chdir(QUERY_STRING + 1))
+       ) {
+               return 1;
+       }
+
+       dirp = opendir(".");
+       if (!dirp)
+               return 1;
+       dir_list = NULL;
+       dir_list_count = 0;
+       while (1) {
+               struct dirent *dp;
+               struct stat sb;
+
+               dp = readdir(dirp);
+               if (!dp)
+                       break;
+               if (dp->d_name[0] == '.' && !dp->d_name[1])
+                       continue;
+               if (stat(dp->d_name, &sb) != 0)
+                       continue;
+               dir_list = realloc(dir_list, (dir_list_count + 1) * sizeof(dir_list[0]));
+               dir_list[dir_list_count].dl_name = strdup(dp->d_name);
+               dir_list[dir_list_count].dl_mode = sb.st_mode;
+               dir_list[dir_list_count].dl_size = sb.st_size;
+               dir_list[dir_list_count].dl_mtime = sb.st_mtime;
+               dir_list_count++;
+       }
+       closedir(dirp);
+
+       qsort(dir_list, dir_list_count, sizeof(dir_list[0]), (void*)compare_dl);
+
+       fmt_str(
+               "" /* Additional headers (currently none) */
+               "\r\n" /* Mandatory empty line after headers */
+               "<html><head><title>Index of ");
+       /* Guard against directories with &, > etc */
+       fmt_html(QUERY_STRING);
+       fmt_str(
+               "</title>\n"
+               STYLE_STR
+               "</head>" "\n"
+               "<body>" "\n"
+               "<h1>Index of ");
+       fmt_html(QUERY_STRING);
+       fmt_str(
+               "</h1>" "\n"
+               "<table>" "\n"
+               "<col class=nm><col class=sz><col class=dt>" "\n"
+               "<tr class=hdr><th class=cnt>Name<th class=sz>Size<th class=dt>Last modified" "\n");
+
+       odd = 0;
+       count_dirs = 0;
+       count_files = 0;
+       size_total = 0;
+       cdir = dir_list;
+       while (dir_list_count--) {
+               struct tm *tm;
+
+               if (S_ISDIR(cdir->dl_mode)) {
+                       count_dirs++;
+               } else if (S_ISREG(cdir->dl_mode)) {
+                       count_files++;
+                       size_total += cdir->dl_size;
+               } else
+                       goto next;
+
+               fmt_str("<tr class=");
+               *dst++ = (odd ? 'o' : 'e');
+               fmt_str("><td class=nm><a href='");
+               fmt_url(cdir->dl_name); /* %20 etc */
+               if (S_ISDIR(cdir->dl_mode))
+                       *dst++ = '/';
+               fmt_str("'>");
+               fmt_html(cdir->dl_name); /* &lt; etc */
+               if (S_ISDIR(cdir->dl_mode))
+                       *dst++ = '/';
+               fmt_str("</a><td class=sz>");
+               if (S_ISREG(cdir->dl_mode))
+                       fmt_ull(cdir->dl_size);
+               fmt_str("<td class=dt>");
+               tm = gmtime(&cdir->dl_mtime);
+               fmt_04u(1900 + tm->tm_year); *dst++ = '-';
+               fmt_02u(tm->tm_mon + 1); *dst++ = '-';
+               fmt_02u(tm->tm_mday); *dst++ = ' ';
+               fmt_02u(tm->tm_hour); *dst++ = ':';
+               fmt_02u(tm->tm_min); *dst++ = ':';
+               fmt_02u(tm->tm_sec);
+               *dst++ = '\n';
+
+               odd = 1 - odd;
+ next:
+               cdir++;
+       }
+
+       fmt_str("<tr class=foot><th class=cnt>Files: ");
+       fmt_ull(count_files);
+       /* count_dirs - 1: we don't want to count ".." */
+       fmt_str(", directories: ");
+       fmt_ull(count_dirs - 1);
+       fmt_str("<th class=sz>");
+       fmt_ull(size_total);
+       fmt_str("<th class=dt>\n");
+       /* "</table></body></html>" - why bother? */
+       guarantee(BUFFER_SIZE * 2); /* flush */
+
+       return 0;
+}
diff --git a/networking/httpd_post_upload.txt b/networking/httpd_post_upload.txt
new file mode 100644 (file)
index 0000000..a53b114
--- /dev/null
@@ -0,0 +1,76 @@
+POST upload example:
+
+post_upload.htm
+===============
+<html>
+<body>
+<form action=/cgi-bin/post_upload.cgi method=post enctype=multipart/form-data>
+File to upload: <input type=file name=file1> <input type=submit>
+</form>
+
+
+post_upload.cgi
+===============
+#!/bin/sh
+
+# POST upload format:
+# -----------------------------29995809218093749221856446032^M
+# Content-Disposition: form-data; name="file1"; filename="..."^M
+# Content-Type: application/octet-stream^M
+# ^M    <--------- headers end with empty line
+# file contents
+# file contents
+# file contents
+# ^M    <--------- extra empty line
+# -----------------------------29995809218093749221856446032--^M
+
+# Beware: bashism $'\r' is used to handle ^M
+
+file=/tmp/$$-$RANDOM
+
+# CGI output must start with at least empty line (or headers)
+printf '\r\n'
+
+IFS=$'\r'
+read -r delim_line
+
+IFS=''
+delim_line="${delim_line}--"$'\r'
+
+while read -r line; do
+    test "$line" = '' && break
+    test "$line" = $'\r' && break
+done
+
+# This will not work well for binary files: bash 3.2 is upset
+# by reading NUL bytes and loses chunks of data.
+# If you are not bothered by having junk appended to the uploaded file,
+# consider using simple "cat >file" instead of the entire
+# fragment below.
+
+while read -r line; do
+
+    while test "$line" = $'\r'; do
+       read -r line
+       test "$line" = "$delim_line" && {
+           # Aha! Empty line + delimiter! All done
+           cat <<EOF
+<html>
+<body>
+File upload has been accepted
+EOF
+           exit 0
+       }
+       # Empty line + NOT delimiter. Save empty line,
+       # and go check next line
+       printf "%s\n" $'\r' -vC >&3
+    done
+    # Not empty line - just save
+    printf "%s\n" "$line" -vC >&3
+done 3>"$file"
+
+cat <<EOF
+<html>
+<body>
+File upload was not terminated with '$delim_line' - ??!
+EOF
diff --git a/networking/ifconfig.c b/networking/ifconfig.c
new file mode 100644 (file)
index 0000000..22b1682
--- /dev/null
@@ -0,0 +1,540 @@
+/* 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, <waltje@uwalt.nl.mugnet.org>
+ *
+ * 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 <magick@linux-fan.com>
+ */
+
+#include <net/if.h>
+#include <net/if_arp.h>
+#include <netinet/in.h>
+#if defined(__GLIBC__) && __GLIBC__ >=2 && __GLIBC_MINOR__ >= 1
+#include <netpacket/packet.h>
+#include <net/ethernet.h>
+#else
+#include <sys/types.h>
+#include <netinet/if_ether.h>
+#endif
+#include "inet_common.h"
+#include "libbb.h"
+
+#if ENABLE_FEATURE_IFCONFIG_SLIP
+# include <net/if_slip.h>
+#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;
+       unsigned short 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[] = {
+       { "SIFMETRIC",  SIOCSIFMETRIC,  ifreq_offsetof(ifr_metric) },
+       { "SIFMTU",     SIOCSIFMTU,     ifreq_offsetof(ifr_mtu) },
+       { "SIFTXQLEN",  SIOCSIFTXQLEN,  ifreq_offsetof(ifr_qlen) },
+       { "SIFDSTADDR", SIOCSIFDSTADDR, ifreq_offsetof(ifr_dstaddr) },
+       { "SIFNETMASK", SIOCSIFNETMASK, ifreq_offsetof(ifr_netmask) },
+       { "SIFBRDADDR", SIOCSIFBRDADDR, ifreq_offsetof(ifr_broadaddr) },
+#if ENABLE_FEATURE_IFCONFIG_HW
+       { "SIFHWADDR",  SIOCSIFHWADDR,  ifreq_offsetof(ifr_hwaddr) },
+#endif
+       { "SIFDSTADDR", SIOCSIFDSTADDR, ifreq_offsetof(ifr_dstaddr) },
+#ifdef SIOCSKEEPALIVE
+       { "SKEEPALIVE", SIOCSKEEPALIVE, ifreq_offsetof(ifr_data) },
+#endif
+#ifdef SIOCSOUTFILL
+       { "SOUTFILL",   SIOCSOUTFILL,   ifreq_offsetof(ifr_data) },
+#endif
+#if ENABLE_FEATURE_IFCONFIG_MEMSTART_IOADDR_IRQ
+       { "SIFMAP",     SIOCSIFMAP,     ifreq_offsetof(ifr_map.mem_start) },
+       { "SIFMAP",     SIOCSIFMAP,     ifreq_offsetof(ifr_map.base_addr) },
+       { "SIFMAP",     SIOCSIFMAP,     ifreq_offsetof(ifr_map.irq) },
+#endif
+       /* Last entry if for unmatched (possibly hostname) arg. */
+#if ENABLE_FEATURE_IPV6
+       { "SIFADDR",    SIOCSIFADDR,    ifreq_offsetof(ifr_addr) }, /* IPv6 version ignores the offset */
+       { "DIFADDR",    SIOCDIFADDR,    ifreq_offsetof(ifr_addr) }, /* IPv6 version ignores the offset */
+#endif
+       { "SIFADDR",    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
+
+/*
+ * Our main function.
+ */
+int ifconfig_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int ifconfig_main(int argc, char **argv)
+{
+       struct ifreq ifr;
+       struct sockaddr_in sai;
+#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 */
+       strncpy_IFNAMSIZ(ifr.ifr_name, *argv);
+
+       /* 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 + ARRAY_SIZE(Arg1Opt) - 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
+                                               else {
+                                                       len_and_sockaddr *lsa;
+                                                       if (strcmp(host, "inet") == 0)
+                                                               continue; /* compat stuff */
+                                                       lsa = xhost2sockaddr(host, 0);
+#if ENABLE_FEATURE_IPV6
+                                                       if (lsa->u.sa.sa_family == AF_INET6) {
+                                                               int sockfd6;
+                                                               struct in6_ifreq ifr6;
+
+                                                               memcpy((char *) &ifr6.ifr6_addr,
+                                                                               (char *) &(lsa->u.sin6.sin6_addr),
+                                                                               sizeof(struct in6_addr));
+
+                                                               /* Create a channel to the NET kernel. */
+                                                               sockfd6 = xsocket(AF_INET6, SOCK_DGRAM, 0);
+                                                               xioctl(sockfd6, SIOGIFINDEX, &ifr);
+                                                               ifr6.ifr6_ifindex = ifr.ifr_ifindex;
+                                                               ifr6.ifr6_prefixlen = prefix_len;
+                                                               ioctl_or_perror_and_die(sockfd6, a1op->selector, &ifr6, "SIOC%s", a1op->name);
+                                                               if (ENABLE_FEATURE_CLEAN_UP)
+                                                                       free(lsa);
+                                                               continue;
+                                                       }
+#endif
+                                                       sai.sin_addr = lsa->u.sin.sin_addr;
+                                                       if (ENABLE_FEATURE_CLEAN_UP)
+                                                               free(lsa);
+                                               }
+#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. */
+                                               smalluint hw_class= index_in_substrings("ether\0"
+                                                               USE_FEATURE_HWIB("infiniband\0"), *argv) + 1;
+                                               if (!hw_class || !*++argv)
+                                                       bb_show_usage();
+                                               /*safe_strncpy(host, *argv, sizeof(host));*/
+                                               host = *argv;
+                                               if (hw_class == 1 ? in_ether(host, &sa) : in_ib(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) {
+                                               xioctl(sockfd, SIOCGIFMAP, &ifr);
+                                               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;
+                               }
+
+                               ioctl_or_perror_and_die(sockfd, a1op->selector, &ifr, "SIOC%s", 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;
+               }
+
+               xioctl(sockfd, SIOCGIFFLAGS, &ifr);
+               selector = op->selector;
+               if (mask & SET_MASK)
+                       ifr.ifr_flags |= selector;
+               else
+                       ifr.ifr_flags &= ~selector;
+               xioctl(sockfd, SIOCSIFFLAGS, &ifr);
+       } /* 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 = (char *) 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/ifenslave.c b/networking/ifenslave.c
new file mode 100644 (file)
index 0000000..fa22642
--- /dev/null
@@ -0,0 +1,592 @@
+/* Mode: C;
+ *
+ * Mini ifenslave implementation for busybox
+ * Copyright (C) 2005 by Marc Leeman <marc.leeman@barco.com>
+ *
+ * ifenslave.c: Configure network interfaces for parallel routing.
+ *
+ *      This program controls the Linux implementation of running multiple
+ *      network interfaces in parallel.
+ *
+ * Author:      Donald Becker <becker@cesdis.gsfc.nasa.gov>
+ *              Copyright 1994-1996 Donald Becker
+ *
+ *              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.
+ *
+ *      The author may be reached as becker@CESDIS.gsfc.nasa.gov, or C/O
+ *      Center of Excellence in Space Data and Information Sciences
+ *         Code 930.5, Goddard Space Flight Center, Greenbelt MD 20771
+ *
+ *  Changes :
+ *    - 2000/10/02 Willy Tarreau <willy at meta-x.org> :
+ *       - few fixes. Master's MAC address is now correctly taken from
+ *         the first device when not previously set ;
+ *       - detach support : call BOND_RELEASE to detach an enslaved interface.
+ *       - give a mini-howto from command-line help : # ifenslave -h
+ *
+ *    - 2001/02/16 Chad N. Tindel <ctindel at ieee dot org> :
+ *       - Master is now brought down before setting the MAC address.  In
+ *         the 2.4 kernel you can't change the MAC address while the device is
+ *         up because you get EBUSY.
+ *
+ *    - 2001/09/13 Takao Indoh <indou dot takao at jp dot fujitsu dot com>
+ *       - Added the ability to change the active interface on a mode 1 bond
+ *         at runtime.
+ *
+ *    - 2001/10/23 Chad N. Tindel <ctindel at ieee dot org> :
+ *       - No longer set the MAC address of the master.  The bond device will
+ *         take care of this itself
+ *       - Try the SIOC*** versions of the bonding ioctls before using the
+ *         old versions
+ *    - 2002/02/18 Erik Habbinga <erik_habbinga @ hp dot com> :
+ *       - ifr2.ifr_flags was not initialized in the hwaddr_notset case,
+ *         SIOCGIFFLAGS now called before hwaddr_notset test
+ *
+ *    - 2002/10/31 Tony Cureington <tony.cureington * hp_com> :
+ *       - If the master does not have a hardware address when the first slave
+ *         is enslaved, the master is assigned the hardware address of that
+ *         slave - there is a comment in bonding.c stating "ifenslave takes
+ *         care of this now." This corrects the problem of slaves having
+ *         different hardware addresses in active-backup mode when
+ *         multiple interfaces are specified on a single ifenslave command
+ *         (ifenslave bond0 eth0 eth1).
+ *
+ *    - 2003/03/18 - Tsippy Mendelson <tsippy.mendelson at intel dot com> and
+ *                   Shmulik Hen <shmulik.hen at intel dot com>
+ *       - Moved setting the slave's mac address and openning it, from
+ *         the application to the driver. This enables support of modes
+ *         that need to use the unique mac address of each slave.
+ *         The driver also takes care of closing the slave and restoring its
+ *         original mac address upon release.
+ *         In addition, block possibility of enslaving before the master is up.
+ *         This prevents putting the system in an undefined state.
+ *
+ *    - 2003/05/01 - Amir Noam <amir.noam at intel dot com>
+ *       - Added ABI version control to restore compatibility between
+ *         new/old ifenslave and new/old bonding.
+ *       - Prevent adding an adapter that is already a slave.
+ *         Fixes the problem of stalling the transmission and leaving
+ *         the slave in a down state.
+ *
+ *    - 2003/05/01 - Shmulik Hen <shmulik.hen at intel dot com>
+ *       - Prevent enslaving if the bond device is down.
+ *         Fixes the problem of leaving the system in unstable state and
+ *         halting when trying to remove the module.
+ *       - Close socket on all abnormal exists.
+ *       - Add versioning scheme that follows that of the bonding driver.
+ *         current version is 1.0.0 as a base line.
+ *
+ *    - 2003/05/22 - Jay Vosburgh <fubar at us dot ibm dot com>
+ *       - ifenslave -c was broken; it's now fixed
+ *       - Fixed problem with routes vanishing from master during enslave
+ *         processing.
+ *
+ *    - 2003/05/27 - Amir Noam <amir.noam at intel dot com>
+ *       - Fix backward compatibility issues:
+ *         For drivers not using ABI versions, slave was set down while
+ *         it should be left up before enslaving.
+ *         Also, master was not set down and the default set_mac_address()
+ *         would fail and generate an error message in the system log.
+ *       - For opt_c: slave should not be set to the master's setting
+ *         while it is running. It was already set during enslave. To
+ *         simplify things, it is now handeled separately.
+ *
+ *    - 2003/12/01 - Shmulik Hen <shmulik.hen at intel dot com>
+ *       - Code cleanup and style changes
+ *         set version to 1.1.0
+ */
+
+#include "libbb.h"
+
+/* #include <net/if.h> - no. linux/if_bonding.h pulls in linux/if.h */
+#include <net/if_arp.h>
+#include <linux/if_bonding.h>
+#include <linux/sockios.h>
+
+#ifndef IFNAMSIZ
+#define IFNAMSIZ 16
+#endif
+
+typedef uint64_t u64; /* hack, so we may include kernel's ethtool.h */
+typedef uint32_t u32; /* ditto */
+typedef uint16_t u16; /* ditto */
+typedef uint8_t u8;   /* ditto */
+#include <linux/ethtool.h>
+
+
+struct dev_data {
+       struct ifreq mtu, flags, hwaddr;
+};
+
+
+enum { skfd = 3 };      /* AF_INET socket for ioctl() calls. */
+struct globals {
+       unsigned abi_ver;       /* userland - kernel ABI version */
+       smallint hwaddr_set;    /* Master's hwaddr is set */
+       struct dev_data master;
+       struct dev_data slave;
+};
+#define G (*ptr_to_globals)
+#define abi_ver    (G.abi_ver   )
+#define hwaddr_set (G.hwaddr_set)
+#define master     (G.master    )
+#define slave      (G.slave     )
+#define INIT_G() do { \
+       SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
+} while (0)
+
+
+/* NOINLINEs are placed where it results in smaller code (gcc 4.3.1) */
+
+static int ioctl_on_skfd(unsigned request, struct ifreq *ifr)
+{
+       return ioctl(skfd, request, ifr);
+}
+
+static int set_ifrname_and_do_ioctl(unsigned request, struct ifreq *ifr, const char *ifname)
+{
+       strncpy_IFNAMSIZ(ifr->ifr_name, ifname);
+       return ioctl_on_skfd(request, ifr);
+}
+
+static int get_if_settings(char *ifname, struct dev_data *dd)
+{
+       int res;
+
+       res = set_ifrname_and_do_ioctl(SIOCGIFMTU, &dd->mtu, ifname);
+       res |= set_ifrname_and_do_ioctl(SIOCGIFFLAGS, &dd->flags, ifname);
+       res |= set_ifrname_and_do_ioctl(SIOCGIFHWADDR, &dd->hwaddr, ifname);
+
+       return res;
+}
+
+static int get_slave_flags(char *slave_ifname)
+{
+       return set_ifrname_and_do_ioctl(SIOCGIFFLAGS, &slave.flags, slave_ifname);
+}
+
+static int set_hwaddr(char *ifname, struct sockaddr *hwaddr)
+{
+       struct ifreq ifr;
+
+       memcpy(&(ifr.ifr_hwaddr), hwaddr, sizeof(*hwaddr));
+       return set_ifrname_and_do_ioctl(SIOCSIFHWADDR, &ifr, ifname);
+}
+
+static int set_mtu(char *ifname, int mtu)
+{
+       struct ifreq ifr;
+
+       ifr.ifr_mtu = mtu;
+       return set_ifrname_and_do_ioctl(SIOCSIFMTU, &ifr, ifname);
+}
+
+static int set_if_flags(char *ifname, int flags)
+{
+       struct ifreq ifr;
+
+       ifr.ifr_flags = flags;
+       return set_ifrname_and_do_ioctl(SIOCSIFFLAGS, &ifr, ifname);
+}
+
+static int set_if_up(char *ifname, int flags)
+{
+       int res = set_if_flags(ifname, flags | IFF_UP);
+       if (res)
+               bb_perror_msg("%s: can't up", ifname);
+       return res;
+}
+
+static int set_if_down(char *ifname, int flags)
+{
+       int res = set_if_flags(ifname, flags & ~IFF_UP);
+       if (res)
+               bb_perror_msg("%s: can't down", ifname);
+       return res;
+}
+
+static int clear_if_addr(char *ifname)
+{
+       struct ifreq ifr;
+
+       ifr.ifr_addr.sa_family = AF_INET;
+       memset(ifr.ifr_addr.sa_data, 0, sizeof(ifr.ifr_addr.sa_data));
+       return set_ifrname_and_do_ioctl(SIOCSIFADDR, &ifr, ifname);
+}
+
+static int set_if_addr(char *master_ifname, char *slave_ifname)
+{
+#if (SIOCGIFADDR | SIOCSIFADDR \
+  | SIOCGIFDSTADDR | SIOCSIFDSTADDR \
+  | SIOCGIFBRDADDR | SIOCSIFBRDADDR \
+  | SIOCGIFNETMASK | SIOCSIFNETMASK) <= 0xffff
+#define INT uint16_t
+#else
+#define INT int
+#endif
+       static const struct {
+               INT g_ioctl;
+               INT s_ioctl;
+       } ifra[] = {
+               { SIOCGIFADDR,    SIOCSIFADDR    },
+               { SIOCGIFDSTADDR, SIOCSIFDSTADDR },
+               { SIOCGIFBRDADDR, SIOCSIFBRDADDR },
+               { SIOCGIFNETMASK, SIOCSIFNETMASK },
+       };
+
+       struct ifreq ifr;
+       int res;
+       unsigned i;
+
+       for (i = 0; i < ARRAY_SIZE(ifra); i++) {
+               res = set_ifrname_and_do_ioctl(ifra[i].g_ioctl, &ifr, master_ifname);
+               if (res < 0) {
+                       ifr.ifr_addr.sa_family = AF_INET;
+                       memset(ifr.ifr_addr.sa_data, 0,
+                              sizeof(ifr.ifr_addr.sa_data));
+               }
+
+               res = set_ifrname_and_do_ioctl(ifra[i].s_ioctl, &ifr, slave_ifname);
+               if (res < 0)
+                       return res;
+       }
+
+       return 0;
+}
+
+static void change_active(char *master_ifname, char *slave_ifname)
+{
+       struct ifreq ifr;
+
+       if (!(slave.flags.ifr_flags & IFF_SLAVE)) {
+               bb_error_msg_and_die("%s is not a slave", slave_ifname);
+       }
+
+       strncpy_IFNAMSIZ(ifr.ifr_slave, slave_ifname);
+       if (set_ifrname_and_do_ioctl(SIOCBONDCHANGEACTIVE, &ifr, master_ifname)
+        && ioctl_on_skfd(BOND_CHANGE_ACTIVE_OLD, &ifr)
+       ) {
+               bb_perror_msg_and_die(
+                       "master %s, slave %s: can't "
+                       "change active",
+                       master_ifname, slave_ifname);
+       }
+}
+
+static NOINLINE int enslave(char *master_ifname, char *slave_ifname)
+{
+       struct ifreq ifr;
+       int res;
+
+       if (slave.flags.ifr_flags & IFF_SLAVE) {
+               bb_error_msg(
+                       "%s is already a slave",
+                       slave_ifname);
+               return 1;
+       }
+
+       res = set_if_down(slave_ifname, slave.flags.ifr_flags);
+       if (res)
+               return res;
+
+       if (abi_ver < 2) {
+               /* Older bonding versions would panic if the slave has no IP
+                * address, so get the IP setting from the master.
+                */
+               res = set_if_addr(master_ifname, slave_ifname);
+               if (res) {
+                       bb_perror_msg("%s: can't set address", slave_ifname);
+                       return res;
+               }
+       } else {
+               res = clear_if_addr(slave_ifname);
+               if (res) {
+                       bb_perror_msg("%s: can't clear address", slave_ifname);
+                       return res;
+               }
+       }
+
+       if (master.mtu.ifr_mtu != slave.mtu.ifr_mtu) {
+               res = set_mtu(slave_ifname, master.mtu.ifr_mtu);
+               if (res) {
+                       bb_perror_msg("%s: can't set MTU", slave_ifname);
+                       return res;
+               }
+       }
+
+       if (hwaddr_set) {
+               /* Master already has an hwaddr
+                * so set it's hwaddr to the slave
+                */
+               if (abi_ver < 1) {
+                       /* The driver is using an old ABI, so
+                        * the application sets the slave's
+                        * hwaddr
+                        */
+                       if (set_hwaddr(slave_ifname, &(master.hwaddr.ifr_hwaddr))) {
+                               bb_perror_msg("%s: can't set hw address",
+                                               slave_ifname);
+                               goto undo_mtu;
+                       }
+
+                       /* For old ABI the application needs to bring the
+                        * slave back up
+                        */
+                       if (set_if_up(slave_ifname, slave.flags.ifr_flags))
+                               goto undo_slave_mac;
+               }
+               /* The driver is using a new ABI,
+                * so the driver takes care of setting
+                * the slave's hwaddr and bringing
+                * it up again
+                */
+       } else {
+               /* No hwaddr for master yet, so
+                * set the slave's hwaddr to it
+                */
+               if (abi_ver < 1) {
+                       /* For old ABI, the master needs to be
+                        * down before setting it's hwaddr
+                        */
+                       if (set_if_down(master_ifname, master.flags.ifr_flags))
+                               goto undo_mtu;
+               }
+
+               if (set_hwaddr(master_ifname, &(slave.hwaddr.ifr_hwaddr))) {
+                       bb_error_msg("%s: can't set hw address",
+                               master_ifname);
+                       goto undo_mtu;
+               }
+
+               if (abi_ver < 1) {
+                       /* For old ABI, bring the master
+                        * back up
+                        */
+                       if (set_if_up(master_ifname, master.flags.ifr_flags))
+                               goto undo_master_mac;
+               }
+
+               hwaddr_set = 1;
+       }
+
+       /* Do the real thing */
+       strncpy_IFNAMSIZ(ifr.ifr_slave, slave_ifname);
+       if (set_ifrname_and_do_ioctl(SIOCBONDENSLAVE, &ifr, master_ifname)
+        && ioctl_on_skfd(BOND_ENSLAVE_OLD, &ifr)
+       ) {
+               goto undo_master_mac;
+       }
+
+       return 0;
+
+/* rollback (best effort) */
+ undo_master_mac:
+       set_hwaddr(master_ifname, &(master.hwaddr.ifr_hwaddr));
+       hwaddr_set = 0;
+       goto undo_mtu;
+
+ undo_slave_mac:
+       set_hwaddr(slave_ifname, &(slave.hwaddr.ifr_hwaddr));
+ undo_mtu:
+       set_mtu(slave_ifname, slave.mtu.ifr_mtu);
+       return 1;
+}
+
+static int release(char *master_ifname, char *slave_ifname)
+{
+       struct ifreq ifr;
+       int res = 0;
+
+       if (!(slave.flags.ifr_flags & IFF_SLAVE)) {
+               bb_error_msg("%s is not a slave", slave_ifname);
+               return 1;
+       }
+
+       strncpy_IFNAMSIZ(ifr.ifr_slave, slave_ifname);
+       if (set_ifrname_and_do_ioctl(SIOCBONDRELEASE, &ifr, master_ifname) < 0
+        && ioctl_on_skfd(BOND_RELEASE_OLD, &ifr) < 0
+       ) {
+               return 1;
+       }
+       if (abi_ver < 1) {
+               /* The driver is using an old ABI, so we'll set the interface
+                * down to avoid any conflicts due to same MAC/IP
+                */
+               res = set_if_down(slave_ifname, slave.flags.ifr_flags);
+       }
+
+       /* set to default mtu */
+       set_mtu(slave_ifname, 1500);
+
+       return res;
+}
+
+static NOINLINE void get_drv_info(char *master_ifname)
+{
+       struct ifreq ifr;
+       struct ethtool_drvinfo info;
+
+       memset(&ifr, 0, sizeof(ifr));
+       ifr.ifr_data = (caddr_t)&info;
+       info.cmd = ETHTOOL_GDRVINFO;
+       /* both fields are 32 bytes long (long enough) */
+       strcpy(info.driver, "ifenslave");
+       strcpy(info.fw_version, utoa(BOND_ABI_VERSION));
+       if (set_ifrname_and_do_ioctl(SIOCETHTOOL, &ifr, master_ifname) < 0) {
+               if (errno == EOPNOTSUPP)
+                       return;
+               bb_perror_msg_and_die("%s: SIOCETHTOOL error", master_ifname);
+       }
+
+       abi_ver = bb_strtou(info.fw_version, NULL, 0);
+       if (errno)
+               bb_error_msg_and_die("%s: SIOCETHTOOL error", master_ifname);
+}
+
+int ifenslave_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int ifenslave_main(int argc UNUSED_PARAM, char **argv)
+{
+       char *master_ifname, *slave_ifname;
+       int rv;
+       int res;
+       unsigned opt;
+       enum {
+               OPT_c = (1 << 0),
+               OPT_d = (1 << 1),
+               OPT_f = (1 << 2),
+       };
+#if ENABLE_GETOPT_LONG
+       static const char ifenslave_longopts[] ALIGN1 =
+               "change-active\0"  No_argument "c"
+               "detach\0"         No_argument "d"
+               "force\0"          No_argument "f"
+               /* "all-interfaces\0" No_argument "a" */
+               ;
+
+       applet_long_options = ifenslave_longopts;
+#endif
+       INIT_G();
+
+       opt = getopt32(argv, "cdfa");
+       argv += optind;
+       if (opt & (opt-1)) /* Only one option can be given */
+               bb_show_usage();
+
+       master_ifname = *argv++;
+
+       /* No interface names - show all interfaces. */
+       if (!master_ifname) {
+               display_interfaces(NULL);
+               return EXIT_SUCCESS;
+       }
+
+       /* Open a basic socket */
+       xmove_fd(xsocket(AF_INET, SOCK_DGRAM, 0), skfd);
+
+       /* Exchange abi version with bonding module */
+       get_drv_info(master_ifname);
+
+       slave_ifname = *argv++;
+       if (!slave_ifname) {
+               if (opt & (OPT_d|OPT_c)) {
+                       /* --change or --detach, and no slaves given -
+                        * show all interfaces. */
+                       display_interfaces(slave_ifname /* == NULL */);
+                       return 2; /* why 2? */
+               }
+               /* A single arg means show the
+                * configuration for this interface
+                */
+               display_interfaces(master_ifname);
+               return EXIT_SUCCESS;
+       }
+
+       if (get_if_settings(master_ifname, &master)) {
+               /* Probably a good reason not to go on */
+               bb_perror_msg_and_die("%s: can't get settings", master_ifname);
+       }
+
+       /* Check if master is indeed a master;
+        * if not then fail any operation
+        */
+       if (!(master.flags.ifr_flags & IFF_MASTER))
+               bb_error_msg_and_die("%s is not a master", master_ifname);
+
+       /* Check if master is up; if not then fail any operation */
+       if (!(master.flags.ifr_flags & IFF_UP))
+               bb_error_msg_and_die("%s is not up", master_ifname);
+
+#ifdef WHY_BOTHER
+       /* Neither -c[hange] nor -d[etach] -> it's "enslave" then;
+        * and -f[orce] is not there too. Check that it's ethernet. */
+       if (!(opt & (OPT_d|OPT_c|OPT_f)) {
+               /* The family '1' is ARPHRD_ETHER for ethernet. */
+               if (master.hwaddr.ifr_hwaddr.sa_family != 1) {
+                       bb_error_msg_and_die(
+                               "%s is not ethernet-like (-f overrides)",
+                               master_ifname);
+               }
+       }
+#endif
+
+       /* Accepts only one slave */
+       if (opt & OPT_c) {
+               /* Change active slave */
+               if (get_slave_flags(slave_ifname)) {
+                       bb_perror_msg_and_die(
+                               "%s: can't get flags", slave_ifname);
+               }
+               change_active(master_ifname, slave_ifname);
+               return EXIT_SUCCESS;
+       }
+
+       /* Accepts multiple slaves */
+       res = 0;
+       do {
+               if (opt & OPT_d) {
+                       /* Detach a slave interface from the master */
+                       rv = get_slave_flags(slave_ifname);
+                       if (rv) {
+                               /* Can't work with this slave, */
+                               /* remember the error and skip it */
+                               bb_perror_msg(
+                                       "skipping %s: can't get flags",
+                                       slave_ifname);
+                               res = rv;
+                               continue;
+                       }
+                       rv = release(master_ifname, slave_ifname);
+                       if (rv) {
+                               bb_perror_msg("can't release %s from %s",
+                                       slave_ifname, master_ifname);
+                               res = rv;
+                       }
+               } else {
+                       /* Attach a slave interface to the master */
+                       rv = get_if_settings(slave_ifname, &slave);
+                       if (rv) {
+                               /* Can't work with this slave, */
+                               /* remember the error and skip it */
+                               bb_perror_msg(
+                                       "skipping %s: can't get settings",
+                                       slave_ifname);
+                               res = rv;
+                               continue;
+                       }
+                       rv = enslave(master_ifname, slave_ifname);
+                       if (rv) {
+                               bb_perror_msg("can't enslave %s to %s",
+                                       slave_ifname, master_ifname);
+                               res = rv;
+                       }
+               }
+       } while ((slave_ifname = *argv++) != NULL);
+
+       if (ENABLE_FEATURE_CLEAN_UP) {
+               close(skfd);
+       }
+
+       return res;
+}
diff --git a/networking/ifupdown.c b/networking/ifupdown.c
new file mode 100644 (file)
index 0000000..dc7ed49
--- /dev/null
@@ -0,0 +1,1301 @@
+/* vi: set sw=4 ts=4: */
+/*
+ *  ifupdown for busybox
+ *  Copyright (c) 2002 Glenn McGrath
+ *  Copyright (c) 2003-2004 Erik Andersen <andersen@codepoet.org>
+ *
+ *  Based on ifupdown v 0.6.4 by Anthony Towns
+ *  Copyright (c) 1999 Anthony Towns <aj@azure.humbug.org.au>
+ *
+ *  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
+ *  (defined via CONFIG_IFUPDOWN_IFSTATE_PATH) and can be overridden by build
+ *  configuration.
+ *
+ * Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
+ */
+
+#include <sys/utsname.h>
+#include <fnmatch.h>
+
+#include "libbb.h"
+
+#define MAX_OPT_DEPTH 10
+#define EUNBALBRACK 10001
+#define EUNDEFVAR   10002
+#define EUNBALPER   10000
+
+#if ENABLE_FEATURE_IFUPDOWN_MAPPING
+#define MAX_INTERFACE_LENGTH 10
+#endif
+
+#define UDHCPC_CMD_OPTIONS CONFIG_IFUPDOWN_UDHCPC_CMD_OPTIONS
+
+#define debug_noise(args...) /*fprintf(stderr, args)*/
+
+/* Forward declaration */
+struct interface_defn_t;
+
+typedef int execfn(char *command);
+
+struct method_t {
+       const char *name;
+       int (*up)(struct interface_defn_t *ifd, execfn *e);
+       int (*down)(struct interface_defn_t *ifd, execfn *e);
+};
+
+struct address_family_t {
+       const 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 **my_environ;
+
+static const char *startup_PATH;
+
+#if ENABLE_FEATURE_IFUPDOWN_IPV4 || ENABLE_FEATURE_IFUPDOWN_IPV6
+
+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) {
+               static char *label_buf;
+               //char *result;
+
+               free(label_buf);
+               label_buf = xstrdup(ifd->iface);
+               // Remove virtual iface suffix - why?
+               // ubuntu's ifup doesn't do this
+               //result = strchrnul(label_buf, ':');
+               //*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;
+}
+
+#if ENABLE_FEATURE_IFUPDOWN_IP
+static int count_netmask_bits(const char *dotted_quad)
+{
+//     int result;
+//     unsigned 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;
+//     if ((a|b|c|d) >> 8)
+//             return -1; /* one of numbers is >= 256 */
+//     d |= (a << 24) | (b << 16) | (c << 8); /* IP */
+//     d = ~d; /* 11110000 -> 00001111 */
+
+       /* Shorter version */
+       int result;
+       struct in_addr ip;
+       unsigned d;
+
+       if (inet_aton(dotted_quad, &ip) == 0)
+               return -1; /* malformed dotted IP */
+       d = ntohl(ip.s_addr); /* IP in host order */
+       d = ~d; /* 11110000 -> 00001111 */
+       if (d & (d+1)) /* check that it is in 00001111 form */
+               return -1; /* no it is not */
+       result = 32;
+       while (d) {
+               d >>= 1;
+               result--;
+       }
+       return result;
+}
+#endif
+
+static char *parse(const char *command, struct interface_defn_t *ifd)
+{
+       size_t old_pos[MAX_OPT_DEPTH] = { 0 };
+       int okay[MAX_OPT_DEPTH] = { 1 };
+       int opt_depth = 1;
+       char *result = NULL;
+
+       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] = result ? strlen(result) : 0;
+                               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) {
+#if ENABLE_FEATURE_IFUPDOWN_IP
+                                       /* "hwaddress <class> <address>":
+                                        * unlike ifconfig, ip doesnt want <class>
+                                        * (usually "ether" keyword). Skip it. */
+                                       if (strncmp(command, "hwaddress", 9) == 0) {
+                                               varvalue = skip_whitespace(skip_non_whitespace(varvalue));
+                                       }
+#endif
+                                       addstr(&result, varvalue, strlen(varvalue));
+                               } else {
+#if ENABLE_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);
+                                                       if (res > 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) {
+               /* parse error? */
+               return 0;
+       }
+       /* out == "": parsed ok but not all needed variables known, skip */
+       ret = out[0] ? (*exec)(out) : 1;
+
+       free(out);
+       if (ret != 1) {
+               return 0;
+       }
+       return 1;
+}
+#endif
+
+#if ENABLE_FEATURE_IFUPDOWN_IPV6
+static int loopback_up6(struct interface_defn_t *ifd, execfn *exec)
+{
+#if ENABLE_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)
+{
+#if ENABLE_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;
+#if ENABLE_FEATURE_IFUPDOWN_IP
+       result = execute("ip addr add %address%/%netmask% dev %iface%[[ label %label%]]", ifd, exec);
+       result += execute("ip link set[[ mtu %mtu%]][[ addr %hwaddress%]] %iface% up", ifd, exec);
+       /* Was: "[[ ip ....%gateway% ]]". Removed extra spaces w/o checking */
+       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)
+{
+#if ENABLE_FEATURE_IFUPDOWN_IP
+       return execute("ip link set %iface% down", ifd, exec);
+#else
+       return execute("ifconfig %iface% down", ifd, exec);
+#endif
+}
+
+#if ENABLE_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[] = {
+#if ENABLE_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",
+       ARRAY_SIZE(methods6),
+       methods6
+};
+#endif /* FEATURE_IFUPDOWN_IPV6 */
+
+#if ENABLE_FEATURE_IFUPDOWN_IPV4
+static int loopback_up(struct interface_defn_t *ifd, execfn *exec)
+{
+#if ENABLE_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)
+{
+#if ENABLE_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;
+#if ENABLE_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%]][[ addr %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;
+#if ENABLE_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); */
+       /* Bringing the interface down deletes the routes in itself.
+          Otherwise this fails if we reference 'gateway' when using this from dhcp_down */
+       result = 1;
+       result += execute("ifconfig %iface% down", ifd, exec);
+#endif
+       return ((result == 2) ? 2 : 0);
+}
+
+#if ENABLE_FEATURE_IFUPDOWN_EXTERNAL_DHCP
+struct dhcp_client_t
+{
+       const char *name;
+       const char *startcmd;
+       const char *stopcmd;
+};
+
+static const struct dhcp_client_t ext_dhcp_clients[] = {
+       { "dhcpcd",
+               "dhcpcd[[ -h %hostname%]][[ -i %vendor%]][[ -I %clientid%]][[ -l %leasetime%]] %iface%",
+               "dhcpcd -k %iface%",
+       },
+       { "dhclient",
+               "dhclient -pf /var/run/dhclient.%iface%.pid %iface%",
+               "kill -9 `cat /var/run/dhclient.%iface%.pid` 2>/dev/null",
+       },
+       { "pump",
+               "pump -i %iface%[[ -h %hostname%]][[ -l %leasehours%]]",
+               "pump -i %iface% -k",
+       },
+       { "udhcpc",
+               "udhcpc " UDHCPC_CMD_OPTIONS " -p /var/run/udhcpc.%iface%.pid -i %iface%[[ -H %hostname%]][[ -c %clientid%]]"
+                               "[[ -s %script%]][[ %udhcpc_opts%]]",
+               "kill `cat /var/run/udhcpc.%iface%.pid` 2>/dev/null",
+       },
+};
+#endif /* ENABLE_FEATURE_IFUPDOWN_EXTERNAL_DHCPC */
+
+#if ENABLE_FEATURE_IFUPDOWN_EXTERNAL_DHCP
+static int dhcp_up(struct interface_defn_t *ifd, execfn *exec)
+{
+       unsigned i;
+#if ENABLE_FEATURE_IFUPDOWN_IP
+       /* ip doesn't up iface when it configures it (unlike ifconfig) */
+       if (!execute("ip link set[[ addr %hwaddress%]] %iface% up", ifd, exec))
+               return 0;
+#else
+       /* needed if we have hwaddress on dhcp iface */
+       if (!execute("ifconfig %iface%[[ hw %hwaddress%]] up", ifd, exec))
+               return 0;
+#endif
+       for (i = 0; i < ARRAY_SIZE(ext_dhcp_clients); 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;
+}
+#elif ENABLE_APP_UDHCPC
+static int dhcp_up(struct interface_defn_t *ifd, execfn *exec)
+{
+#if ENABLE_FEATURE_IFUPDOWN_IP
+       /* ip doesn't up iface when it configures it (unlike ifconfig) */
+       if (!execute("ip link set[[ addr %hwaddress%]] %iface% up", ifd, exec))
+               return 0;
+#else
+       /* needed if we have hwaddress on dhcp iface */
+       if (!execute("ifconfig %iface%[[ hw %hwaddress%]] up", ifd, exec))
+               return 0;
+#endif
+       return execute("udhcpc " UDHCPC_CMD_OPTIONS " -p /var/run/udhcpc.%iface%.pid "
+                       "-i %iface%[[ -H %hostname%]][[ -c %clientid%]][[ -s %script%]][[ %udhcpc_opts%]]",
+                       ifd, exec);
+}
+#else
+static int dhcp_up(struct interface_defn_t *ifd UNUSED_PARAM,
+               execfn *exec UNUSED_PARAM)
+{
+       return 0; /* no dhcp support */
+}
+#endif
+
+#if ENABLE_FEATURE_IFUPDOWN_EXTERNAL_DHCP
+static int dhcp_down(struct interface_defn_t *ifd, execfn *exec)
+{
+       int result = 0;
+       unsigned i;
+
+       for (i = 0; i < ARRAY_SIZE(ext_dhcp_clients); i++) {
+               if (exists_execable(ext_dhcp_clients[i].name)) {
+                       result += execute(ext_dhcp_clients[i].stopcmd, ifd, exec);
+                       if (result)
+                               break;
+               }
+       }
+
+       if (!result)
+               bb_error_msg("warning: no dhcp clients found and stopped");
+
+       /* Sleep a bit, otherwise static_down tries to bring down interface too soon,
+          and it may come back up because udhcpc is still shutting down */
+       usleep(100000);
+       result += static_down(ifd, exec);
+       return ((result == 3) ? 3 : 0);
+}
+#elif ENABLE_APP_UDHCPC
+static int dhcp_down(struct interface_defn_t *ifd, execfn *exec)
+{
+       int result;
+       result = execute("kill "
+                      "`cat /var/run/udhcpc.%iface%.pid` 2>/dev/null", ifd, exec);
+       /* Also bring the hardware interface down since
+          killing the dhcp client alone doesn't do it.
+          This enables consecutive ifup->ifdown->ifup */
+       /* Sleep a bit, otherwise static_down tries to bring down interface too soon,
+          and it may come back up because udhcpc is still shutting down */
+       usleep(100000);
+       result += static_down(ifd, exec);
+       return ((result == 3) ? 3 : 0);
+}
+#else
+static int dhcp_down(struct interface_defn_t *ifd UNUSED_PARAM,
+               execfn *exec UNUSED_PARAM)
+{
+       return 0; /* no dhcp support */
+}
+#endif
+
+static int manual_up_down(struct interface_defn_t *ifd UNUSED_PARAM, execfn *exec UNUSED_PARAM)
+{
+       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("start-stop-daemon --start -x wvdial "
+               "-p /var/run/wvdial.%iface% -b -m --[[ %provider%]]", ifd, exec);
+}
+
+static int wvdial_down(struct interface_defn_t *ifd, execfn *exec)
+{
+       return execute("start-stop-daemon --stop -x 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",
+       ARRAY_SIZE(methods),
+       methods
+};
+
+#endif /* if ENABLE_FEATURE_IFUPDOWN_IPV4 */
+
+static char *next_word(char **buf)
+{
+       unsigned length;
+       char *word;
+
+       /* Skip over leading whitespace */
+       word = skip_whitespace(*buf);
+
+       /* Stop on EOL */
+       if (*word == '\0')
+               return NULL;
+
+       /* Find the length of this word (can't be 0) */
+       length = strcspn(word, " \t\n");
+
+       /* Unless we are already at NUL, store NUL and advance */
+       if (word[length] != '\0')
+               word[length++] = '\0';
+
+       *buf = word + length;
+
+       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;
+       /* TODO: use index_in_str_array() */
+       for (i = 0; i < af->n_methods; i++) {
+               if (strcmp(af->method[i].name, name) == 0) {
+                       return &af->method[i];
+               }
+       }
+       return NULL;
+}
+
+static struct interfaces_file_t *read_interfaces(const char *filename)
+{
+       /* Let's try to be compatible.
+        *
+        * "man 5 interfaces" says:
+        * Lines starting with "#" are ignored. Note that end-of-line
+        * comments are NOT supported, comments must be on a line of their own.
+        * A line may be extended across multiple lines by making
+        * the last character a backslash.
+        *
+        * Seen elsewhere in example config file:
+        * A first non-blank "#" character makes the rest of the line
+        * be ignored. Blank lines are ignored. Lines may be indented freely.
+        * A "\" character at the very end of the line indicates the next line
+        * should be treated as a continuation of the current one.
+        */
+#if ENABLE_FEATURE_IFUPDOWN_MAPPING
+       struct mapping_defn_t *currmap = NULL;
+#endif
+       struct interface_defn_t *currif = NULL;
+       struct interfaces_file_t *defn;
+       FILE *f;
+       char *buf;
+       char *first_word;
+       char *rest_of_line;
+       enum { NONE, IFACE, MAPPING } currently_processing = NONE;
+
+       defn = xzalloc(sizeof(*defn));
+       f = xfopen_for_read(filename);
+
+       while ((buf = xmalloc_fgetline(f)) != NULL) {
+#if ENABLE_DESKTOP
+               /* Trailing "\" concatenates lines */
+               char *p;
+               while ((p = last_char_is(buf, '\\')) != NULL) {
+                       *p = '\0';
+                       rest_of_line = xmalloc_fgetline(f);
+                       if (!rest_of_line)
+                               break;
+                       p = xasprintf("%s%s", buf, rest_of_line);
+                       free(buf);
+                       free(rest_of_line);
+                       buf = p;
+               }
+#endif
+               rest_of_line = buf;
+               first_word = next_word(&rest_of_line);
+               if (!first_word || *first_word == '#') {
+                       free(buf);
+                       continue; /* blank/comment line */
+               }
+
+               if (strcmp(first_word, "mapping") == 0) {
+#if ENABLE_FEATURE_IFUPDOWN_MAPPING
+                       currmap = xzalloc(sizeof(*currmap));
+
+                       while ((first_word = next_word(&rest_of_line)) != NULL) {
+                               currmap->match = xrealloc_vector(currmap->match, 4, currmap->n_matches);
+                               currmap->match[currmap->n_matches++] = xstrdup(first_word);
+                       }
+                       /*currmap->max_mappings = 0; - done by xzalloc */
+                       /*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(first_word, "iface") == 0) {
+                       static const struct address_family_t *const addr_fams[] = {
+#if ENABLE_FEATURE_IFUPDOWN_IPV4
+                               &addr_inet,
+#endif
+#if ENABLE_FEATURE_IFUPDOWN_IPV6
+                               &addr_inet6,
+#endif
+                               NULL
+                       };
+                       char *iface_name;
+                       char *address_family_name;
+                       char *method_name;
+                       llist_t *iface_list;
+
+                       currif = xzalloc(sizeof(*currif));
+                       iface_name = next_word(&rest_of_line);
+                       address_family_name = next_word(&rest_of_line);
+                       method_name = next_word(&rest_of_line);
+
+                       if (method_name == NULL)
+                               bb_error_msg_and_die("too few parameters for line \"%s\"", buf);
+
+                       /* ship any trailing whitespace */
+                       rest_of_line = skip_whitespace(rest_of_line);
+
+                       if (rest_of_line[0] != '\0' /* && rest_of_line[0] != '#' */)
+                               bb_error_msg_and_die("too many parameters \"%s\"", buf);
+
+                       currif->iface = xstrdup(iface_name);
+
+                       currif->address_family = get_address_family(addr_fams, address_family_name);
+                       if (!currif->address_family)
+                               bb_error_msg_and_die("unknown address type \"%s\"", address_family_name);
+
+                       currif->method = get_method(currif->address_family, method_name);
+                       if (!currif->method)
+                               bb_error_msg_and_die("unknown method \"%s\"", method_name);
+
+                       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_and_die("duplicate interface \"%s\"", tmp->iface);
+                               }
+                       }
+                       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(first_word, "auto") == 0) {
+                       while ((first_word = next_word(&rest_of_line)) != NULL) {
+
+                               /* Check the interface isnt already listed */
+                               if (llist_find_str(defn->autointerfaces, first_word)) {
+                                       bb_perror_msg_and_die("interface declared auto twice \"%s\"", buf);
+                               }
+
+                               /* Add the interface to the list */
+                               llist_add_to_end(&(defn->autointerfaces), xstrdup(first_word));
+                               debug_noise("\nauto %s\n", first_word);
+                       }
+                       currently_processing = NONE;
+               } else {
+                       switch (currently_processing) {
+                       case IFACE:
+                               if (rest_of_line[0] == '\0')
+                                       bb_error_msg_and_die("option with empty value \"%s\"", buf);
+
+                               if (strcmp(first_word, "up") != 0
+                                && strcmp(first_word, "down") != 0
+                                && strcmp(first_word, "pre-up") != 0
+                                && strcmp(first_word, "post-down") != 0
+                               ) {
+                                       int i;
+                                       for (i = 0; i < currif->n_options; i++) {
+                                               if (strcmp(currif->option[i].name, first_word) == 0)
+                                                       bb_error_msg_and_die("duplicate option \"%s\"", buf);
+                                       }
+                               }
+                               if (currif->n_options >= currif->max_options) {
+                                       currif->max_options += 10;
+                                       currif->option = xrealloc(currif->option,
+                                               sizeof(*currif->option) * currif->max_options);
+                               }
+                               debug_noise("\t%s=%s\n", first_word, rest_of_line);
+                               currif->option[currif->n_options].name = xstrdup(first_word);
+                               currif->option[currif->n_options].value = xstrdup(rest_of_line);
+                               currif->n_options++;
+                               break;
+                       case MAPPING:
+#if ENABLE_FEATURE_IFUPDOWN_MAPPING
+                               if (strcmp(first_word, "script") == 0) {
+                                       if (currmap->script != NULL)
+                                               bb_error_msg_and_die("duplicate script in mapping \"%s\"", buf);
+                                       currmap->script = xstrdup(next_word(&rest_of_line));
+                               } else if (strcmp(first_word, "map") == 0) {
+                                       if (currmap->n_mappings >= currmap->max_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(&rest_of_line));
+                                       currmap->n_mappings++;
+                               } else {
+                                       bb_error_msg_and_die("misplaced option \"%s\"", buf);
+                               }
+#endif
+                               break;
+                       case NONE:
+                       default:
+                               bb_error_msg_and_die("misplaced option \"%s\"", buf);
+                       }
+               }
+               free(buf);
+       } /* while (fgets) */
+
+       if (ferror(f) != 0) {
+               /* ferror does NOT set errno! */
+               bb_error_msg_and_die("%s: I/O error", filename);
+       }
+       fclose(f);
+
+       return defn;
+}
+
+static char *setlocalenv(const 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 (my_environ != NULL) {
+               for (ppch = my_environ; *ppch; ppch++) {
+                       free(*ppch);
+                       *ppch = NULL;
+               }
+               free(my_environ);
+       }
+       my_environ = xzalloc(sizeof(char *) * (n_env_entries + 1 /* for final NULL */ ));
+       environend = my_environ;
+
+       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);
+               child = vfork();
+               switch (child) {
+               case -1: /* failure */
+                       return 0;
+               case 0: /* child */
+                       execle(DEFAULT_SHELL, DEFAULT_SHELL, "-c", str, (char *) NULL, my_environ);
+                       _exit(127);
+               }
+               safe_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);
+       /* heh, we don't bother free'ing it */
+       return doit(buf);
+}
+
+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;
+}
+
+#if ENABLE_FEATURE_IFUPDOWN_MAPPING
+static int popen2(FILE **in, FILE **out, char *command, char *param)
+{
+       char *argv[3] = { command, param, NULL };
+       struct fd_pair infd, outfd;
+       pid_t pid;
+
+       xpiped_pair(infd);
+       xpiped_pair(outfd);
+
+       fflush(NULL);
+       pid = vfork();
+
+       switch (pid) {
+       case -1:  /* failure */
+               bb_perror_msg_and_die("vfork");
+       case 0:  /* child */
+               /* NB: close _first_, then move fds! */
+               close(infd.wr);
+               close(outfd.rd);
+               xmove_fd(infd.rd, 0);
+               xmove_fd(outfd.wr, 1);
+               BB_EXECVP(command, argv);
+               _exit(127);
+       }
+       /* parent */
+       close(infd.rd);
+       close(outfd.wr);
+       *in = fdopen(infd.wr, "w");
+       *out = fdopen(outfd.rd, "r");
+       return pid;
+}
+
+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. Never fails. */
+       pid = popen2(&in, &out, map->script, physical);
+
+       /* Write mappings to stdin of mapping script. */
+       for (i = 0; i < map->n_mappings; i++) {
+               fprintf(in, "%s\n", map->mapping[i]);
+       }
+       fclose(in);
+       safe_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 = xmalloc_fgetline(out);
+
+               if (new_logical) {
+                       /* 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;
+               }
+       }
+
+       fclose(out);
+
+       return logical;
+}
+#endif /* FEATURE_IFUPDOWN_MAPPING */
+
+static llist_t *find_iface_state(llist_t *state_list, const char *iface)
+{
+       unsigned 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;
+}
+
+/* read the previous state from the state file */
+static llist_t *read_iface_state(void)
+{
+       llist_t *state_list = NULL;
+       FILE *state_fp = fopen_for_read(CONFIG_IFUPDOWN_IFSTATE_PATH);
+
+       if (state_fp) {
+               char *start, *end_ptr;
+               while ((start = xmalloc_fgets(state_fp)) != NULL) {
+                       /* We should only need to check for a single character */
+                       end_ptr = start + strcspn(start, " \t\n");
+                       *end_ptr = '\0';
+                       llist_add_to(&state_list, start);
+               }
+               fclose(state_fp);
+       }
+       return state_list;
+}
+
+
+int ifupdown_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int ifupdown_main(int argc, char **argv)
+{
+       int (*cmds)(struct interface_defn_t *);
+       struct interfaces_file_t *defn;
+       llist_t *target_list = NULL;
+       const char *interfaces = "/etc/network/interfaces";
+       bool any_failures = 0;
+
+       cmds = iface_down;
+       if (applet_name[2] == 'u') {
+               /* ifup command */
+               cmds = iface_up;
+       }
+
+       getopt32(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);
+
+       startup_PATH = getenv("PATH");
+       if (!startup_PATH) startup_PATH = "";
+
+       /* Create a list of interfaces to work on */
+       if (DO_ALL) {
+               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;
+               bool 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) {
+                       llist_t *state_list = read_iface_state();
+                       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;
+                               }
+                       }
+                       llist_free(state_list, free);
+               }
+
+#if ENABLE_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) {
+                       bb_putchar('\n');
+               }
+
+               if (!okay && !FORCE) {
+                       bb_error_msg("ignoring unknown interface %s", liface);
+                       any_failures = 1;
+               } else if (!NO_ACT) {
+                       /* update the state file */
+                       FILE *state_fp;
+                       llist_t *state;
+                       llist_t *state_list = read_iface_state();
+                       llist_t *iface_state = find_iface_state(state_list, iface);
+
+                       if (cmds == iface_up) {
+                               char * const 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 state_list */
+                               llist_unlink(&state_list, iface_state);
+                               free(llist_pop(&iface_state));
+                       }
+
+                       /* Actually write the new state */
+                       state_fp = xfopen_for_write(CONFIG_IFUPDOWN_IFSTATE_PATH);
+                       state = state_list;
+                       while (state) {
+                               if (state->data) {
+                                       fprintf(state_fp, "%s\n", state->data);
+                               }
+                               state = state->link;
+                       }
+                       fclose(state_fp);
+                       llist_free(state_list, free);
+               }
+       }
+
+       return any_failures;
+}
diff --git a/networking/inetd.c b/networking/inetd.c
new file mode 100644 (file)
index 0000000..590bf23
--- /dev/null
@@ -0,0 +1,1592 @@
+/* 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 <dzo@simtreas.ru>     */
+/* IPv6 support, many bug fixes by Denys Vlasenko (c) 2008 */
+/*
+ * 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 configured services when a connection
+ * from a peer is established or a datagram arrives.
+ * 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 peer's 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. peer's address can be obtained
+ * using recvfrom.
+ *
+ * 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
+ *                                      (usually "tcp" or "udp")
+ *      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
+ *      rpc/protocol                    "rpc/tcp" etc
+ *      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.
+ */
+
+/* inetd rules for passing file descriptors to children
+ * (http://www.freebsd.org/cgi/man.cgi?query=inetd):
+ *
+ * The wait/nowait entry specifies whether the server that is invoked by
+ * inetd will take over the socket associated with the service access point,
+ * and thus whether inetd should wait for the server to exit before listen-
+ * ing for new service requests.  Datagram servers must use "wait", as
+ * they are always invoked with the original datagram socket bound to the
+ * specified service address.  These servers must read at least one datagram
+ * from the socket before exiting.  If a datagram server connects to its
+ * peer, freeing the socket so inetd can receive further messages on the
+ * socket, it is said to be a "multi-threaded" server; it should read one
+ * datagram from the socket and create a new socket connected to the peer.
+ * It should fork, and the parent should then exit to allow inetd to check
+ * for new service requests to spawn new servers.  Datagram servers which
+ * process all incoming datagrams on a socket and eventually time out are
+ * said to be "single-threaded".  The comsat(8), biff(1) and talkd(8)
+ * utilities are both examples of the latter type of datagram server.  The
+ * tftpd(8) utility is an example of a multi-threaded datagram server.
+ *
+ * Servers using stream sockets generally are multi-threaded and use the
+ * "nowait" entry. Connection requests for these services are accepted by
+ * inetd, and the server is given only the newly-accepted socket connected
+ * to a client of the service.  Most stream-based services operate in this
+ * manner.  Stream-based servers that use "wait" are started with the lis-
+ * tening service socket, and must accept at least one connection request
+ * before exiting.  Such a server would normally accept and process incoming
+ * connection requests until a timeout.
+ */
+
+/* Despite of above doc saying that dgram services must use "wait",
+ * "udp nowait" servers are implemented in busyboxed inetd.
+ * IPv6 addresses are also implemented. However, they may look ugly -
+ * ":::service..." means "address '::' (IPv6 wildcard addr)":"service"...
+ * You have to put "tcp6"/"udp6" in protocol field to select IPv6.
+ */
+
+/* Here's the scoop concerning the user[:group] feature:
+ * 1) group is not specified:
+ *      a) user = root: NO setuid() or setgid() is done
+ *      b) other:       initgroups(name, primary group)
+ *                      setgid(primary group as found in passwd)
+ *                      setuid()
+ * 2) group is specified:
+ *      a) user = root: setgid(specified group)
+ *                      NO initgroups()
+ *                      NO setuid()
+ *      b) other:       initgroups(name, specified group)
+ *                      setgid(specified group)
+ *                      setuid()
+ */
+
+#include <syslog.h>
+#include <sys/un.h>
+
+#include "libbb.h"
+
+#if ENABLE_FEATURE_INETD_RPC
+#include <rpc/rpc.h>
+#include <rpc/pmap_clnt.h>
+#endif
+
+#if !BB_MMU
+/* stream version of chargen is forking but not execing,
+ * can't do that (easily) on NOMMU */
+#undef  ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_CHARGEN
+#define ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_CHARGEN 0
+#endif
+
+#define _PATH_INETDPID  "/var/run/inetd.pid"
+
+#define CNT_INTERVAL    60      /* servers in CNT_INTERVAL sec. */
+#define RETRYTIME       60      /* retry after bind or server fail */
+
+// TODO: explain, or get rid of setrlimit games
+
+#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
+
+#if ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_DISCARD \
+ || ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_ECHO    \
+ || ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_CHARGEN \
+ || ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_TIME    \
+ || ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_DAYTIME
+# define INETD_BUILTINS_ENABLED
+#endif
+
+typedef struct servtab_t {
+       /* The most frequently referenced one: */
+       int se_fd;                            /* open descriptor */
+       /* NB: 'biggest fields last' saves on code size (~250 bytes) */
+       /* [addr:]service socktype proto wait user[:group] prog [args] */
+       char *se_local_hostname;              /* addr to listen on */
+       char *se_service;                     /* "80" or "www" or "mount/2[-3]" */
+       /* socktype is in se_socktype */      /* "stream" "dgram" "raw" "rdm" "seqpacket" */
+       char *se_proto;                       /* "unix" or "[rpc/]tcp[6]" */
+#if ENABLE_FEATURE_INETD_RPC
+       int se_rpcprog;                       /* rpc program number */
+       int se_rpcver_lo;                     /* rpc program lowest version */
+       int se_rpcver_hi;                     /* rpc program highest version */
+#define is_rpc_service(sep)       ((sep)->se_rpcver_lo != 0)
+#else
+#define is_rpc_service(sep)       0
+#endif
+       pid_t se_wait;                        /* 0:"nowait", 1:"wait", >1:"wait" */
+                                             /* and waiting for this pid */
+       socktype_t se_socktype;               /* SOCK_STREAM/DGRAM/RDM/... */
+       family_t se_family;                   /* AF_UNIX/INET[6] */
+       /* se_proto_no is used by RPC code only... hmm */
+       smallint se_proto_no;                 /* IPPROTO_TCP/UDP, n/a for AF_UNIX */
+       smallint se_checked;                  /* looked at during merge */
+       unsigned se_max;                      /* allowed instances per minute */
+       unsigned se_count;                    /* number started since se_time */
+       unsigned se_time;                     /* when we started counting */
+       char *se_user;                        /* user name to run as */
+       char *se_group;                       /* group name to run as, can be NULL */
+#ifdef INETD_BUILTINS_ENABLED
+       const struct builtin *se_builtin;     /* if built-in, description */
+#endif
+       struct servtab_t *se_next;
+       len_and_sockaddr *se_lsa;
+       char *se_program;                     /* server program */
+#define MAXARGV 20
+       char *se_argv[MAXARGV + 1];           /* program arguments */
+} servtab_t;
+
+#ifdef INETD_BUILTINS_ENABLED
+/* Echo received data */
+#if ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_ECHO
+static void echo_stream(int, servtab_t *);
+static void echo_dg(int, servtab_t *);
+#endif
+/* Internet /dev/null */
+#if ENABLE_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 */
+#if ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_TIME
+static void machtime_stream(int, servtab_t *);
+static void machtime_dg(int, servtab_t *);
+#endif
+/* Return human-readable time */
+#if ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_DAYTIME
+static void daytime_stream(int, servtab_t *);
+static void daytime_dg(int, servtab_t *);
+#endif
+/* Familiar character generator */
+#if ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_CHARGEN
+static void chargen_stream(int, servtab_t *);
+static void chargen_dg(int, servtab_t *);
+#endif
+
+struct builtin {
+       /* NB: not necessarily NUL terminated */
+       char bi_service7[7];      /* internally provided service name */
+       uint8_t bi_fork;          /* 1 if stream fn should run in child */
+       void (*bi_stream_fn)(int, servtab_t *);
+       void (*bi_dgram_fn)(int, servtab_t *);
+};
+
+static const struct builtin builtins[] = {
+#if ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_ECHO
+       { "echo", 1, echo_stream, echo_dg },
+#endif
+#if ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_DISCARD
+       { "discard", 1, discard_stream, discard_dg },
+#endif
+#if ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_CHARGEN
+       { "chargen", 1, chargen_stream, chargen_dg },
+#endif
+#if ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_TIME
+       { "time", 0, machtime_stream, machtime_dg },
+#endif
+#if ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_DAYTIME
+       { "daytime", 0, daytime_stream, daytime_dg },
+#endif
+};
+#endif /* INETD_BUILTINS_ENABLED */
+
+struct globals {
+       rlim_t rlim_ofile_cur;
+       struct rlimit rlim_ofile;
+       servtab_t *serv_list;
+       int global_queuelen;
+       int maxsock;            /* max fd# in allsock, -1: unknown */
+       /* whenever maxsock grows, prev_maxsock is set to new maxsock,
+        * but if maxsock is set to -1, prev_maxsock is not changed */
+       int prev_maxsock;
+       unsigned max_concurrency;
+       smallint alarm_armed;
+       uid_t real_uid; /* user ID who ran us */
+       const char *config_filename;
+       parser_t *parser;
+       char *default_local_hostname;
+#if ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_CHARGEN
+       char *end_ring;
+       char *ring_pos;
+       char ring[128];
+#endif
+       fd_set allsock;
+       /* Used in next_line(), and as scratch read buffer */
+       char line[256];          /* _at least_ 256, see LINE_SIZE */
+};
+#define G (*(struct globals*)&bb_common_bufsiz1)
+enum { LINE_SIZE = COMMON_BUFSIZE - offsetof(struct globals, line) };
+struct BUG_G_too_big {
+       char BUG_G_too_big[sizeof(G) <= COMMON_BUFSIZE ? 1 : -1];
+};
+#define rlim_ofile_cur  (G.rlim_ofile_cur )
+#define rlim_ofile      (G.rlim_ofile     )
+#define serv_list       (G.serv_list      )
+#define global_queuelen (G.global_queuelen)
+#define maxsock         (G.maxsock        )
+#define prev_maxsock    (G.prev_maxsock   )
+#define max_concurrency (G.max_concurrency)
+#define alarm_armed     (G.alarm_armed    )
+#define real_uid        (G.real_uid       )
+#define config_filename (G.config_filename)
+#define parser          (G.parser         )
+#define default_local_hostname (G.default_local_hostname)
+#define first_ps_byte   (G.first_ps_byte  )
+#define last_ps_byte    (G.last_ps_byte   )
+#define end_ring        (G.end_ring       )
+#define ring_pos        (G.ring_pos       )
+#define ring            (G.ring           )
+#define allsock         (G.allsock        )
+#define line            (G.line           )
+#define INIT_G() do { \
+       rlim_ofile_cur = OPEN_MAX; \
+       global_queuelen = 128; \
+       config_filename = "/etc/inetd.conf"; \
+} while (0)
+
+static void maybe_close(int fd)
+{
+       if (fd >= 0)
+               close(fd);
+}
+
+// TODO: move to libbb?
+static len_and_sockaddr *xzalloc_lsa(int family)
+{
+       len_and_sockaddr *lsa;
+       int sz;
+
+       sz = sizeof(struct sockaddr_in);
+       if (family == AF_UNIX)
+               sz = sizeof(struct sockaddr_un);
+#if ENABLE_FEATURE_IPV6
+       if (family == AF_INET6)
+               sz = sizeof(struct sockaddr_in6);
+#endif
+       lsa = xzalloc(LSA_LEN_SIZE + sz);
+       lsa->len = sz;
+       lsa->u.sa.sa_family = family;
+       return lsa;
+}
+
+static void rearm_alarm(void)
+{
+       if (!alarm_armed) {
+               alarm_armed = 1;
+               alarm(RETRYTIME);
+       }
+}
+
+static void block_CHLD_HUP_ALRM(sigset_t *m)
+{
+       sigemptyset(m);
+       sigaddset(m, SIGCHLD);
+       sigaddset(m, SIGHUP);
+       sigaddset(m, SIGALRM);
+       sigprocmask(SIG_BLOCK, m, m); /* old sigmask is stored in m */
+}
+
+static void restore_sigmask(sigset_t *m)
+{
+       sigprocmask(SIG_SETMASK, m, NULL);
+}
+
+#if ENABLE_FEATURE_INETD_RPC
+static void register_rpc(servtab_t *sep)
+{
+       int n;
+       struct sockaddr_in ir_sin;
+       socklen_t size;
+
+       size = sizeof(ir_sin);
+       if (getsockname(sep->se_fd, (struct sockaddr *) &ir_sin, &size) < 0) {
+               bb_perror_msg("getsockname");
+               return;
+       }
+
+       for (n = sep->se_rpcver_lo; n <= sep->se_rpcver_hi; n++) {
+               pmap_unset(sep->se_rpcprog, n);
+               if (!pmap_set(sep->se_rpcprog, n, sep->se_proto_no, 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, sep->se_proto_no, ntohs(ir_sin.sin_port));
+       }
+}
+
+static void unregister_rpc(servtab_t *sep)
+{
+       int n;
+
+       for (n = sep->se_rpcver_lo; n <= sep->se_rpcver_hi; n++) {
+               if (!pmap_unset(sep->se_rpcprog, n))
+                       bb_perror_msg("pmap_unset(%u,%u)", sep->se_rpcprog, n);
+       }
+}
+#endif /* FEATURE_INETD_RPC */
+
+static void bump_nofile(void)
+{
+       enum { FD_CHUNK = 32 };
+       struct rlimit rl;
+
+       /* Never fails under Linux (except if you pass it bad arguments) */
+       getrlimit(RLIMIT_NOFILE, &rl);
+       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("can't extend file limit, max = %d",
+                                               (int) rl.rlim_cur);
+               return;
+       }
+
+       if (setrlimit(RLIMIT_NOFILE, &rl) < 0) {
+               bb_perror_msg("setrlimit");
+               return;
+       }
+
+       rlim_ofile_cur = rl.rlim_cur;
+}
+
+static void remove_fd_from_set(int fd)
+{
+       if (fd >= 0) {
+               FD_CLR(fd, &allsock);
+               maxsock = -1;
+       }
+}
+
+static void add_fd_to_set(int fd)
+{
+       if (fd >= 0) {
+               FD_SET(fd, &allsock);
+               if (maxsock >= 0 && fd > maxsock) {
+                       prev_maxsock = maxsock = fd;
+                       if ((rlim_t)fd > rlim_ofile_cur - FD_MARGIN)
+                               bump_nofile();
+               }
+       }
+}
+
+static void recalculate_maxsock(void)
+{
+       int fd = 0;
+
+       /* We may have no services, in this case maxsock should still be >= 0
+        * (code elsewhere is not happy with maxsock == -1) */
+       maxsock = 0;
+       while (fd <= prev_maxsock) {
+               if (FD_ISSET(fd, &allsock))
+                       maxsock = fd;
+               fd++;
+       }
+       prev_maxsock = maxsock;
+       if ((rlim_t)maxsock > rlim_ofile_cur - FD_MARGIN)
+               bump_nofile();
+}
+
+static void prepare_socket_fd(servtab_t *sep)
+{
+       int r, fd;
+
+       fd = socket(sep->se_family, sep->se_socktype, 0);
+       if (fd < 0) {
+               bb_perror_msg("socket");
+               return;
+       }
+       setsockopt_reuseaddr(fd);
+
+#if ENABLE_FEATURE_INETD_RPC
+       if (is_rpc_service(sep)) {
+               struct passwd *pwd;
+
+               /* zero out the port for all RPC services; let bind()
+                * find one. */
+               set_nport(sep->se_lsa, 0);
+
+               /* for RPC services, attempt to use a reserved port
+                * if they are going to be running as root. */
+               if (real_uid == 0 && sep->se_family == AF_INET
+                && (pwd = getpwnam(sep->se_user)) != NULL
+                && pwd->pw_uid == 0
+               ) {
+                       r = bindresvport(fd, &sep->se_lsa->u.sin);
+               } else {
+                       r = bind(fd, &sep->se_lsa->u.sa, sep->se_lsa->len);
+               }
+               if (r == 0) {
+                       int saveerrno = errno;
+                       /* update lsa with port# */
+                       getsockname(fd, &sep->se_lsa->u.sa, &sep->se_lsa->len);
+                       errno = saveerrno;
+               }
+       } else
+#endif
+       {
+               if (sep->se_family == AF_UNIX) {
+                       struct sockaddr_un *sun;
+                       sun = (struct sockaddr_un*)&(sep->se_lsa->u.sa);
+                       unlink(sun->sun_path);
+               }
+               r = bind(fd, &sep->se_lsa->u.sa, sep->se_lsa->len);
+       }
+       if (r < 0) {
+               bb_perror_msg("%s/%s: bind",
+                               sep->se_service, sep->se_proto);
+               close(fd);
+               rearm_alarm();
+               return;
+       }
+       if (sep->se_socktype == SOCK_STREAM)
+               listen(fd, global_queuelen);
+
+       add_fd_to_set(fd);
+       sep->se_fd = fd;
+}
+
+static int reopen_config_file(void)
+{
+       free(default_local_hostname);
+       default_local_hostname = xstrdup("*");
+       if (parser != NULL)
+               config_close(parser);
+       parser = config_open(config_filename);
+       return (parser != NULL);
+}
+
+static void close_config_file(void)
+{
+       if (parser) {
+               config_close(parser);
+               parser = NULL;
+       }
+}
+
+static void free_servtab_strings(servtab_t *cp)
+{
+       int i;
+
+       free(cp->se_local_hostname);
+       free(cp->se_service);
+       free(cp->se_proto);
+       free(cp->se_user);
+       free(cp->se_group);
+       free(cp->se_lsa); /* not a string in fact */
+       free(cp->se_program);
+       for (i = 0; i < MAXARGV; i++)
+               free(cp->se_argv[i]);
+}
+
+static servtab_t *new_servtab(void)
+{
+       servtab_t *newtab = xzalloc(sizeof(servtab_t));
+       newtab->se_fd = -1; /* paranoia */
+       return newtab;
+}
+
+static servtab_t *dup_servtab(servtab_t *sep)
+{
+       servtab_t *newtab;
+       int argc;
+
+       newtab = new_servtab();
+       *newtab = *sep; /* struct copy */
+       /* deep-copying strings */
+       newtab->se_service = xstrdup(newtab->se_service);
+       newtab->se_proto = xstrdup(newtab->se_proto);
+       newtab->se_user = xstrdup(newtab->se_user);
+       newtab->se_group = xstrdup(newtab->se_group);
+       newtab->se_program = xstrdup(newtab->se_program);
+       for (argc = 0; argc <= MAXARGV; argc++)
+               newtab->se_argv[argc] = xstrdup(newtab->se_argv[argc]);
+       /* NB: se_fd, se_hostaddr and se_next are always
+        * overwrittend by callers, so we don't bother resetting them
+        * to NULL/0/-1 etc */
+
+       return newtab;
+}
+
+/* gcc generates much more code if this is inlined */
+static servtab_t *parse_one_line(void)
+{
+       int argc;
+       char *token[6+MAXARGV];
+       char *p, *arg;
+       char *hostdelim;
+       servtab_t *sep;
+       servtab_t *nsep;
+ new:
+       sep = new_servtab();
+ more:
+       argc = config_read(parser, token, 6+MAXARGV, 1, "# \t", PARSE_NORMAL);
+       if (!argc) {
+               free(sep);
+               return NULL;
+       }
+
+       /* [host:]service socktype proto wait user[:group] prog [args] */
+       /* Check for "host:...." line */
+       arg = token[0];
+       hostdelim = strrchr(arg, ':');
+       if (hostdelim) {
+               *hostdelim = '\0';
+               sep->se_local_hostname = xstrdup(arg);
+               arg = hostdelim + 1;
+               if (*arg == '\0' && argc == 1) {
+                       /* Line has just "host:", change the
+                        * default host for the following lines. */
+                       free(default_local_hostname);
+                       default_local_hostname = sep->se_local_hostname;
+                       goto more;
+               }
+       } else
+               sep->se_local_hostname = xstrdup(default_local_hostname);
+
+       /* service socktype proto wait user[:group] prog [args] */
+       sep->se_service = xstrdup(arg);
+
+       /* socktype proto wait user[:group] prog [args] */
+       if (argc < 6) {
+ parse_err:
+               bb_error_msg("parse error on line %u, line is ignored",
+                               parser->lineno);
+               free_servtab_strings(sep);
+               /* Just "goto more" can make sep to carry over e.g.
+                * "rpc"-ness (by having se_rpcver_lo != 0).
+                * We will be more paranoid: */
+               free(sep);
+               goto new;
+       }
+
+       {
+               static int8_t SOCK_xxx[] ALIGN1 = {
+                       -1,
+                       SOCK_STREAM, SOCK_DGRAM, SOCK_RDM,
+                       SOCK_SEQPACKET, SOCK_RAW
+               };
+               sep->se_socktype = SOCK_xxx[1 + index_in_strings(
+                       "stream""\0" "dgram""\0" "rdm""\0"
+                       "seqpacket""\0" "raw""\0"
+                       , token[1])];
+       }
+
+       /* {unix,[rpc/]{tcp,udp}[6]} wait user[:group] prog [args] */
+       sep->se_proto = arg = xstrdup(token[2]);
+       if (strcmp(arg, "unix") == 0) {
+               sep->se_family = AF_UNIX;
+       } else {
+               char *six;
+               sep->se_family = AF_INET;
+               six = last_char_is(arg, '6');
+               if (six) {
+#if ENABLE_FEATURE_IPV6
+                       *six = '\0';
+                       sep->se_family = AF_INET6;
+#else
+                       bb_error_msg("%s: no support for IPv6", sep->se_proto);
+                       goto parse_err;
+#endif
+               }
+               if (strncmp(arg, "rpc/", 4) == 0) {
+#if ENABLE_FEATURE_INETD_RPC
+                       unsigned n;
+                       arg += 4;
+                       p = strchr(sep->se_service, '/');
+                       if (p == NULL) {
+                               bb_error_msg("no rpc version: '%s'", sep->se_service);
+                               goto parse_err;
+                       }
+                       *p++ = '\0';
+                       n = bb_strtou(p, &p, 10);
+                       if (n > INT_MAX) {
+ bad_ver_spec:
+                               bb_error_msg("bad rpc version");
+                               goto parse_err;
+                       }
+                       sep->se_rpcver_lo = sep->se_rpcver_hi = n;
+                       if (*p == '-') {
+                               p++;
+                               n = bb_strtou(p, &p, 10);
+                               if (n > INT_MAX || (int)n < sep->se_rpcver_lo)
+                                       goto bad_ver_spec;
+                               sep->se_rpcver_hi = n;
+                       }
+                       if (*p != '\0')
+                               goto bad_ver_spec;
+#else
+                       bb_error_msg("no support for rpc services");
+                       goto parse_err;
+#endif
+               }
+               /* we don't really need getprotobyname()! */
+               if (strcmp(arg, "tcp") == 0)
+                       sep->se_proto_no = IPPROTO_TCP; /* = 6 */
+               if (strcmp(arg, "udp") == 0)
+                       sep->se_proto_no = IPPROTO_UDP; /* = 17 */
+               if (six)
+                       *six = '6';
+               if (!sep->se_proto_no) /* not tcp/udp?? */
+                       goto parse_err;
+       }
+
+       /* [no]wait[.max] user[:group] prog [args] */
+       arg = token[3];
+       sep->se_max = max_concurrency;
+       p = strchr(arg, '.');
+       if (p) {
+               *p++ = '\0';
+               sep->se_max = bb_strtou(p, NULL, 10);
+               if (errno)
+                       goto parse_err;
+       }
+       sep->se_wait = (arg[0] != 'n' || arg[1] != 'o');
+       if (!sep->se_wait) /* "no" seen */
+               arg += 2;
+       if (strcmp(arg, "wait") != 0)
+               goto parse_err;
+
+       /* user[:group] prog [args] */
+       sep->se_user = xstrdup(token[4]);
+       arg = strchr(sep->se_user, '.');
+       if (arg == NULL)
+               arg = strchr(sep->se_user, ':');
+       if (arg) {
+               *arg++ = '\0';
+               sep->se_group = xstrdup(arg);
+       }
+
+       /* prog [args] */
+       sep->se_program = xstrdup(token[5]);
+#ifdef INETD_BUILTINS_ENABLED
+       if (strcmp(sep->se_program, "internal") == 0
+        && strlen(sep->se_service) <= 7
+        && (sep->se_socktype == SOCK_STREAM
+            || sep->se_socktype == SOCK_DGRAM)
+       ) {
+               unsigned i;
+               for (i = 0; i < ARRAY_SIZE(builtins); i++)
+                       if (strncmp(builtins[i].bi_service7, sep->se_service, 7) == 0)
+                               goto found_bi;
+               bb_error_msg("unknown internal service %s", sep->se_service);
+               goto parse_err;
+ found_bi:
+               sep->se_builtin = &builtins[i];
+               /* stream builtins must be "nowait", dgram must be "wait" */
+               if (sep->se_wait != (sep->se_socktype == SOCK_DGRAM))
+                       goto parse_err;
+       }
+#endif
+       argc = 0;
+       while ((arg = token[6+argc]) != NULL && argc < MAXARGV)
+               sep->se_argv[argc++] = xstrdup(arg);
+
+       /* catch mixups. "<service> stream udp ..." == wtf */
+       if (sep->se_socktype == SOCK_STREAM) {
+               if (sep->se_proto_no == IPPROTO_UDP)
+                       goto parse_err;
+       }
+       if (sep->se_socktype == SOCK_DGRAM) {
+               if (sep->se_proto_no == IPPROTO_TCP)
+                       goto parse_err;
+       }
+
+//     bb_info_msg(
+//             "ENTRY[%s][%s][%s][%d][%d][%d][%d][%d][%s][%s][%s]",
+//             sep->se_local_hostname, sep->se_service, sep->se_proto, sep->se_wait, sep->se_proto_no,
+//             sep->se_max, sep->se_count, sep->se_time, sep->se_user, sep->se_group, sep->se_program);
+
+       /* check if the hostname specifier is a comma separated list
+        * of hostnames. we'll make new entries for each address. */
+       while ((hostdelim = strrchr(sep->se_local_hostname, ',')) != NULL) {
+               nsep = dup_servtab(sep);
+               /* NUL terminate the hostname field of the existing entry,
+                * and make a dup for the new entry. */
+               *hostdelim++ = '\0';
+               nsep->se_local_hostname = xstrdup(hostdelim);
+               nsep->se_next = sep->se_next;
+               sep->se_next = nsep;
+       }
+
+       /* was doing it here: */
+       /* DNS resolution, create copies for each IP address */
+       /* IPv6-ization destroyed it :( */
+
+       return sep;
+}
+
+static servtab_t *insert_in_servlist(servtab_t *cp)
+{
+       servtab_t *sep;
+       sigset_t omask;
+
+       sep = new_servtab();
+       *sep = *cp; /* struct copy */
+       sep->se_fd = -1;
+#if ENABLE_FEATURE_INETD_RPC
+       sep->se_rpcprog = -1;
+#endif
+       block_CHLD_HUP_ALRM(&omask);
+       sep->se_next = serv_list;
+       serv_list = sep;
+       restore_sigmask(&omask);
+       return sep;
+}
+
+static int same_serv_addr_proto(servtab_t *old, servtab_t *new)
+{
+       if (strcmp(old->se_local_hostname, new->se_local_hostname) != 0)
+               return 0;
+       if (strcmp(old->se_service, new->se_service) != 0)
+               return 0;
+       if (strcmp(old->se_proto, new->se_proto) != 0)
+               return 0;
+       return 1;
+}
+
+static void reread_config_file(int sig UNUSED_PARAM)
+{
+       servtab_t *sep, *cp, **sepp;
+       len_and_sockaddr *lsa;
+       sigset_t omask;
+       unsigned n;
+       uint16_t port;
+       int save_errno = errno;
+
+       if (!reopen_config_file())
+               goto ret;
+       for (sep = serv_list; sep; sep = sep->se_next)
+               sep->se_checked = 0;
+
+       goto first_line;
+       while (1) {
+               if (cp == NULL) {
+ first_line:
+                       cp = parse_one_line();
+                       if (cp == NULL)
+                               break;
+               }
+               for (sep = serv_list; sep; sep = sep->se_next)
+                       if (same_serv_addr_proto(sep, cp))
+                               goto equal_servtab;
+               /* not an "equal" servtab */
+               sep = insert_in_servlist(cp);
+               goto after_check;
+ equal_servtab:
+               {
+                       int i;
+
+                       block_CHLD_HUP_ALRM(&omask);
+#if ENABLE_FEATURE_INETD_RPC
+                       if (is_rpc_service(sep))
+                               unregister_rpc(sep);
+                       sep->se_rpcver_lo = cp->se_rpcver_lo;
+                       sep->se_rpcver_hi = cp->se_rpcver_hi;
+#endif
+                       if (cp->se_wait == 0) {
+                               /* New config says "nowait". If old one
+                                * was "wait", we currently may be waiting
+                                * for a child (and not accepting connects).
+                                * Stop waiting, start listening again.
+                                * (if it's not true, this op is harmless) */
+                               add_fd_to_set(sep->se_fd);
+                       }
+                       sep->se_wait = cp->se_wait;
+                       sep->se_max = cp->se_max;
+                       /* string fields need more love - we don't want to leak them */
+#define SWAP(type, a, b) do { type c = (type)a; a = (type)b; b = (type)c; } while (0)
+                       SWAP(char*, sep->se_user, cp->se_user);
+                       SWAP(char*, sep->se_group, cp->se_group);
+                       SWAP(char*, sep->se_program, cp->se_program);
+                       for (i = 0; i < MAXARGV; i++)
+                               SWAP(char*, sep->se_argv[i], cp->se_argv[i]);
+#undef SWAP
+                       restore_sigmask(&omask);
+                       free_servtab_strings(cp);
+               }
+ after_check:
+               /* cp->string_fields are consumed by insert_in_servlist()
+                * or freed at this point, cp itself is not yet freed. */
+               sep->se_checked = 1;
+
+               /* create new len_and_sockaddr */
+               switch (sep->se_family) {
+                       struct sockaddr_un *sun;
+               case AF_UNIX:
+                       lsa = xzalloc_lsa(AF_UNIX);
+                       sun = (struct sockaddr_un*)&lsa->u.sa;
+                       safe_strncpy(sun->sun_path, sep->se_service, sizeof(sun->sun_path));
+                       break;
+
+               default: /* case AF_INET, case AF_INET6 */
+                       n = bb_strtou(sep->se_service, NULL, 10);
+#if ENABLE_FEATURE_INETD_RPC
+                       if (is_rpc_service(sep)) {
+                               sep->se_rpcprog = n;
+                               if (errno) { /* se_service is not numeric */
+                                       struct rpcent *rp = getrpcbyname(sep->se_service);
+                                       if (rp == NULL) {
+                                               bb_error_msg("%s: unknown rpc service", sep->se_service);
+                                               goto next_cp;
+                                       }
+                                       sep->se_rpcprog = rp->r_number;
+                               }
+                               if (sep->se_fd == -1)
+                                       prepare_socket_fd(sep);
+                               if (sep->se_fd != -1)
+                                       register_rpc(sep);
+                               goto next_cp;
+                       }
+#endif
+                       /* what port to listen on? */
+                       port = htons(n);
+                       if (errno || n > 0xffff) { /* se_service is not numeric */
+                               char protoname[4];
+                               struct servent *sp;
+                               /* can result only in "tcp" or "udp": */
+                               safe_strncpy(protoname, sep->se_proto, 4);
+                               sp = getservbyname(sep->se_service, protoname);
+                               if (sp == NULL) {
+                                       bb_error_msg("%s/%s: unknown service",
+                                                       sep->se_service, sep->se_proto);
+                                       goto next_cp;
+                               }
+                               port = sp->s_port;
+                       }
+                       if (LONE_CHAR(sep->se_local_hostname, '*')) {
+                               lsa = xzalloc_lsa(sep->se_family);
+                               set_nport(lsa, port);
+                       } else {
+                               lsa = host_and_af2sockaddr(sep->se_local_hostname,
+                                               ntohs(port), sep->se_family);
+                               if (!lsa) {
+                                       bb_error_msg("%s/%s: unknown host '%s'",
+                                               sep->se_service, sep->se_proto,
+                                               sep->se_local_hostname);
+                                       goto next_cp;
+                               }
+                       }
+                       break;
+               } /* end of "switch (sep->se_family)" */
+
+               /* did lsa change? Then close/open */
+               if (sep->se_lsa == NULL
+                || lsa->len != sep->se_lsa->len
+                || memcmp(&lsa->u.sa, &sep->se_lsa->u.sa, lsa->len) != 0
+               ) {
+                       remove_fd_from_set(sep->se_fd);
+                       maybe_close(sep->se_fd);
+                       free(sep->se_lsa);
+                       sep->se_lsa = lsa;
+                       sep->se_fd = -1;
+               } else {
+                       free(lsa);
+               }
+               if (sep->se_fd == -1)
+                       prepare_socket_fd(sep);
+ next_cp:
+               sep = cp->se_next;
+               free(cp);
+               cp = sep;
+       } /* end of "while (1) parse lines" */
+       close_config_file();
+
+       /* Purge anything not looked at above - these are stale entries,
+        * new config file doesnt have them. */
+       block_CHLD_HUP_ALRM(&omask);
+       sepp = &serv_list;
+       while ((sep = *sepp)) {
+               if (sep->se_checked) {
+                       sepp = &sep->se_next;
+                       continue;
+               }
+               *sepp = sep->se_next;
+               remove_fd_from_set(sep->se_fd);
+               maybe_close(sep->se_fd);
+#if ENABLE_FEATURE_INETD_RPC
+               if (is_rpc_service(sep))
+                       unregister_rpc(sep);
+#endif
+               if (sep->se_family == AF_UNIX)
+                       unlink(sep->se_service);
+               free_servtab_strings(sep);
+               free(sep);
+       }
+       restore_sigmask(&omask);
+ ret:
+       errno = save_errno;
+}
+
+static void reap_child(int sig UNUSED_PARAM)
+{
+       pid_t pid;
+       int status;
+       servtab_t *sep;
+       int save_errno = errno;
+
+       for (;;) {
+               pid = wait_any_nohang(&status);
+               if (pid <= 0)
+                       break;
+               for (sep = serv_list; sep; sep = sep->se_next) {
+                       if (sep->se_wait != pid)
+                               continue;
+                       /* One of our "wait" services */
+                       if (WIFEXITED(status) && WEXITSTATUS(status))
+                               bb_error_msg("%s: exit status 0x%x",
+                                               sep->se_program, WEXITSTATUS(status));
+                       else if (WIFSIGNALED(status))
+                               bb_error_msg("%s: exit signal 0x%x",
+                                               sep->se_program, WTERMSIG(status));
+                       sep->se_wait = 1;
+                       add_fd_to_set(sep->se_fd);
+                       break;
+               }
+       }
+       errno = save_errno;
+}
+
+static void retry_network_setup(int sig UNUSED_PARAM)
+{
+       int save_errno = errno;
+       servtab_t *sep;
+
+       alarm_armed = 0;
+       for (sep = serv_list; sep; sep = sep->se_next) {
+               if (sep->se_fd == -1) {
+                       prepare_socket_fd(sep);
+#if ENABLE_FEATURE_INETD_RPC
+                       if (sep->se_fd != -1 && is_rpc_service(sep))
+                               register_rpc(sep);
+#endif
+               }
+       }
+       errno = save_errno;
+}
+
+static void clean_up_and_exit(int sig UNUSED_PARAM)
+{
+       servtab_t *sep;
+
+       /* XXX signal race walking sep list */
+       for (sep = serv_list; sep; sep = sep->se_next) {
+               if (sep->se_fd == -1)
+                       continue;
+
+               switch (sep->se_family) {
+               case AF_UNIX:
+                       unlink(sep->se_service);
+                       break;
+               default: /* case AF_INET, AF_INET6 */
+#if ENABLE_FEATURE_INETD_RPC
+                       if (sep->se_wait == 1 && is_rpc_service(sep))
+                               unregister_rpc(sep);   /* XXX signal race */
+#endif
+                       break;
+               }
+               if (ENABLE_FEATURE_CLEAN_UP)
+                       close(sep->se_fd);
+       }
+       remove_pidfile(_PATH_INETDPID);
+       exit(EXIT_SUCCESS);
+}
+
+int inetd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int inetd_main(int argc UNUSED_PARAM, char **argv)
+{
+       struct sigaction sa, saved_pipe_handler;
+       servtab_t *sep, *sep2;
+       struct passwd *pwd;
+       struct group *grp = grp; /* for compiler */
+       int opt;
+       pid_t pid;
+       sigset_t omask;
+
+       INIT_G();
+
+       real_uid = getuid();
+       if (real_uid != 0) /* run by non-root user */
+               config_filename = NULL;
+
+       opt_complementary = "R+:q+"; /* -q N, -R N */
+       opt = getopt32(argv, "R:feq:", &max_concurrency, &global_queuelen);
+       argv += optind;
+       //argc -= optind;
+       if (argv[0])
+               config_filename = argv[0];
+       if (config_filename == NULL)
+               bb_error_msg_and_die("non-root must specify config file");
+       if (!(opt & 2))
+               bb_daemonize_or_rexec(0, argv - optind);
+       else
+               bb_sanitize_stdio();
+       if (!(opt & 4)) {
+               openlog(applet_name, LOG_PID, LOG_DAEMON);
+               logmode = LOGMODE_SYSLOG;
+       }
+
+       if (real_uid == 0) {
+               /* run by root, ensure groups vector gets trashed */
+               gid_t gid = getgid();
+               setgroups(1, &gid);
+       }
+
+       write_pidfile(_PATH_INETDPID);
+
+       /* never fails under Linux (except if you pass it bad arguments) */
+       getrlimit(RLIMIT_NOFILE, &rlim_ofile);
+       rlim_ofile_cur = rlim_ofile.rlim_cur;
+       if (rlim_ofile_cur == RLIM_INFINITY)    /* ! */
+               rlim_ofile_cur = OPEN_MAX;
+
+       memset(&sa, 0, sizeof(sa));
+       /*sigemptyset(&sa.sa_mask); - memset did it */
+       sigaddset(&sa.sa_mask, SIGALRM);
+       sigaddset(&sa.sa_mask, SIGCHLD);
+       sigaddset(&sa.sa_mask, SIGHUP);
+       sa.sa_handler = retry_network_setup;
+       sigaction_set(SIGALRM, &sa);
+       sa.sa_handler = reread_config_file;
+       sigaction_set(SIGHUP, &sa);
+       sa.sa_handler = reap_child;
+       sigaction_set(SIGCHLD, &sa);
+       sa.sa_handler = clean_up_and_exit;
+       sigaction_set(SIGTERM, &sa);
+       sa.sa_handler = clean_up_and_exit;
+       sigaction_set(SIGINT, &sa);
+       sa.sa_handler = SIG_IGN;
+       sigaction(SIGPIPE, &sa, &saved_pipe_handler);
+
+       reread_config_file(SIGHUP); /* load config from file */
+
+       for (;;) {
+               int ready_fd_cnt;
+               int ctrl, accepted_fd, new_udp_fd;
+               fd_set readable;
+
+               if (maxsock < 0)
+                       recalculate_maxsock();
+
+               readable = allsock; /* struct copy */
+               /* if there are no fds to wait on, we will block
+                * until signal wakes us up (maxsock == 0, but readable
+                * never contains fds 0 and 1...) */
+               ready_fd_cnt = select(maxsock + 1, &readable, NULL, NULL, NULL);
+               if (ready_fd_cnt < 0) {
+                       if (errno != EINTR) {
+                               bb_perror_msg("select");
+                               sleep(1);
+                       }
+                       continue;
+               }
+
+               for (sep = serv_list; ready_fd_cnt && sep; sep = sep->se_next) {
+                       if (sep->se_fd == -1 || !FD_ISSET(sep->se_fd, &readable))
+                               continue;
+
+                       ready_fd_cnt--;
+                       ctrl = sep->se_fd;
+                       accepted_fd = -1;
+                       new_udp_fd = -1;
+                       if (!sep->se_wait) {
+                               if (sep->se_socktype == SOCK_STREAM) {
+                                       ctrl = accepted_fd = accept(sep->se_fd, NULL, NULL);
+                                       if (ctrl < 0) {
+                                               if (errno != EINTR)
+                                                       bb_perror_msg("accept (for %s)", sep->se_service);
+                                               continue;
+                                       }
+                               }
+                               /* "nowait" udp */
+                               if (sep->se_socktype == SOCK_DGRAM
+                                && sep->se_family != AF_UNIX
+                               ) {
+/* How udp "nowait" works:
+ * child peeks at (received and buffered by kernel) UDP packet,
+ * performs connect() on the socket so that it is linked only
+ * to this peer. But this also affects parent, because descriptors
+ * are shared after fork() a-la dup(). When parent performs
+ * select(), it will see this descriptor connected to the peer (!)
+ * and still readable, will act on it and mess things up
+ * (can create many copies of same child, etc).
+ * Parent must create and use new socket instead. */
+                                       new_udp_fd = socket(sep->se_family, SOCK_DGRAM, 0);
+                                       if (new_udp_fd < 0) { /* error: eat packet, forget about it */
+ udp_err:
+                                               recv(sep->se_fd, line, LINE_SIZE, MSG_DONTWAIT);
+                                               continue;
+                                       }
+                                       setsockopt_reuseaddr(new_udp_fd);
+                                       /* TODO: better do bind after vfork in parent,
+                                        * so that we don't have two wildcard bound sockets
+                                        * even for a brief moment? */
+                                       if (bind(new_udp_fd, &sep->se_lsa->u.sa, sep->se_lsa->len) < 0) {
+                                               close(new_udp_fd);
+                                               goto udp_err;
+                                       }
+                               }
+                       }
+
+                       block_CHLD_HUP_ALRM(&omask);
+                       pid = 0;
+#ifdef INETD_BUILTINS_ENABLED
+                       /* do we need to fork? */
+                       if (sep->se_builtin == NULL
+                        || (sep->se_socktype == SOCK_STREAM
+                            && sep->se_builtin->bi_fork))
+#endif
+                       {
+                               if (sep->se_max != 0) {
+                                       if (++sep->se_count == 1)
+                                               sep->se_time = monotonic_sec();
+                                       else if (sep->se_count >= sep->se_max) {
+                                               unsigned now = monotonic_sec();
+                                               /* did we accumulate se_max connects too quickly? */
+                                               if (now - sep->se_time <= CNT_INTERVAL) {
+                                                       bb_error_msg("%s/%s: too many connections, pausing",
+                                                                       sep->se_service, sep->se_proto);
+                                                       remove_fd_from_set(sep->se_fd);
+                                                       close(sep->se_fd);
+                                                       sep->se_fd = -1;
+                                                       sep->se_count = 0;
+                                                       rearm_alarm(); /* will revive it in RETRYTIME sec */
+                                                       restore_sigmask(&omask);
+                                                       maybe_close(accepted_fd);
+                                                       continue; /* -> check next fd in fd set */
+                                               }
+                                               sep->se_count = 0;
+                                       }
+                               }
+                               /* on NOMMU, streamed chargen
+                                * builtin wouldn't work, but it is
+                                * not allowed on NOMMU (ifdefed out) */
+#ifdef INETD_BUILTINS_ENABLED
+                               if (BB_MMU && sep->se_builtin)
+                                       pid = fork();
+                               else
+#endif
+                                       pid = vfork();
+
+                               if (pid < 0) { /* fork error */
+                                       bb_perror_msg("fork");
+                                       sleep(1);
+                                       restore_sigmask(&omask);
+                                       maybe_close(accepted_fd);
+                                       continue; /* -> check next fd in fd set */
+                               }
+                               if (pid == 0)
+                                       pid--; /* -1: "we did fork and we are child" */
+                       }
+                       /* if pid == 0 here, we never forked */
+
+                       if (pid > 0) { /* parent */
+                               if (sep->se_wait) {
+                                       /* tcp wait: we passed listening socket to child,
+                                        * will wait for child to terminate */
+                                       sep->se_wait = pid;
+                                       remove_fd_from_set(sep->se_fd);
+                               }
+                               if (new_udp_fd >= 0) {
+                                       /* udp nowait: child connected the socket,
+                                        * we created and will use new, unconnected one */
+                                       xmove_fd(new_udp_fd, sep->se_fd);
+                               }
+                               restore_sigmask(&omask);
+                               maybe_close(accepted_fd);
+                               continue; /* -> check next fd in fd set */
+                       }
+
+                       /* we are either child or didn't vfork at all */
+#ifdef INETD_BUILTINS_ENABLED
+                       if (sep->se_builtin) {
+                               if (pid) { /* "pid" is -1: we did vfork */
+                                       close(sep->se_fd); /* listening socket */
+                                       logmode = LOGMODE_NONE; /* make xwrite etc silent */
+                               }
+                               restore_sigmask(&omask);
+                               if (sep->se_socktype == SOCK_STREAM)
+                                       sep->se_builtin->bi_stream_fn(ctrl, sep);
+                               else
+                                       sep->se_builtin->bi_dgram_fn(ctrl, sep);
+                               if (pid) /* we did vfork */
+                                       _exit(EXIT_FAILURE);
+                               maybe_close(accepted_fd);
+                               continue; /* -> check next fd in fd set */
+                       }
+#endif
+                       /* child */
+                       setsid();
+                       /* "nowait" udp */
+                       if (new_udp_fd >= 0) {
+                               len_and_sockaddr *lsa = xzalloc_lsa(sep->se_family);
+                               /* peek at the packet and remember peer addr */
+                               int r = recvfrom(ctrl, NULL, 0, MSG_PEEK|MSG_DONTWAIT,
+                                       &lsa->u.sa, &lsa->len);
+                               if (r < 0)
+                                       goto do_exit1;
+                               /* make this socket "connected" to peer addr:
+                                * only packets from this peer will be recv'ed,
+                                * and bare write()/send() will work on it */
+                               connect(ctrl, &lsa->u.sa, lsa->len);
+                               free(lsa);
+                       }
+                       /* prepare env and exec program */
+                       pwd = getpwnam(sep->se_user);
+                       if (pwd == NULL) {
+                               bb_error_msg("%s: no such %s", sep->se_user, "user");
+                               goto do_exit1;
+                       }
+                       if (sep->se_group && (grp = getgrnam(sep->se_group)) == NULL) {
+                               bb_error_msg("%s: no such %s", sep->se_group, "group");
+                               goto do_exit1;
+                       }
+                       if (real_uid != 0 && real_uid != pwd->pw_uid) {
+                               /* a user running private inetd */
+                               bb_error_msg("non-root must run services as himself");
+                               goto do_exit1;
+                       }
+                       if (pwd->pw_uid) {
+                               if (sep->se_group)
+                                       pwd->pw_gid = grp->gr_gid;
+                               /* initgroups, setgid, setuid: */
+                               change_identity(pwd);
+                       } else if (sep->se_group) {
+                               xsetgid(grp->gr_gid);
+                               setgroups(1, &grp->gr_gid);
+                       }
+                       if (rlim_ofile.rlim_cur != rlim_ofile_cur)
+                               if (setrlimit(RLIMIT_NOFILE, &rlim_ofile) < 0)
+                                       bb_perror_msg("setrlimit");
+                       closelog();
+                       xmove_fd(ctrl, 0);
+                       xdup2(0, 1);
+                       xdup2(0, 2);
+                       /* NB: among others, this loop closes listening socket
+                        * for nowait stream children */
+                       for (sep2 = serv_list; sep2; sep2 = sep2->se_next)
+                               maybe_close(sep2->se_fd);
+                       sigaction_set(SIGPIPE, &saved_pipe_handler);
+                       restore_sigmask(&omask);
+                       BB_EXECVP(sep->se_program, sep->se_argv);
+                       bb_perror_msg("exec %s", sep->se_program);
+ do_exit1:
+                       /* eat packet in udp case */
+                       if (sep->se_socktype != SOCK_STREAM)
+                               recv(0, line, LINE_SIZE, MSG_DONTWAIT);
+                       _exit(EXIT_FAILURE);
+               } /* for (sep = servtab...) */
+       } /* for (;;) */
+}
+
+#if !BB_MMU
+static const char *const cat_args[] = { "cat", NULL };
+#endif
+
+/*
+ * Internet services provided internally by inetd:
+ */
+#if ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_ECHO
+/* Echo service -- echo data back. */
+/* ARGSUSED */
+static void echo_stream(int s, servtab_t *sep UNUSED_PARAM)
+{
+#if BB_MMU
+       while (1) {
+               ssize_t sz = safe_read(s, line, LINE_SIZE);
+               if (sz <= 0)
+                       break;
+               xwrite(s, line, sz);
+       }
+#else
+       /* We are after vfork here! */
+       /* move network socket to stdin/stdout */
+       xmove_fd(s, STDIN_FILENO);
+       xdup2(STDIN_FILENO, STDOUT_FILENO);
+       /* no error messages please... */
+       close(STDERR_FILENO);
+       xopen(bb_dev_null, O_WRONLY);
+       BB_EXECVP("cat", (char**)cat_args);
+       /* on failure we return to main, which does exit(EXIT_FAILURE) */
+#endif
+}
+static void echo_dg(int s, servtab_t *sep)
+{
+       enum { BUFSIZE = 12*1024 }; /* for jumbo sized packets! :) */
+       char *buf = xmalloc(BUFSIZE); /* too big for stack */
+       int sz;
+       len_and_sockaddr *lsa = alloca(LSA_LEN_SIZE + sep->se_lsa->len);
+
+       lsa->len = sep->se_lsa->len;
+       /* dgram builtins are non-forking - DONT BLOCK! */
+       sz = recvfrom(s, buf, BUFSIZE, MSG_DONTWAIT, &lsa->u.sa, &lsa->len);
+       if (sz > 0)
+               sendto(s, buf, sz, 0, &lsa->u.sa, lsa->len);
+       free(buf);
+}
+#endif  /* FEATURE_INETD_SUPPORT_BUILTIN_ECHO */
+
+
+#if ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_DISCARD
+/* Discard service -- ignore data. */
+/* ARGSUSED */
+static void discard_stream(int s, servtab_t *sep UNUSED_PARAM)
+{
+#if BB_MMU
+       while (safe_read(s, line, LINE_SIZE) > 0)
+               continue;
+#else
+       /* We are after vfork here! */
+       /* move network socket to stdin */
+       xmove_fd(s, STDIN_FILENO);
+       /* discard output */
+       close(STDOUT_FILENO);
+       xopen(bb_dev_null, O_WRONLY);
+       /* no error messages please... */
+       xdup2(STDOUT_FILENO, STDERR_FILENO);
+       BB_EXECVP("cat", (char**)cat_args);
+       /* on failure we return to main, which does exit(EXIT_FAILURE) */
+#endif
+}
+/* ARGSUSED */
+static void discard_dg(int s, servtab_t *sep UNUSED_PARAM)
+{
+       /* dgram builtins are non-forking - DONT BLOCK! */
+       recv(s, line, LINE_SIZE, MSG_DONTWAIT);
+}
+#endif /* FEATURE_INETD_SUPPORT_BUILTIN_DISCARD */
+
+
+#if ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_CHARGEN
+#define LINESIZ 72
+static void init_ring(void)
+{
+       int i;
+
+       end_ring = ring;
+       for (i = 0; i <= 128; ++i)
+               if (isprint(i))
+                       *end_ring++ = i;
+}
+/* Character generator. MMU arches only. */
+/* ARGSUSED */
+static void chargen_stream(int s, servtab_t *sep UNUSED_PARAM)
+{
+       char *rs;
+       int len;
+       char text[LINESIZ + 2];
+
+       if (!end_ring) {
+               init_ring();
+               rs = ring;
+       }
+
+       text[LINESIZ] = '\r';
+       text[LINESIZ + 1] = '\n';
+       rs = ring;
+       for (;;) {
+               len = end_ring - rs;
+               if (len >= LINESIZ)
+                       memmove(text, rs, LINESIZ);
+               else {
+                       memmove(text, rs, len);
+                       memmove(text + len, ring, LINESIZ - len);
+               }
+               if (++rs == end_ring)
+                       rs = ring;
+               xwrite(s, text, sizeof(text));
+       }
+}
+/* ARGSUSED */
+static void chargen_dg(int s, servtab_t *sep)
+{
+       int len;
+       char text[LINESIZ + 2];
+       len_and_sockaddr *lsa = alloca(LSA_LEN_SIZE + sep->se_lsa->len);
+
+       /* Eat UDP packet which started it all */
+       /* dgram builtins are non-forking - DONT BLOCK! */
+       lsa->len = sep->se_lsa->len;
+       if (recvfrom(s, text, sizeof(text), MSG_DONTWAIT, &lsa->u.sa, &lsa->len) < 0)
+               return;
+
+       if (!end_ring) {
+               init_ring();
+               ring_pos = ring;
+       }
+
+       len = end_ring - ring_pos;
+       if (len >= LINESIZ)
+               memmove(text, ring_pos, LINESIZ);
+       else {
+               memmove(text, ring_pos, len);
+               memmove(text + len, ring, LINESIZ - len);
+       }
+       if (++ring_pos == end_ring)
+               ring_pos = ring;
+       text[LINESIZ] = '\r';
+       text[LINESIZ + 1] = '\n';
+       sendto(s, text, sizeof(text), 0, &lsa->u.sa, lsa->len);
+}
+#endif /* FEATURE_INETD_SUPPORT_BUILTIN_CHARGEN */
+
+
+#if ENABLE_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 uint32_t machtime(void)
+{
+       struct timeval tv;
+
+       gettimeofday(&tv, NULL);
+       return htonl((uint32_t)(tv.tv_sec + 2208988800));
+}
+/* ARGSUSED */
+static void machtime_stream(int s, servtab_t *sep UNUSED_PARAM)
+{
+       uint32_t result;
+
+       result = machtime();
+       full_write(s, &result, sizeof(result));
+}
+static void machtime_dg(int s, servtab_t *sep)
+{
+       uint32_t result;
+       len_and_sockaddr *lsa = alloca(LSA_LEN_SIZE + sep->se_lsa->len);
+
+       lsa->len = sep->se_lsa->len;
+       if (recvfrom(s, line, LINE_SIZE, MSG_DONTWAIT, &lsa->u.sa, &lsa->len) < 0)
+               return;
+
+       result = machtime();
+       sendto(s, &result, sizeof(result), 0, &lsa->u.sa, lsa->len);
+}
+#endif /* FEATURE_INETD_SUPPORT_BUILTIN_TIME */
+
+
+#if ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_DAYTIME
+/* Return human-readable time of day */
+/* ARGSUSED */
+static void daytime_stream(int s, servtab_t *sep UNUSED_PARAM)
+{
+       time_t t;
+
+       t = time(NULL);
+       fdprintf(s, "%.24s\r\n", ctime(&t));
+}
+static void daytime_dg(int s, servtab_t *sep)
+{
+       time_t t;
+       len_and_sockaddr *lsa = alloca(LSA_LEN_SIZE + sep->se_lsa->len);
+
+       lsa->len = sep->se_lsa->len;
+       if (recvfrom(s, line, LINE_SIZE, MSG_DONTWAIT, &lsa->u.sa, &lsa->len) < 0)
+               return;
+
+       t = time(NULL);
+       sprintf(line, "%.24s\r\n", ctime(&t));
+       sendto(s, line, strlen(line), 0, &lsa->u.sa, lsa->len);
+}
+#endif /* FEATURE_INETD_SUPPORT_BUILTIN_DAYTIME */
diff --git a/networking/interface.c b/networking/interface.c
new file mode 100644 (file)
index 0000000..ef187be
--- /dev/null
@@ -0,0 +1,1292 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * stolen from net-tools-1.59 and stripped down for busybox by
+ *                     Erik Andersen <andersen@codepoet.org>
+ *
+ * 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.
+ *
+ *
+ * Author:      Fred N. van Kempen, <waltje@uwalt.nl.mugnet.org>
+ *              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 <mrs.brisby@nimh.org>
+ *
+ * {1.34} - 19980630 - Arnaldo Carvalho de Melo <acme@conectiva.com.br>
+ *                     - 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 <net/if.h>
+#include <net/if_arp.h>
+#include "inet_common.h"
+#include "libbb.h"
+
+
+#if ENABLE_FEATURE_HWIB
+/* #include <linux/if_infiniband.h> */
+#undef INFINIBAND_ALEN
+#define INFINIBAND_ALEN 20
+#endif
+
+#if ENABLE_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
+
+/* Display an Internet socket address. */
+static const char* FAST_FUNC INET_sprint(struct sockaddr *sap, int numeric)
+{
+       static char *buff; /* defaults to NULL */
+
+       free(buff);
+       if (sap->sa_family == 0xFFFF || sap->sa_family == 0)
+               return "[NONE SET]";
+       buff = INET_rresolve((struct sockaddr_in *) sap, numeric, 0xffffff00);
+       return buff;
+}
+
+#ifdef UNUSED_AND_BUGGY
+static int INET_getsock(char *bufp, struct sockaddr *sap)
+{
+       char *sp = bufp, *bp;
+       unsigned int i;
+       unsigned val;
+       struct sockaddr_in *sock_in;
+
+       sock_in = (struct sockaddr_in *) sap;
+       sock_in->sin_family = AF_INET;
+       sock_in->sin_port = 0;
+
+       val = 0;
+       bp = (char *) &val;
+       for (i = 0; i < sizeof(sock_in->sin_addr.s_addr); i++) {
+               *sp = toupper(*sp);
+
+               if ((unsigned)(*sp - 'A') <= 5)
+                       bp[i] |= (int) (*sp - ('A' - 10));
+               else if (isdigit(*sp))
+                       bp[i] |= (int) (*sp - '0');
+               else
+                       return -1;
+
+               bp[i] <<= 4;
+               sp++;
+               *sp = toupper(*sp);
+
+               if ((unsigned)(*sp - 'A') <= 5)
+                       bp[i] |= (int) (*sp - ('A' - 10));
+               else if (isdigit(*sp))
+                       bp[i] |= (int) (*sp - '0');
+               else
+                       return -1;
+
+               sp++;
+       }
+       sock_in->sin_addr.s_addr = htonl(val);
+
+       return (sp - bufp);
+}
+#endif
+
+static int FAST_FUNC INET_input(/*int type,*/ const char *bufp, struct sockaddr *sap)
+{
+       return INET_resolve(bufp, (struct sockaddr_in *) sap, 0);
+/*
+       switch (type) {
+       case 1:
+               return (INET_getsock(bufp, sap));
+       case 256:
+               return (INET_resolve(bufp, (struct sockaddr_in *) sap, 1));
+       default:
+               return (INET_resolve(bufp, (struct sockaddr_in *) sap, 0));
+       }
+*/
+}
+
+static const struct aftype inet_aftype = {
+       .name   = "inet",
+       .title  = "DARPA Internet",
+       .af     = AF_INET,
+       .alen   = 4,
+       .sprint = INET_sprint,
+       .input  = INET_input,
+};
+
+#ifdef HAVE_AFINET6
+
+/* Display an Internet socket address. */
+/* dirty! struct sockaddr usually doesn't suffer for inet6 addresses, fst. */
+static const char* FAST_FUNC INET6_sprint(struct sockaddr *sap, int numeric)
+{
+       static char *buff;
+
+       free(buff);
+       if (sap->sa_family == 0xFFFF || sap->sa_family == 0)
+               return "[NONE SET]";
+       buff = INET6_rresolve((struct sockaddr_in6 *) sap, numeric);
+       return buff;
+}
+
+#ifdef UNUSED
+static int INET6_getsock(char *bufp, struct sockaddr *sap)
+{
+       struct sockaddr_in6 *sin6;
+
+       sin6 = (struct sockaddr_in6 *) sap;
+       sin6->sin6_family = AF_INET6;
+       sin6->sin6_port = 0;
+
+       if (inet_pton(AF_INET6, bufp, sin6->sin6_addr.s6_addr) <= 0)
+               return -1;
+
+       return 16;                      /* ?;) */
+}
+#endif
+
+static int FAST_FUNC INET6_input(/*int type,*/ const char *bufp, struct sockaddr *sap)
+{
+       return INET6_resolve(bufp, (struct sockaddr_in6 *) sap);
+/*
+       switch (type) {
+       case 1:
+               return (INET6_getsock(bufp, sap));
+       default:
+               return (INET6_resolve(bufp, (struct sockaddr_in6 *) sap));
+       }
+*/
+}
+
+static const struct aftype inet6_aftype = {
+       .name   = "inet6",
+       .title  = "IPv6",
+       .af     = AF_INET6,
+       .alen   = sizeof(struct in6_addr),
+       .sprint = INET6_sprint,
+       .input  = INET6_input,
+};
+
+#endif /* HAVE_AFINET6 */
+
+/* Display an UNSPEC address. */
+static char* FAST_FUNC UNSPEC_print(unsigned char *ptr)
+{
+       static char *buff;
+
+       char *pos;
+       unsigned int i;
+
+       if (!buff)
+               buff = xmalloc(sizeof(struct sockaddr) * 3 + 1);
+       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 const char* FAST_FUNC UNSPEC_sprint(struct sockaddr *sap, int numeric UNUSED_PARAM)
+{
+       if (sap->sa_family == 0xFFFF || sap->sa_family == 0)
+               return "[NONE SET]";
+       return UNSPEC_print((unsigned char *)sap->sa_data);
+}
+
+static const struct aftype unspec_aftype = {
+       .name   = "unspec",
+       .title  = "UNSPEC",
+       .af     = AF_UNSPEC,
+       .alen   = 0,
+       .print  = UNSPEC_print,
+       .sprint = UNSPEC_sprint,
+};
+
+static const struct aftype *const aftypes[] = {
+       &inet_aftype,
+#ifdef HAVE_AFINET6
+       &inet6_aftype,
+#endif
+       &unspec_aftype,
+       NULL
+};
+
+/* Check our protocol family table for this family. */
+const struct aftype* FAST_FUNC get_aftype(const char *name)
+{
+       const struct aftype *const *afp;
+
+       afp = aftypes;
+       while (*afp != NULL) {
+               if (!strcmp((*afp)->name, name))
+                       return (*afp);
+               afp++;
+       }
+       return NULL;
+}
+
+/* Check our protocol family table for this family. */
+static const struct aftype *get_afntype(int af)
+{
+       const struct aftype *const *afp;
+
+       afp = aftypes;
+       while (*afp != NULL) {
+               if ((*afp)->af == af)
+                       return *afp;
+               afp++;
+       }
+       return NULL;
+}
+
+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 */
+};
+
+
+smallint interface_opt_a;      /* show all interfaces */
+
+static struct interface *int_list, *int_last;
+
+
+#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));
+       strncpy_IFNAMSIZ(new->name, name);
+       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 char *get_name(char *name, char *p)
+{
+       /* Extract <name> from nul-terminated p where p matches
+          <name>: 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) < IFNAMSIZ) {
+                       memcpy(name, &p[namestart], nameend - namestart);
+                       name[nameend - namestart] = '\0';
+                       p = &p[nameend];
+               } else {
+                       /* Interface name too large */
+                       name[0] = '\0';
+               }
+       } else {
+               /* trailing ':' not found - return empty */
+               name[0] = '\0';
+       }
+       return p + 1;
+}
+
+/* If scanf supports size qualifiers for %n conversions, then we can
+ * use a modified fmt that simply stores the position in the fields
+ * having no associated fields in the proc string.  Of course, we need
+ * to zero them again when we're done.  But that is smaller than the
+ * old approach of multiple scanf occurrences with large numbers of
+ * args. */
+
+/* static const char *const ss_fmt[] = { */
+/*     "%lln%llu%lu%lu%lu%lu%ln%ln%lln%llu%lu%lu%lu%lu%lu", */
+/*     "%llu%llu%lu%lu%lu%lu%ln%ln%llu%llu%lu%lu%lu%lu%lu", */
+/*     "%llu%llu%lu%lu%lu%lu%lu%lu%llu%llu%lu%lu%lu%lu%lu%lu" */
+/* }; */
+
+       /* Lie about the size of the int pointed to for %n. */
+#if INT_MAX == LONG_MAX
+static const char *const ss_fmt[] = {
+       "%n%llu%u%u%u%u%n%n%n%llu%u%u%u%u%u",
+       "%llu%llu%u%u%u%u%n%n%llu%llu%u%u%u%u%u",
+       "%llu%llu%u%u%u%u%u%u%llu%llu%u%u%u%u%u%u"
+};
+#else
+static const char *const ss_fmt[] = {
+       "%n%llu%lu%lu%lu%lu%n%n%n%llu%lu%lu%lu%lu%lu",
+       "%llu%llu%lu%lu%lu%lu%n%n%llu%llu%lu%lu%lu%lu%lu",
+       "%llu%llu%lu%lu%lu%lu%lu%lu%llu%llu%lu%lu%lu%lu%lu%lu"
+};
+
+#endif
+
+static void get_dev_fields(char *bp, struct interface *ife, int procnetdev_vsn)
+{
+       memset(&ife->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 int procnetdev_version(char *buf)
+{
+       if (strstr(buf, "compressed"))
+               return 2;
+       if (strstr(buf, "bytes"))
+               return 1;
+       return 0;
+}
+
+static int if_readconf(void)
+{
+       int numreqs = 30;
+       struct ifconf ifc;
+       struct ifreq *ifr;
+       int n, err = -1;
+       int skfd;
+
+       ifc.ifc_buf = NULL;
+
+       /* SIOCGIFCONF currently seems to only work properly on AF_INET sockets
+          (as of 2.1.128) */
+       skfd = socket(AF_INET, SOCK_DGRAM, 0);
+       if (skfd < 0) {
+               bb_perror_msg("error: no inet socket available");
+               return -1;
+       }
+
+       for (;;) {
+               ifc.ifc_len = sizeof(struct ifreq) * numreqs;
+               ifc.ifc_buf = xrealloc(ifc.ifc_buf, ifc.ifc_len);
+
+               if (ioctl_or_warn(skfd, SIOCGIFCONF, &ifc) < 0) {
+                       goto out;
+               }
+               if (ifc.ifc_len == (int)(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:
+       close(skfd);
+       free(ifc.ifc_buf);
+       return err;
+}
+
+static int if_readlist_proc(char *target)
+{
+       static smallint 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_or_warn(_PATH_PROCNET_DEV, "r");
+       if (!fh) {
+               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)) {
+               bb_perror_msg(_PATH_PROCNET_DEV);
+               err = -1;
+               proc_read = 0;
+       }
+       fclose(fh);
+       return err;
+}
+
+static int if_readlist(void)
+{
+       int err = if_readlist_proc(NULL);
+       /* Needed in order to get ethN:M aliases */
+       if (!err)
+               err = if_readconf();
+       return err;
+}
+
+/* Fetch the interface configuration from the kernel. */
+static int if_fetch(struct interface *ife)
+{
+       struct ifreq ifr;
+       char *ifname = ife->name;
+       int skfd;
+
+       skfd = xsocket(AF_INET, SOCK_DGRAM, 0);
+
+       strncpy_IFNAMSIZ(ifr.ifr_name, ifname);
+       if (ioctl(skfd, SIOCGIFFLAGS, &ifr) < 0) {
+               close(skfd);
+               return -1;
+       }
+       ife->flags = ifr.ifr_flags;
+
+       strncpy_IFNAMSIZ(ifr.ifr_name, ifname);
+       memset(ife->hwaddr, 0, 32);
+       if (ioctl(skfd, SIOCGIFHWADDR, &ifr) >= 0)
+               memcpy(ife->hwaddr, ifr.ifr_hwaddr.sa_data, 8);
+
+       ife->type = ifr.ifr_hwaddr.sa_family;
+
+       strncpy_IFNAMSIZ(ifr.ifr_name, ifname);
+       ife->metric = 0;
+       if (ioctl(skfd, SIOCGIFMETRIC, &ifr) >= 0)
+               ife->metric = ifr.ifr_metric;
+
+       strncpy_IFNAMSIZ(ifr.ifr_name, ifname);
+       ife->mtu = 0;
+       if (ioctl(skfd, SIOCGIFMTU, &ifr) >= 0)
+               ife->mtu = ifr.ifr_mtu;
+
+       memset(&ife->map, 0, sizeof(struct ifmap));
+#ifdef SIOCGIFMAP
+       strncpy_IFNAMSIZ(ifr.ifr_name, ifname);
+       if (ioctl(skfd, SIOCGIFMAP, &ifr) == 0)
+               ife->map = ifr.ifr_map;
+#endif
+
+#ifdef HAVE_TXQUEUELEN
+       strncpy_IFNAMSIZ(ifr.ifr_name, ifname);
+       ife->tx_queue_len = -1; /* unknown value */
+       if (ioctl(skfd, SIOCGIFTXQLEN, &ifr) >= 0)
+               ife->tx_queue_len = ifr.ifr_qlen;
+#else
+       ife->tx_queue_len = -1; /* unknown value */
+#endif
+
+       strncpy_IFNAMSIZ(ifr.ifr_name, ifname);
+       ifr.ifr_addr.sa_family = AF_INET;
+       memset(&ife->addr, 0, sizeof(struct sockaddr));
+       if (ioctl(skfd, SIOCGIFADDR, &ifr) == 0) {
+               ife->has_ip = 1;
+               ife->addr = ifr.ifr_addr;
+               strncpy_IFNAMSIZ(ifr.ifr_name, ifname);
+               memset(&ife->dstaddr, 0, sizeof(struct sockaddr));
+               if (ioctl(skfd, SIOCGIFDSTADDR, &ifr) >= 0)
+                       ife->dstaddr = ifr.ifr_dstaddr;
+
+               strncpy_IFNAMSIZ(ifr.ifr_name, ifname);
+               memset(&ife->broadaddr, 0, sizeof(struct sockaddr));
+               if (ioctl(skfd, SIOCGIFBRDADDR, &ifr) >= 0)
+                       ife->broadaddr = ifr.ifr_broadaddr;
+
+               strncpy_IFNAMSIZ(ifr.ifr_name, ifname);
+               memset(&ife->netmask, 0, sizeof(struct sockaddr));
+               if (ioctl(skfd, SIOCGIFNETMASK, &ifr) >= 0)
+                       ife->netmask = ifr.ifr_netmask;
+       }
+
+       close(skfd);
+       return 0;
+}
+
+static int do_if_fetch(struct interface *ife)
+{
+       if (if_fetch(ife) < 0) {
+               const 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;
+}
+
+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 <net/if_arp.h>
+
+#if (defined(__GLIBC__) && __GLIBC__ >= 2 && __GLIBC_MINOR__ >= 1) || defined(_NEWLIB_VERSION)
+#include <net/ethernet.h>
+#else
+#include <linux/if_ether.h>
+#endif
+
+/* Display an Ethernet address in readable format. */
+static char* FAST_FUNC ether_print(unsigned char *ptr)
+{
+       static char *buff;
+
+       free(buff);
+       buff = xasprintf("%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 int FAST_FUNC ether_input(const char *bufp, struct sockaddr *sap);
+
+static const struct hwtype ether_hwtype = {
+       .name  = "ether",
+       .title = "Ethernet",
+       .type  = ARPHRD_ETHER,
+       .alen  = ETH_ALEN,
+       .print = ether_print,
+       .input = ether_input
+};
+
+static unsigned hexchar2int(char c)
+{
+       if (isdigit(c))
+               return c - '0';
+       c &= ~0x20; /* a -> A */
+       if ((unsigned)(c - 'A') <= 5)
+               return c - ('A' - 10);
+       return ~0U;
+}
+
+/* Input an Ethernet address and convert to binary. */
+static int FAST_FUNC ether_input(const char *bufp, struct sockaddr *sap)
+{
+       unsigned char *ptr;
+       char c;
+       int i;
+       unsigned val;
+
+       sap->sa_family = ether_hwtype.type;
+       ptr = (unsigned char*) sap->sa_data;
+
+       i = 0;
+       while ((*bufp != '\0') && (i < ETH_ALEN)) {
+               val = hexchar2int(*bufp++) * 0x10;
+               if (val > 0xff) {
+                       errno = EINVAL;
+                       return -1;
+               }
+               c = *bufp;
+               if (c == ':' || c == 0)
+                       val >>= 4;
+               else {
+                       val |= hexchar2int(c);
+                       if (val > 0xff) {
+                               errno = EINVAL;
+                               return -1;
+                       }
+               }
+               if (c != 0)
+                       bufp++;
+               *ptr++ = (unsigned char) val;
+               i++;
+
+               /* We might get a semicolon here - not required. */
+               if (*bufp == ':') {
+                       bufp++;
+               }
+       }
+       return 0;
+}
+
+#include <net/if_arp.h>
+
+static const struct hwtype ppp_hwtype = {
+       .name =         "ppp",
+       .title =        "Point-to-Point Protocol",
+       .type =         ARPHRD_PPP
+};
+
+#if ENABLE_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
+#if ENABLE_FEATURE_HWIB
+static const struct hwtype ib_hwtype = {
+       .name  = "infiniband",
+       .title = "InfiniBand",
+       .type  = ARPHRD_INFINIBAND,
+       .alen  = INFINIBAND_ALEN,
+       .print = UNSPEC_print,
+       .input = in_ib,
+};
+#endif
+
+
+static const struct hwtype *const hwtypes[] = {
+       &loop_hwtype,
+       &ether_hwtype,
+       &ppp_hwtype,
+       &unspec_hwtype,
+#if ENABLE_FEATURE_IPV6
+       &sit_hwtype,
+#endif
+#if ENABLE_FEATURE_HWIB
+       &ib_hwtype,
+#endif
+       NULL
+};
+
+#ifdef IFF_PORTSEL
+static const char *const if_port_text[] = {
+       /* Keep in step with <linux/netdevice.h> */
+       "unknown",
+       "10base2",
+       "10baseT",
+       "AUI",
+       "100baseT",
+       "100baseTX",
+       "100baseFX",
+       NULL
+};
+#endif
+
+/* Check our hardware type table for this type. */
+const struct hwtype* FAST_FUNC get_hwtype(const char *name)
+{
+       const struct hwtype *const *hwp;
+
+       hwp = hwtypes;
+       while (*hwp != NULL) {
+               if (!strcmp((*hwp)->name, name))
+                       return (*hwp);
+               hwp++;
+       }
+       return NULL;
+}
+
+/* Check our hardware type table for this type. */
+const struct hwtype* FAST_FUNC 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)
+{
+       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[] ALIGN1 = "\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);
+}
+
+
+#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 */
+
+
+static void ife_print6(struct interface *ptr)
+{
+
+       FILE *f;
+       char addr6[40], devname[20];
+       struct sockaddr_in6 sap;
+       int plen, scope, dad_status, if_idx;
+       char addr6p[8][5];
+
+       f = fopen_for_read(_PATH_PROCNET_IFINET6);
+       if (f == NULL)
+               return;
+
+       while (fscanf
+                  (f, "%4s%4s%4s%4s%4s%4s%4s%4s %08x %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_sprint((struct sockaddr *) &sap, 1),
+                                  plen);
+                       printf(" Scope:");
+                       switch (scope & IPV6_ADDR_SCOPE_MASK) {
+                       case 0:
+                               puts("Global");
+                               break;
+                       case IPV6_ADDR_LINKLOCAL:
+                               puts("Link");
+                               break;
+                       case IPV6_ADDR_SITELOCAL:
+                               puts("Site");
+                               break;
+                       case IPV6_ADDR_COMPATv4:
+                               puts("Compat");
+                               break;
+                       case IPV6_ADDR_LOOPBACK:
+                               puts("Host");
+                               break;
+                       default:
+                               puts("Unknown");
+                       }
+               }
+       }
+       fclose(f);
+}
+#else
+#define ife_print6(a) ((void)0)
+#endif
+
+
+static void ife_print(struct interface *ptr)
+{
+       const struct aftype *ap;
+       const struct hwtype *hw;
+       int hf;
+       int can_compress = 0;
+
+       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
+       bb_putchar('\n');
+
+       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));
+       }
+
+       ife_print6(ptr);
+
+       printf("          ");
+       /* DONT FORGET TO ADD THE FLAGS IN ife_print_short, too */
+
+       if (ptr->flags == 0) {
+               printf("[NO FLAGS] ");
+       } else {
+               static const char ife_print_flags_strs[] ALIGN1 =
+                       "UP\0"
+                       "BROADCAST\0"
+                       "DEBUG\0"
+                       "LOOPBACK\0"
+                       "POINTOPOINT\0"
+                       "NOTRAILERS\0"
+                       "RUNNING\0"
+                       "NOARP\0"
+                       "PROMISC\0"
+                       "ALLMULTI\0"
+                       "SLAVE\0"
+                       "MASTER\0"
+                       "MULTICAST\0"
+#ifdef HAVE_DYNAMIC
+                       "DYNAMIC\0"
+#endif
+                       ;
+               static const unsigned short ife_print_flags_mask[] ALIGN2 = {
+                       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
+               };
+               const unsigned short *mask = ife_print_flags_mask;
+               const char *str = ife_print_flags_strs;
+               do {
+                       if (ptr->flags & *mask) {
+                               printf("%s ", str);
+                       }
+                       mask++;
+                       str += strlen(str) + 1;
+               } while (*str);
+       }
+
+       /* 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
+       bb_putchar('\n');
+
+       /* 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);
+               bb_putchar('\n');
+       }
+       bb_putchar('\n');
+}
+
+static int do_if_print(struct interface *ife) /*, int *opt_a)*/
+{
+       int res;
+
+       res = do_if_fetch(ife);
+       if (res >= 0) {
+               if ((ife->flags & IFF_UP) || interface_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;
+}
+
+#ifdef UNUSED
+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;
+}
+#endif
+
+/* for ipv4 add/del modes */
+static int if_print(char *ifname)
+{
+       struct interface *ife;
+       int res;
+
+       if (!ifname) {
+               /*res = for_all_interfaces(do_if_print, &interface_opt_a);*/
+               if (!int_list && (if_readlist() < 0))
+                       return -1;
+               for (ife = int_list; ife; ife = ife->next) {
+                       int err = do_if_print(ife); /*, &interface_opt_a);*/
+                       if (err)
+                               return err;
+               }
+               return 0;
+       }
+       ife = lookup_interface(ifname);
+       res = do_if_fetch(ife);
+       if (res >= 0)
+               ife_print(ife);
+       return res;
+}
+
+#if ENABLE_FEATURE_HWIB
+/* Input an Infiniband address and convert to binary. */
+int FAST_FUNC in_ib(const char *bufp, struct sockaddr *sap)
+{
+       unsigned char *ptr;
+       char c;
+       const char *orig;
+       int i;
+       unsigned val;
+
+       sap->sa_family = ib_hwtype.type;
+       ptr = (unsigned char *) sap->sa_data;
+
+       i = 0;
+       orig = bufp;
+       while ((*bufp != '\0') && (i < INFINIBAND_ALEN)) {
+               val = 0;
+               c = *bufp++;
+               if (isdigit(c))
+                       val = c - '0';
+               else if (c >= 'a' && c <= 'f')
+                       val = c - 'a' + 10;
+               else if (c >= 'A' && c <= 'F')
+                       val = c - 'A' + 10;
+               else {
+                       errno = EINVAL;
+                       return -1;
+               }
+               val <<= 4;
+               c = *bufp;
+               if (isdigit(c))
+                       val |= c - '0';
+               else if (c >= 'a' && c <= 'f')
+                       val |= c - 'a' + 10;
+               else if (c >= 'A' && c <= 'F')
+                       val |= c - 'A' + 10;
+               else if (c == ':' || c == 0)
+                       val >>= 4;
+               else {
+                       errno = EINVAL;
+                       return -1;
+               }
+               if (c != 0)
+                       bufp++;
+               *ptr++ = (unsigned char) (val & 0377);
+               i++;
+
+               /* We might get a semicolon here - not required. */
+               if (*bufp == ':') {
+                       bufp++;
+               }
+       }
+#ifdef DEBUG
+       fprintf(stderr, "in_ib(%s): %s\n", orig, UNSPEC_print(sap->sa_data));
+#endif
+       return 0;
+}
+#endif
+
+
+int FAST_FUNC display_interfaces(char *ifname)
+{
+       int status;
+
+       status = if_print(ifname);
+
+       return (status < 0); /* status < 0 == 1 -- error */
+}
diff --git a/networking/ip.c b/networking/ip.c
new file mode 100644 (file)
index 0000000..9903c68
--- /dev/null
@@ -0,0 +1,123 @@
+/* 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, <kuznet@ms2.inr.ac.ru>
+ *
+ *
+ * Changes:
+ *
+ * Rani Assaf <rani@magic.metawire.com> 980929:        resolve addresses
+ * Bernhard Reutner-Fischer rewrote to use index_in_substr_array
+ */
+
+#include "libbb.h"
+
+#include "libiproute/utils.h"
+#include "libiproute/ip_common.h"
+
+#if ENABLE_FEATURE_IP_ADDRESS \
+ || ENABLE_FEATURE_IP_ROUTE \
+ || ENABLE_FEATURE_IP_LINK \
+ || ENABLE_FEATURE_IP_TUNNEL \
+ || ENABLE_FEATURE_IP_RULE
+
+static int NORETURN ip_print_help(char **argv UNUSED_PARAM)
+{
+       bb_show_usage();
+}
+
+static int ip_do(int (*ip_func)(char **argv), char **argv)
+{
+       argv = ip_parse_common_args(argv + 1);
+       return ip_func(argv);
+}
+
+#if ENABLE_FEATURE_IP_ADDRESS
+int ipaddr_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int ipaddr_main(int argc UNUSED_PARAM, char **argv)
+{
+       return ip_do(do_ipaddr, argv);
+}
+#endif
+#if ENABLE_FEATURE_IP_LINK
+int iplink_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int iplink_main(int argc UNUSED_PARAM, char **argv)
+{
+       return ip_do(do_iplink, argv);
+}
+#endif
+#if ENABLE_FEATURE_IP_ROUTE
+int iproute_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int iproute_main(int argc UNUSED_PARAM, char **argv)
+{
+       return ip_do(do_iproute, argv);
+}
+#endif
+#if ENABLE_FEATURE_IP_RULE
+int iprule_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int iprule_main(int argc UNUSED_PARAM, char **argv)
+{
+       return ip_do(do_iprule, argv);
+}
+#endif
+#if ENABLE_FEATURE_IP_TUNNEL
+int iptunnel_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int iptunnel_main(int argc UNUSED_PARAM, char **argv)
+{
+       return ip_do(do_iptunnel, argv);
+}
+#endif
+
+
+int ip_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int ip_main(int argc UNUSED_PARAM, char **argv)
+{
+       static const char keywords[] ALIGN1 =
+               USE_FEATURE_IP_ADDRESS("address\0")
+               USE_FEATURE_IP_ROUTE("route\0")
+               USE_FEATURE_IP_LINK("link\0")
+               USE_FEATURE_IP_TUNNEL("tunnel\0" "tunl\0")
+               USE_FEATURE_IP_RULE("rule\0")
+               ;
+       enum {
+               USE_FEATURE_IP_ADDRESS(IP_addr,)
+               USE_FEATURE_IP_ROUTE(IP_route,)
+               USE_FEATURE_IP_LINK(IP_link,)
+               USE_FEATURE_IP_TUNNEL(IP_tunnel, IP_tunl,)
+               USE_FEATURE_IP_RULE(IP_rule,)
+               IP_none
+       };
+       int (*ip_func)(char**) = ip_print_help;
+
+       argv = ip_parse_common_args(argv + 1);
+       if (*argv) {
+               int key = index_in_substrings(keywords, *argv);
+               argv++;
+#if ENABLE_FEATURE_IP_ADDRESS
+               if (key == IP_addr)
+                       ip_func = do_ipaddr;
+#endif
+#if ENABLE_FEATURE_IP_ROUTE
+               if (key == IP_route)
+                       ip_func = do_iproute;
+#endif
+#if ENABLE_FEATURE_IP_LINK
+               if (key == IP_link)
+                       ip_func = do_iplink;
+#endif
+#if ENABLE_FEATURE_IP_TUNNEL
+               if (key == IP_tunnel || key == IP_tunl)
+                       ip_func = do_iptunnel;
+#endif
+#if ENABLE_FEATURE_IP_RULE
+               if (key == IP_rule)
+                       ip_func = do_iprule;
+#endif
+       }
+       return ip_func(argv);
+}
+
+#endif /* any of ENABLE_FEATURE_IP_xxx is 1 */
diff --git a/networking/ipcalc.c b/networking/ipcalc.c
new file mode 100644 (file)
index 0000000..d8fa5f3
--- /dev/null
@@ -0,0 +1,190 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Mini ipcalc implementation for busybox
+ *
+ * By Jordan Crouse <jordan@cosmicpenguin.net>
+ *    Stephan Linz  <linz@li-pro.net>
+ *
+ * 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 <sys/socket.h>
+#include <arpa/inet.h>
+
+#include "libbb.h"
+
+#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;
+}
+
+#if ENABLE_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 char ipcalc_longopts[] ALIGN1 =
+               "netmask\0"   No_argument "m"
+               "broadcast\0" No_argument "b"
+               "network\0"   No_argument "n"
+# if ENABLE_FEATURE_IPCALC_FANCY
+               "prefix\0"    No_argument "p"
+               "hostname\0"  No_argument "h"
+               "silent\0"    No_argument "s"
+# endif
+               ;
+#endif
+
+int ipcalc_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+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 = ipcalc_longopts;
+#endif
+       opt = getopt32(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;
+
+                       hostinfo = gethostbyaddr((char *) &ipaddr, sizeof(ipaddr), AF_INET);
+                       if (!hostinfo) {
+                               bb_herror_msg_and_die("cannot find hostname for %s", argv[0]);
+                       }
+                       str_tolower(hostinfo->h_name);
+
+                       printf("HOSTNAME=%s\n", hostinfo->h_name);
+               }
+       }
+
+       return EXIT_SUCCESS;
+}
diff --git a/networking/isrv.c b/networking/isrv.c
new file mode 100644 (file)
index 0000000..66bb371
--- /dev/null
@@ -0,0 +1,338 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Generic non-forking server infrastructure.
+ * Intended to make writing telnetd-type servers easier.
+ *
+ * Copyright (C) 2007 Denys Vlasenko
+ *
+ * Licensed under GPL version 2, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.h"
+#include "isrv.h"
+
+#define DEBUG 0
+
+#if DEBUG
+#define DPRINTF(args...) bb_error_msg(args)
+#else
+#define DPRINTF(args...) ((void)0)
+#endif
+
+/* Helpers */
+
+/* Opaque structure */
+
+struct isrv_state_t {
+       short  *fd2peer; /* one per registered fd */
+       void  **param_tbl; /* one per registered peer */
+       /* one per registered peer; doesn't exist if !timeout */
+       time_t *timeo_tbl;
+       int   (*new_peer)(isrv_state_t *state, int fd);
+       time_t  curtime;
+       int     timeout;
+       int     fd_count;
+       int     peer_count;
+       int     wr_count;
+       fd_set  rd;
+       fd_set  wr;
+};
+#define FD2PEER    (state->fd2peer)
+#define PARAM_TBL  (state->param_tbl)
+#define TIMEO_TBL  (state->timeo_tbl)
+#define CURTIME    (state->curtime)
+#define TIMEOUT    (state->timeout)
+#define FD_COUNT   (state->fd_count)
+#define PEER_COUNT (state->peer_count)
+#define WR_COUNT   (state->wr_count)
+
+/* callback */
+void isrv_want_rd(isrv_state_t *state, int fd)
+{
+       FD_SET(fd, &state->rd);
+}
+
+/* callback */
+void isrv_want_wr(isrv_state_t *state, int fd)
+{
+       if (!FD_ISSET(fd, &state->wr)) {
+               WR_COUNT++;
+               FD_SET(fd, &state->wr);
+       }
+}
+
+/* callback */
+void isrv_dont_want_rd(isrv_state_t *state, int fd)
+{
+       FD_CLR(fd, &state->rd);
+}
+
+/* callback */
+void isrv_dont_want_wr(isrv_state_t *state, int fd)
+{
+       if (FD_ISSET(fd, &state->wr)) {
+               WR_COUNT--;
+               FD_CLR(fd, &state->wr);
+       }
+}
+
+/* callback */
+int isrv_register_fd(isrv_state_t *state, int peer, int fd)
+{
+       int n;
+
+       DPRINTF("register_fd(peer:%d,fd:%d)", peer, fd);
+
+       if (FD_COUNT >= FD_SETSIZE) return -1;
+       if (FD_COUNT <= fd) {
+               n = FD_COUNT;
+               FD_COUNT = fd + 1;
+
+               DPRINTF("register_fd: FD_COUNT %d", FD_COUNT);
+
+               FD2PEER = xrealloc(FD2PEER, FD_COUNT * sizeof(FD2PEER[0]));
+               while (n < fd) FD2PEER[n++] = -1;
+       }
+
+       DPRINTF("register_fd: FD2PEER[%d] = %d", fd, peer);
+
+       FD2PEER[fd] = peer;
+       return 0;
+}
+
+/* callback */
+void isrv_close_fd(isrv_state_t *state, int fd)
+{
+       DPRINTF("close_fd(%d)", fd);
+
+       close(fd);
+       isrv_dont_want_rd(state, fd);
+       if (WR_COUNT) isrv_dont_want_wr(state, fd);
+
+       FD2PEER[fd] = -1;
+       if (fd == FD_COUNT-1) {
+               do fd--; while (fd >= 0 && FD2PEER[fd] == -1);
+               FD_COUNT = fd + 1;
+
+               DPRINTF("close_fd: FD_COUNT %d", FD_COUNT);
+
+               FD2PEER = xrealloc(FD2PEER, FD_COUNT * sizeof(FD2PEER[0]));
+       }
+}
+
+/* callback */
+int isrv_register_peer(isrv_state_t *state, void *param)
+{
+       int n;
+
+       if (PEER_COUNT >= FD_SETSIZE) return -1;
+       n = PEER_COUNT++;
+
+       DPRINTF("register_peer: PEER_COUNT %d", PEER_COUNT);
+
+       PARAM_TBL = xrealloc(PARAM_TBL, PEER_COUNT * sizeof(PARAM_TBL[0]));
+       PARAM_TBL[n] = param;
+       if (TIMEOUT) {
+               TIMEO_TBL = xrealloc(TIMEO_TBL, PEER_COUNT * sizeof(TIMEO_TBL[0]));
+               TIMEO_TBL[n] = CURTIME;
+       }
+       return n;
+}
+
+static void remove_peer(isrv_state_t *state, int peer)
+{
+       int movesize;
+       int fd;
+
+       DPRINTF("remove_peer(%d)", peer);
+
+       fd = FD_COUNT - 1;
+       while (fd >= 0) {
+               if (FD2PEER[fd] == peer) {
+                       isrv_close_fd(state, fd);
+                       fd--;
+                       continue;
+               }
+               if (FD2PEER[fd] > peer)
+                       FD2PEER[fd]--;
+               fd--;
+       }
+
+       PEER_COUNT--;
+       DPRINTF("remove_peer: PEER_COUNT %d", PEER_COUNT);
+
+       movesize = (PEER_COUNT - peer) * sizeof(void*);
+       if (movesize > 0) {
+               memcpy(&PARAM_TBL[peer], &PARAM_TBL[peer+1], movesize);
+               if (TIMEOUT)
+                       memcpy(&TIMEO_TBL[peer], &TIMEO_TBL[peer+1], movesize);
+       }
+       PARAM_TBL = xrealloc(PARAM_TBL, PEER_COUNT * sizeof(PARAM_TBL[0]));
+       if (TIMEOUT)
+               TIMEO_TBL = xrealloc(TIMEO_TBL, PEER_COUNT * sizeof(TIMEO_TBL[0]));
+}
+
+static void handle_accept(isrv_state_t *state, int fd)
+{
+       int n, newfd;
+
+       /* suppress gcc warning "cast from ptr to int of different size" */
+       fcntl(fd, F_SETFL, (int)(ptrdiff_t)(PARAM_TBL[0]) | O_NONBLOCK);
+       newfd = accept(fd, NULL, 0);
+       fcntl(fd, F_SETFL, (int)(ptrdiff_t)(PARAM_TBL[0]));
+       if (newfd < 0) {
+               if (errno == EAGAIN) return;
+               /* Most probably someone gave us wrong fd type
+                * (for example, non-socket). Don't want
+                * to loop forever. */
+               bb_perror_msg_and_die("accept");
+       }
+
+       DPRINTF("new_peer(%d)", newfd);
+       n = state->new_peer(state, newfd);
+       if (n)
+               remove_peer(state, n); /* unsuccesful peer start */
+}
+
+void BUG_sizeof_fd_set_is_strange(void);
+static void handle_fd_set(isrv_state_t *state, fd_set *fds, int (*h)(int, void **))
+{
+       enum { LONG_CNT = sizeof(fd_set) / sizeof(long) };
+       int fds_pos;
+       int fd, peer;
+       /* need to know value at _the beginning_ of this routine */
+       int fd_cnt = FD_COUNT;
+
+       if (LONG_CNT * sizeof(long) != sizeof(fd_set))
+               BUG_sizeof_fd_set_is_strange();
+
+       fds_pos = 0;
+       while (1) {
+               /* Find next nonzero bit */
+               while (fds_pos < LONG_CNT) {
+                       if (((long*)fds)[fds_pos] == 0) {
+                               fds_pos++;
+                               continue;
+                       }
+                       /* Found non-zero word */
+                       fd = fds_pos * sizeof(long)*8; /* word# -> bit# */
+                       while (1) {
+                               if (FD_ISSET(fd, fds)) {
+                                       FD_CLR(fd, fds);
+                                       goto found_fd;
+                               }
+                               fd++;
+                       }
+               }
+               break; /* all words are zero */
+ found_fd:
+               if (fd >= fd_cnt) { /* paranoia */
+                       DPRINTF("handle_fd_set: fd > fd_cnt?? (%d > %d)",
+                                       fd, fd_cnt);
+                       break;
+               }
+               DPRINTF("handle_fd_set: fd %d is active", fd);
+               peer = FD2PEER[fd];
+               if (peer < 0)
+                       continue; /* peer is already gone */
+               if (peer == 0) {
+                       handle_accept(state, fd);
+                       continue;
+               }
+               DPRINTF("h(fd:%d)", fd);
+               if (h(fd, &PARAM_TBL[peer])) {
+                       /* this peer is gone */
+                       remove_peer(state, peer);
+               } else if (TIMEOUT) {
+                       TIMEO_TBL[peer] = monotonic_sec();
+               }
+       }
+}
+
+static void handle_timeout(isrv_state_t *state, int (*do_timeout)(void **))
+{
+       int n, peer;
+       peer = PEER_COUNT-1;
+       /* peer 0 is not checked */
+       while (peer > 0) {
+               DPRINTF("peer %d: time diff %d", peer,
+                               (int)(CURTIME - TIMEO_TBL[peer]));
+               if ((CURTIME - TIMEO_TBL[peer]) >= TIMEOUT) {
+                       DPRINTF("peer %d: do_timeout()", peer);
+                       n = do_timeout(&PARAM_TBL[peer]);
+                       if (n)
+                               remove_peer(state, peer);
+               }
+               peer--;
+       }
+}
+
+/* Driver */
+void isrv_run(
+       int listen_fd,
+       int (*new_peer)(isrv_state_t *state, int fd),
+       int (*do_rd)(int fd, void **),
+       int (*do_wr)(int fd, void **),
+       int (*do_timeout)(void **),
+       int timeout,
+       int linger_timeout)
+{
+       isrv_state_t *state = xzalloc(sizeof(*state));
+       state->new_peer = new_peer;
+       state->timeout  = timeout;
+
+       /* register "peer" #0 - it will accept new connections */
+       isrv_register_peer(state, NULL);
+       isrv_register_fd(state, /*peer:*/ 0, listen_fd);
+       isrv_want_rd(state, listen_fd);
+       /* remember flags to make blocking<->nonblocking switch faster */
+       /* (suppress gcc warning "cast from ptr to int of different size") */
+       PARAM_TBL[0] = (void*)(ptrdiff_t)(fcntl(listen_fd, F_GETFL));
+
+       while (1) {
+               struct timeval tv;
+               fd_set rd;
+               fd_set wr;
+               fd_set *wrp = NULL;
+               int n;
+
+               tv.tv_sec = timeout;
+               if (PEER_COUNT <= 1)
+                       tv.tv_sec = linger_timeout;
+               tv.tv_usec = 0;
+               rd = state->rd;
+               if (WR_COUNT) {
+                       wr = state->wr;
+                       wrp = &wr;
+               }
+
+               DPRINTF("run: select(FD_COUNT:%d,timeout:%d)...",
+                               FD_COUNT, (int)tv.tv_sec);
+               n = select(FD_COUNT, &rd, wrp, NULL, tv.tv_sec ? &tv : NULL);
+               DPRINTF("run: ...select:%d", n);
+
+               if (n < 0) {
+                       if (errno != EINTR)
+                               bb_perror_msg("select");
+                       continue;
+               }
+
+               if (n == 0 && linger_timeout && PEER_COUNT <= 1)
+                       break;
+
+               if (timeout) {
+                       time_t t = monotonic_sec();
+                       if (t != CURTIME) {
+                               CURTIME = t;
+                               handle_timeout(state, do_timeout);
+                       }
+               }
+               if (n > 0) {
+                       handle_fd_set(state, &rd, do_rd);
+                       if (wrp)
+                               handle_fd_set(state, wrp, do_wr);
+               }
+       }
+       DPRINTF("run: bailout");
+       /* NB: accept socket is not closed. Caller is to decide what to do */
+}
diff --git a/networking/isrv.h b/networking/isrv.h
new file mode 100644 (file)
index 0000000..f20714d
--- /dev/null
@@ -0,0 +1,37 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Generic non-forking server infrastructure.
+ * Intended to make writing telnetd-type servers easier.
+ *
+ * Copyright (C) 2007 Denys Vlasenko
+ *
+ * Licensed under GPL version 2, see file LICENSE in this tarball for details.
+ */
+
+PUSH_AND_SET_FUNCTION_VISIBILITY_TO_HIDDEN
+
+/* opaque structure */
+struct isrv_state_t;
+typedef struct isrv_state_t isrv_state_t;
+
+/* callbacks */
+void isrv_want_rd(isrv_state_t *state, int fd);
+void isrv_want_wr(isrv_state_t *state, int fd);
+void isrv_dont_want_rd(isrv_state_t *state, int fd);
+void isrv_dont_want_wr(isrv_state_t *state, int fd);
+int isrv_register_fd(isrv_state_t *state, int peer, int fd);
+void isrv_close_fd(isrv_state_t *state, int fd);
+int isrv_register_peer(isrv_state_t *state, void *param);
+
+/* driver */
+void isrv_run(
+       int listen_fd,
+       int (*new_peer)(isrv_state_t *state, int fd),
+       int (*do_rd)(int fd, void **),
+       int (*do_wr)(int fd, void **),
+       int (*do_timeout)(void **),
+       int timeout,
+       int linger_timeout
+);
+
+POP_SAVED_FUNCTION_VISIBILITY
diff --git a/networking/isrv_identd.c b/networking/isrv_identd.c
new file mode 100644 (file)
index 0000000..e8ba007
--- /dev/null
@@ -0,0 +1,147 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Fake identd server.
+ *
+ * Copyright (C) 2007 Denys Vlasenko
+ *
+ * Licensed under GPL version 2, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.h"
+#include <syslog.h>
+#include "isrv.h"
+
+enum { TIMEOUT = 20 };
+
+typedef struct identd_buf_t {
+       int pos;
+       int fd_flag;
+       char buf[64 - 2*sizeof(int)];
+} identd_buf_t;
+
+#define bogouser bb_common_bufsiz1
+
+static int new_peer(isrv_state_t *state, int fd)
+{
+       int peer;
+       identd_buf_t *buf = xzalloc(sizeof(*buf));
+
+       peer = isrv_register_peer(state, buf);
+       if (peer < 0)
+               return 0; /* failure */
+       if (isrv_register_fd(state, peer, fd) < 0)
+               return peer; /* failure, unregister peer */
+
+       buf->fd_flag = fcntl(fd, F_GETFL) | O_NONBLOCK;
+       isrv_want_rd(state, fd);
+       return 0;
+}
+
+static int do_rd(int fd, void **paramp)
+{
+       identd_buf_t *buf = *paramp;
+       char *cur, *p;
+       int retval = 0; /* session is ok (so far) */
+       int sz;
+
+       cur = buf->buf + buf->pos;
+
+       if (buf->fd_flag & O_NONBLOCK)
+               fcntl(fd, F_SETFL, buf->fd_flag);
+       sz = safe_read(fd, cur, sizeof(buf->buf) - buf->pos);
+
+       if (sz < 0) {
+               if (errno != EAGAIN)
+                       goto term; /* terminate this session if !EAGAIN */
+               goto ok;
+       }
+
+       buf->pos += sz;
+       buf->buf[buf->pos] = '\0';
+       p = strpbrk(cur, "\r\n");
+       if (p)
+               *p = '\0';
+       if (!p && sz && buf->pos <= (int)sizeof(buf->buf))
+               goto ok;
+       /* Terminate session. If we are in server mode, then
+        * fd is still in nonblocking mode - we never block here */
+       if (fd == 0) fd++; /* inetd mode? then write to fd 1 */
+       fdprintf(fd, "%s : USERID : UNIX : %s\r\n", buf->buf, bogouser);
+ term:
+       free(buf);
+       retval = 1; /* terminate */
+ ok:
+       if (buf->fd_flag & O_NONBLOCK)
+               fcntl(fd, F_SETFL, buf->fd_flag & ~O_NONBLOCK);
+       return retval;
+}
+
+static int do_timeout(void **paramp UNUSED_PARAM)
+{
+       return 1; /* terminate session */
+}
+
+static void inetd_mode(void)
+{
+       identd_buf_t *buf = xzalloc(sizeof(*buf));
+       /* buf->pos = 0; - xzalloc did it */
+       /* We do NOT want nonblocking I/O here! */
+       /* buf->fd_flag = 0; - xzalloc did it */
+       do
+               alarm(TIMEOUT);
+       while (do_rd(0, (void*)&buf) == 0);
+}
+
+int fakeidentd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int fakeidentd_main(int argc UNUSED_PARAM, char **argv)
+{
+       enum {
+               OPT_foreground = 0x1,
+               OPT_inetd      = 0x2,
+               OPT_inetdwait  = 0x4,
+               OPT_fiw        = 0x7,
+               OPT_bindaddr   = 0x8,
+       };
+
+       const char *bind_address = NULL;
+       unsigned opt;
+       int fd;
+
+       opt = getopt32(argv, "fiwb:", &bind_address);
+       strcpy(bogouser, "nobody");
+       if (argv[optind])
+               strncpy(bogouser, argv[optind], sizeof(bogouser));
+
+       /* Daemonize if no -f and no -i and no -w */
+       if (!(opt & OPT_fiw))
+               bb_daemonize_or_rexec(0, argv);
+
+       /* Where to log in inetd modes? "Classic" inetd
+        * probably has its stderr /dev/null'ed (we need log to syslog?),
+        * but daemontools-like utilities usually expect that children
+        * log to stderr. I like daemontools more. Go their way.
+        * (Or maybe we need yet another option "log to syslog") */
+       if (!(opt & OPT_fiw) /* || (opt & OPT_syslog) */) {
+               openlog(applet_name, LOG_PID, LOG_DAEMON);
+               logmode = LOGMODE_SYSLOG;
+       }
+
+       if (opt & OPT_inetd) {
+               inetd_mode();
+               return 0;
+       }
+
+       /* Ignore closed connections when writing */
+       signal(SIGPIPE, SIG_IGN);
+
+       fd = 0;
+       if (!(opt & OPT_inetdwait)) {
+               fd = create_and_bind_stream_or_die(bind_address,
+                               bb_lookup_port("identd", "tcp", 113));
+               xlisten(fd, 5);
+       }
+
+       isrv_run(fd, new_peer, do_rd, /*do_wr:*/ NULL, do_timeout,
+                       TIMEOUT, (opt & OPT_inetdwait) ? TIMEOUT : 0);
+       return 0;
+}
diff --git a/networking/libiproute/Kbuild b/networking/libiproute/Kbuild
new file mode 100644 (file)
index 0000000..5f9dd32
--- /dev/null
@@ -0,0 +1,64 @@
+# Makefile for busybox
+#
+# Copyright (C) 1999-2004 by Erik Andersen <andersen@codepoet.org>
+#
+# Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
+#
+
+lib-y:=
+
+lib-$(CONFIG_SLATTACH) += \
+       utils.o
+
+lib-$(CONFIG_IP) += \
+       ip_parse_common_args.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_FEATURE_IP_ADDRESS) += \
+       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_FEATURE_IP_LINK) += \
+       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_FEATURE_IP_ROUTE) += \
+       ip_parse_common_args.o \
+       iproute.o \
+       libnetlink.o \
+       ll_map.o \
+       rt_names.o \
+       rtm_map.o \
+       utils.o
+
+lib-$(CONFIG_FEATURE_IP_TUNNEL) += \
+       ip_parse_common_args.o \
+       iptunnel.o \
+       rt_names.o \
+       utils.o
+
+lib-$(CONFIG_FEATURE_IP_RULE) += \
+       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 (file)
index 0000000..aef3252
--- /dev/null
@@ -0,0 +1,37 @@
+/* vi: set sw=4 ts=4: */
+#ifndef IP_COMMON_H
+#define IP_COMMON_H 1
+
+#include "libbb.h"
+#include <asm/types.h>
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+#if !defined IFA_RTA
+#include <linux/if_addr.h>
+#endif
+#if !defined IFLA_RTA
+#include <linux/if_link.h>
+#endif
+
+PUSH_AND_SET_FUNCTION_VISIBILITY_TO_HIDDEN
+
+extern char **ip_parse_common_args(char **argv);
+extern int print_neigh(struct sockaddr_nl *who, struct nlmsghdr *n, void *arg);
+extern int ipaddr_list_or_flush(char **argv, int flush);
+extern int iproute_monitor(char **argv);
+extern void iplink_usage(void) NORETURN;
+extern void ipneigh_reset_filter(void);
+
+extern int do_ipaddr(char **argv);
+extern int do_iproute(char **argv);
+extern int do_iprule(char **argv);
+extern int do_ipneigh(char **argv);
+extern int do_iptunnel(char **argv);
+extern int do_iplink(char **argv);
+extern int do_ipmonitor(char **argv);
+extern int do_multiaddr(char **argv);
+extern int do_multiroute(char **argv);
+
+POP_SAVED_FUNCTION_VISIBILITY
+
+#endif
diff --git a/networking/libiproute/ip_parse_common_args.c b/networking/libiproute/ip_parse_common_args.c
new file mode 100644 (file)
index 0000000..5e4012b
--- /dev/null
@@ -0,0 +1,84 @@
+/* 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, <kuznet@ms2.inr.ac.ru>
+ *
+ *
+ * Changes:
+ *
+ * Rani Assaf <rani@magic.metawire.com> 980929:        resolve addresses
+ */
+
+#include "ip_common.h"  /* #include "libbb.h" is inside */
+#include "utils.h"
+
+family_t preferred_family = AF_UNSPEC;
+smallint oneline;
+char _SL_;
+
+char **ip_parse_common_args(char **argv)
+{
+       static const char ip_common_commands[] ALIGN1 =
+               "oneline" "\0"
+               "family" "\0"
+               "4" "\0"
+               "6" "\0"
+               "0" "\0"
+               ;
+       enum {
+               ARG_oneline,
+               ARG_family,
+               ARG_IPv4,
+               ARG_IPv6,
+               ARG_packet,
+       };
+       static const family_t af_numbers[] = { AF_INET, AF_INET6, AF_PACKET };
+       int arg;
+
+       while (*argv) {
+               char *opt = *argv;
+
+               if (opt[0] != '-')
+                       break;
+               opt++;
+               if (opt[0] == '-') {
+                       opt++;
+                       if (!opt[0]) { /* "--" */
+                               argv++;
+                               break;
+                       }
+               }
+               arg = index_in_substrings(ip_common_commands, opt);
+               if (arg < 0)
+                       bb_show_usage();
+               if (arg == ARG_oneline) {
+                       oneline = 1;
+                       argv++;
+                       continue;
+               }
+               if (arg == ARG_family) {
+                       static const char families[] ALIGN1 =
+                               "inet" "\0" "inet6" "\0" "link" "\0";
+                       argv++;
+                       if (!*argv)
+                               bb_show_usage();
+                       arg = index_in_strings(families, *argv);
+                       if (arg < 0)
+                               invarg(*argv, "protocol family");
+                       /* now arg == 0, 1 or 2 */
+               } else {
+                       arg -= ARG_IPv4;
+                       /* now arg == 0, 1 or 2 */
+               }
+               preferred_family = af_numbers[arg];
+               argv++;
+       }
+       _SL_ = oneline ? '\\' : '\n';
+       return argv;
+}
diff --git a/networking/libiproute/ipaddress.c b/networking/libiproute/ipaddress.c
new file mode 100644 (file)
index 0000000..644874f
--- /dev/null
@@ -0,0 +1,781 @@
+/* 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, <kuznet@ms2.inr.ac.ru>
+ *
+ * Changes:
+ *     Laszlo Valko <valko@linux.karinthy.hu> 990223: address label must be zero terminated
+ */
+
+#include <fnmatch.h>
+#include <net/if.h>
+#include <net/if_arp.h>
+
+#include "ip_common.h"  /* #include "libbb.h" is inside */
+#include "rt_names.h"
+#include "utils.h"
+
+#ifndef IFF_LOWER_UP
+/* from linux/if.h */
+#define IFF_LOWER_UP   0x10000         /* driver signals L1 up*/
+#endif
+
+typedef struct filter_t {
+       char *label;
+       char *flushb;
+       struct rtnl_handle *rth;
+       int scope, scopemask;
+       int flags, flagmask;
+       int flushp;
+       int flushe;
+       int ifindex;
+       family_t family;
+       smallint showqueue;
+       smallint oneline;
+       smallint up;
+       smallint flushed;
+       inet_prefix pfx;
+} filter_t;
+
+#define filter (*(filter_t*)&bb_common_bufsiz1)
+
+
+static void print_link_flags(unsigned flags, unsigned mdown)
+{
+       static const int flag_masks[] = {
+               IFF_LOOPBACK, IFF_BROADCAST, IFF_POINTOPOINT,
+               IFF_MULTICAST, IFF_NOARP, IFF_UP, IFF_LOWER_UP };
+       static const char flag_labels[] ALIGN1 =
+               "LOOPBACK\0""BROADCAST\0""POINTOPOINT\0"
+               "MULTICAST\0""NOARP\0""UP\0""LOWER_UP\0";
+
+       bb_putchar('<');
+       flags &= ~IFF_RUNNING;
+#if 0
+       _PF(ALLMULTI);
+       _PF(PROMISC);
+       _PF(MASTER);
+       _PF(SLAVE);
+       _PF(DEBUG);
+       _PF(DYNAMIC);
+       _PF(AUTOMEDIA);
+       _PF(PORTSEL);
+       _PF(NOTRAILERS);
+#endif
+       flags = print_flags_separated(flag_masks, flag_labels, flags, ",");
+       if (flags)
+               printf("%x", flags);
+       if (mdown)
+               printf(",M-DOWN");
+       printf("> ");
+}
+
+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_IFNAMSIZ(ifr.ifr_name, name);
+       if (ioctl_or_warn(s, SIOCGIFTXQLEN, &ifr) < 0) {
+               close(s);
+               return;
+       }
+       close(s);
+
+       if (ifr.ifr_qlen)
+               printf("qlen %d", ifr.ifr_qlen);
+}
+
+static int print_linkinfo(const struct nlmsghdr *n)
+{
+       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)
+               printf("Deleted ");
+
+       printf("%d: %s", ifi->ifi_index,
+               tb[IFLA_IFNAME] ? (char*)RTA_DATA(tb[IFLA_IFNAME]) : "<nil>");
+
+       if (tb[IFLA_LINK]) {
+               SPRINT_BUF(b1);
+               int iflink = *(int*)RTA_DATA(tb[IFLA_LINK]);
+               if (iflink == 0)
+                       printf("@NONE: ");
+               else {
+                       printf("@%s: ", ll_idx_n2a(iflink, b1));
+                       m_flag = ll_index_to_flags(iflink);
+                       m_flag = !(m_flag & IFF_UP);
+               }
+       } else {
+               printf(": ");
+       }
+       print_link_flags(ifi->ifi_flags, m_flag);
+
+       if (tb[IFLA_MTU])
+               printf("mtu %u ", *(int*)RTA_DATA(tb[IFLA_MTU]));
+       if (tb[IFLA_QDISC])
+               printf("qdisc %s ", (char*)RTA_DATA(tb[IFLA_QDISC]));
+#ifdef IFLA_MASTER
+       if (tb[IFLA_MASTER]) {
+               SPRINT_BUF(b1);
+               printf("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);
+               printf("%c    link/%s ", _SL_, ll_type_n2a(ifi->ifi_type, b1, sizeof(b1)));
+
+               if (tb[IFLA_ADDRESS]) {
+                       fputs(ll_addr_n2a(RTA_DATA(tb[IFLA_ADDRESS]),
+                                                     RTA_PAYLOAD(tb[IFLA_ADDRESS]),
+                                                     ifi->ifi_type,
+                                                     b1, sizeof(b1)), stdout);
+               }
+               if (tb[IFLA_BROADCAST]) {
+                       if (ifi->ifi_flags & IFF_POINTOPOINT)
+                               printf(" peer ");
+                       else
+                               printf(" brd ");
+                       fputs(ll_addr_n2a(RTA_DATA(tb[IFLA_BROADCAST]),
+                                                     RTA_PAYLOAD(tb[IFLA_BROADCAST]),
+                                                     ifi->ifi_type,
+                                                     b1, sizeof(b1)), stdout);
+               }
+       }
+       bb_putchar('\n');
+       /*fflush(stdout);*/
+       return 0;
+}
+
+static int flush_update(void)
+{
+       if (rtnl_send(filter.rth, filter.flushb, filter.flushp) < 0) {
+               bb_perror_msg("failed to send flush request");
+               return -1;
+       }
+       filter.flushp = 0;
+       return 0;
+}
+
+static int print_addrinfo(const struct sockaddr_nl *who UNUSED_PARAM,
+               struct nlmsghdr *n, void *arg UNUSED_PARAM)
+{
+       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 = 1;
+               return 0;
+       }
+
+       if (n->nlmsg_type == RTM_DELADDR)
+               printf("Deleted ");
+
+       if (filter.oneline)
+               printf("%u: %s", ifa->ifa_index, ll_index_to_name(ifa->ifa_index));
+       if (ifa->ifa_family == AF_INET)
+               printf("    inet ");
+       else if (ifa->ifa_family == AF_INET6)
+               printf("    inet6 ");
+       else
+               printf("    family %d ", ifa->ifa_family);
+
+       if (rta_tb[IFA_LOCAL]) {
+               fputs(rt_addr_n2a(ifa->ifa_family,
+                                             RTA_DATA(rta_tb[IFA_LOCAL]),
+                                             abuf, sizeof(abuf)), stdout);
+
+               if (rta_tb[IFA_ADDRESS] == NULL
+                || memcmp(RTA_DATA(rta_tb[IFA_ADDRESS]), RTA_DATA(rta_tb[IFA_LOCAL]), 4) == 0
+               ) {
+                       printf("/%d ", ifa->ifa_prefixlen);
+               } else {
+                       printf(" peer %s/%d ",
+                               rt_addr_n2a(ifa->ifa_family,
+                                           RTA_DATA(rta_tb[IFA_ADDRESS]),
+                                           abuf, sizeof(abuf)),
+                               ifa->ifa_prefixlen);
+               }
+       }
+
+       if (rta_tb[IFA_BROADCAST]) {
+               printf("brd %s ",
+                       rt_addr_n2a(ifa->ifa_family,
+                                   RTA_DATA(rta_tb[IFA_BROADCAST]),
+                                   abuf, sizeof(abuf)));
+       }
+       if (rta_tb[IFA_ANYCAST]) {
+               printf("any %s ",
+                       rt_addr_n2a(ifa->ifa_family,
+                                   RTA_DATA(rta_tb[IFA_ANYCAST]),
+                                   abuf, sizeof(abuf)));
+       }
+       printf("scope %s ", rtnl_rtscope_n2a(ifa->ifa_scope, b1, sizeof(b1)));
+       if (ifa->ifa_flags & IFA_F_SECONDARY) {
+               ifa->ifa_flags &= ~IFA_F_SECONDARY;
+               printf("secondary ");
+       }
+       if (ifa->ifa_flags & IFA_F_TENTATIVE) {
+               ifa->ifa_flags &= ~IFA_F_TENTATIVE;
+               printf("tentative ");
+       }
+       if (ifa->ifa_flags & IFA_F_DEPRECATED) {
+               ifa->ifa_flags &= ~IFA_F_DEPRECATED;
+               printf("deprecated ");
+       }
+       if (!(ifa->ifa_flags & IFA_F_PERMANENT)) {
+               printf("dynamic ");
+       } else
+               ifa->ifa_flags &= ~IFA_F_PERMANENT;
+       if (ifa->ifa_flags)
+               printf("flags %02x ", ifa->ifa_flags);
+       if (rta_tb[IFA_LABEL])
+               fputs((char*)RTA_DATA(rta_tb[IFA_LABEL]), stdout);
+       if (rta_tb[IFA_CACHEINFO]) {
+               struct ifa_cacheinfo *ci = RTA_DATA(rta_tb[IFA_CACHEINFO]);
+               char buf[128];
+               bb_putchar(_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);
+               printf("       %s", buf);
+       }
+       bb_putchar('\n');
+       /*fflush(stdout);*/
+       return 0;
+}
+
+
+struct nlmsg_list
+{
+       struct nlmsg_list *next;
+       struct nlmsghdr   h;
+};
+
+static int print_selected_addrinfo(int ifindex, struct nlmsg_list *ainfo)
+{
+       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, NULL);
+       }
+       return 0;
+}
+
+
+static int store_nlmsg(const 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)
+               continue;
+       *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;
+}
+
+/* Return value becomes exitcode. It's okay to not return at all */
+int ipaddr_list_or_flush(char **argv, int flush)
+{
+       static const char option[] ALIGN1 = "to\0""scope\0""up\0""label\0""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 (!*argv) {
+                       bb_error_msg_and_die(bb_msg_requires_arg, "flush");
+               }
+               if (filter.family == AF_PACKET) {
+                       bb_error_msg_and_die("cannot flush link addresses");
+               }
+       }
+
+       while (*argv) {
+               const int option_num = index_in_strings(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++;
+       }
+
+       xrtnl_open(&rth);
+
+       xrtnl_wilddump_request(&rth, preferred_family, RTM_GETLINK);
+       xrtnl_dump_filter(&rth, store_nlmsg, &linfo);
+
+       if (filter_dev) {
+               filter.ifindex = xll_name_to_index(filter_dev);
+       }
+
+       if (flush) {
+               char flushb[4096-512];
+
+               filter.flushb = flushb;
+               filter.flushp = 0;
+               filter.flushe = sizeof(flushb);
+               filter.rth = &rth;
+
+               for (;;) {
+                       xrtnl_wilddump_request(&rth, filter.family, RTM_GETADDR);
+                       filter.flushed = 0;
+                       xrtnl_dump_filter(&rth, print_addrinfo, NULL);
+                       if (filter.flushed == 0) {
+                               return 0;
+                       }
+                       if (flush_update() < 0)
+                               return 1;
+               }
+       }
+
+       if (filter.family != AF_PACKET) {
+               xrtnl_wilddump_request(&rth, filter.family, RTM_GETADDR);
+               xrtnl_dump_filter(&rth, store_nlmsg, &ainfo);
+       }
+
+
+       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(&l->h) == 0) {
+                       struct ifinfomsg *ifi = NLMSG_DATA(&l->h);
+                       if (filter.family != AF_PACKET)
+                               print_selected_addrinfo(ifi->ifi_index, ainfo);
+               }
+       }
+
+       return 0;
+}
+
+static int default_scope(inet_prefix *lcl)
+{
+       if (lcl->family == AF_INET) {
+               if (lcl->bytelen >= 1 && *(uint8_t*)&lcl->data == 127)
+                       return RT_SCOPE_HOST;
+       }
+       return 0;
+}
+
+/* Return value becomes exitcode. It's okay to not return at all */
+static int ipaddr_modify(int cmd, char **argv)
+{
+       static const char option[] ALIGN1 =
+               "peer\0""remote\0""broadcast\0""brd\0"
+               "anycast\0""scope\0""dev\0""label\0""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;
+       bool 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 (*argv) {
+               const int option_num = index_in_strings(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 (LONE_CHAR(*argv, '+')) {
+                                       brd_len = -1;
+                               } else if (LONE_DASH(*argv)) {
+                                       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;
+               }
+               argv++;
+       }
+
+       if (d == NULL) {
+               bb_error_msg(bb_msg_requires_arg, "\"dev\"");
+               return -1;
+       }
+       if (l && strncmp(d, l, strlen(d)) != 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_and_die("broadcast can be set only for IPv4 addresses");
+               }
+               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);
+
+       xrtnl_open(&rth);
+
+       ll_init_map(&rth);
+
+       req.ifa.ifa_index = xll_name_to_index(d);
+
+       if (rtnl_talk(&rth, &req.n, 0, 0, NULL, NULL, NULL) < 0)
+               return 2;
+
+       return 0;
+}
+
+/* Return value becomes exitcode. It's okay to not return at all */
+int do_ipaddr(char **argv)
+{
+       static const char commands[] ALIGN1 =
+               "add\0""delete\0""list\0""show\0""lst\0""flush\0";
+
+       int command_num = 2; /* default command is list */
+
+       if (*argv) {
+               command_num = index_in_substrings(commands, *argv);
+               if (command_num < 0 || command_num > 5)
+                       bb_error_msg_and_die("unknown command %s", *argv);
+               argv++;
+       }
+       if (command_num == 0) /* add */
+               return ipaddr_modify(RTM_NEWADDR, argv);
+       if (command_num == 1) /* delete */
+               return ipaddr_modify(RTM_DELADDR, argv);
+       if (command_num == 5) /* flush */
+               return ipaddr_list_or_flush(argv, 1);
+       /* 2 == list, 3 == show, 4 == lst */
+       return ipaddr_list_or_flush(argv, 0);
+}
diff --git a/networking/libiproute/iplink.c b/networking/libiproute/iplink.c
new file mode 100644 (file)
index 0000000..1e7ee07
--- /dev/null
@@ -0,0 +1,305 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * iplink.c "ip link".
+ *
+ * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+//#include <sys/ioctl.h>
+//#include <sys/socket.h>
+#include <net/if.h>
+#include <net/if_packet.h>
+#include <netpacket/packet.h>
+#include <net/ethernet.h>
+
+#include "ip_common.h"  /* #include "libbb.h" is inside */
+#include "rt_names.h"
+#include "utils.h"
+
+/* taken from linux/sockios.h */
+#define SIOCSIFNAME    0x8923          /* set interface name */
+
+/* Exits on error */
+static int get_ctl_fd(void)
+{
+       int fd;
+
+       fd = socket(PF_INET, SOCK_DGRAM, 0);
+       if (fd >= 0)
+               return fd;
+       fd = socket(PF_PACKET, SOCK_DGRAM, 0);
+       if (fd >= 0)
+               return fd;
+       return xsocket(PF_INET6, SOCK_DGRAM, 0);
+}
+
+/* Exits on error */
+static void do_chflags(char *dev, uint32_t flags, uint32_t mask)
+{
+       struct ifreq ifr;
+       int fd;
+
+       strncpy_IFNAMSIZ(ifr.ifr_name, dev);
+       fd = get_ctl_fd();
+       xioctl(fd, SIOCGIFFLAGS, &ifr);
+       if ((ifr.ifr_flags ^ flags) & mask) {
+               ifr.ifr_flags &= ~mask;
+               ifr.ifr_flags |= mask & flags;
+               xioctl(fd, SIOCSIFFLAGS, &ifr);
+       }
+       close(fd);
+}
+
+/* Exits on error */
+static void do_changename(char *dev, char *newdev)
+{
+       struct ifreq ifr;
+       int fd;
+
+       strncpy_IFNAMSIZ(ifr.ifr_name, dev);
+       strncpy_IFNAMSIZ(ifr.ifr_newname, newdev);
+       fd = get_ctl_fd();
+       xioctl(fd, SIOCSIFNAME, &ifr);
+       close(fd);
+}
+
+/* Exits on error */
+static void set_qlen(char *dev, int qlen)
+{
+       struct ifreq ifr;
+       int s;
+
+       s = get_ctl_fd();
+       memset(&ifr, 0, sizeof(ifr));
+       strncpy_IFNAMSIZ(ifr.ifr_name, dev);
+       ifr.ifr_qlen = qlen;
+       xioctl(s, SIOCSIFTXQLEN, &ifr);
+       close(s);
+}
+
+/* Exits on error */
+static void set_mtu(char *dev, int mtu)
+{
+       struct ifreq ifr;
+       int s;
+
+       s = get_ctl_fd();
+       memset(&ifr, 0, sizeof(ifr));
+       strncpy_IFNAMSIZ(ifr.ifr_name, dev);
+       ifr.ifr_mtu = mtu;
+       xioctl(s, SIOCSIFMTU, &ifr);
+       close(s);
+}
+
+/* Exits on error */
+static int get_address(char *dev, int *htype)
+{
+       struct ifreq ifr;
+       struct sockaddr_ll me;
+       socklen_t alen;
+       int s;
+
+       s = xsocket(PF_PACKET, SOCK_DGRAM, 0);
+
+       memset(&ifr, 0, sizeof(ifr));
+       strncpy_IFNAMSIZ(ifr.ifr_name, dev);
+       xioctl(s, SIOCGIFINDEX, &ifr);
+
+       memset(&me, 0, sizeof(me));
+       me.sll_family = AF_PACKET;
+       me.sll_ifindex = ifr.ifr_ifindex;
+       me.sll_protocol = htons(ETH_P_LOOP);
+       xbind(s, (struct sockaddr*)&me, sizeof(me));
+
+       alen = sizeof(me);
+       if (getsockname(s, (struct sockaddr*)&me, &alen) == -1) {
+               bb_perror_msg_and_die("getsockname");
+       }
+       close(s);
+       *htype = me.sll_hatype;
+       return me.sll_halen;
+}
+
+/* Exits on error */
+static void parse_address(char *dev, int hatype, int halen, char *lla, struct ifreq *ifr)
+{
+       int alen;
+
+       memset(ifr, 0, sizeof(*ifr));
+       strncpy_IFNAMSIZ(ifr->ifr_name, dev);
+       ifr->ifr_hwaddr.sa_family = hatype;
+
+       alen = hatype == 1/*ARPHRD_ETHER*/ ? 14/*ETH_HLEN*/ : 19/*INFINIBAND_HLEN*/;
+       alen = ll_addr_a2n((unsigned char *)(ifr->ifr_hwaddr.sa_data), alen, lla);
+       if (alen < 0)
+               exit(EXIT_FAILURE);
+       if (alen != halen) {
+               bb_error_msg_and_die("wrong address (%s) length: expected %d bytes", lla, halen);
+       }
+}
+
+/* Exits on error */
+static void set_address(struct ifreq *ifr, int brd)
+{
+       int s;
+
+       s = get_ctl_fd();
+       if (brd)
+               xioctl(s, SIOCSIFHWBROADCAST, ifr);
+       else
+               xioctl(s, SIOCSIFHWADDR, ifr);
+       close(s);
+}
+
+
+static void die_must_be_on_off(const char *msg) NORETURN;
+static void die_must_be_on_off(const char *msg)
+{
+       bb_error_msg_and_die("argument of \"%s\" must be \"on\" or \"off\"", msg);
+}
+
+/* Return value becomes exitcode. It's okay to not return at all */
+static int do_set(char **argv)
+{
+       char *dev = NULL;
+       uint32_t mask = 0;
+       uint32_t flags = 0;
+       int qlen = -1;
+       int mtu = -1;
+       char *newaddr = NULL;
+       char *newbrd = NULL;
+       struct ifreq ifr0, ifr1;
+       char *newname = NULL;
+       int htype, halen;
+       static const char keywords[] ALIGN1 =
+               "up\0""down\0""name\0""mtu\0""multicast\0"
+               "arp\0""address\0""dev\0";
+       enum { ARG_up = 0, ARG_down, ARG_name, ARG_mtu, ARG_multicast,
+               ARG_arp, ARG_addr, ARG_dev };
+       static const char str_on_off[] ALIGN1 = "on\0""off\0";
+       enum { PARM_on = 0, PARM_off };
+       smalluint key;
+
+       while (*argv) {
+               /* substring search ensures that e.g. "addr" and "address"
+                * are both accepted */
+               key = index_in_substrings(keywords, *argv);
+               if (key == ARG_up) {
+                       mask |= IFF_UP;
+                       flags |= IFF_UP;
+               }
+               if (key == ARG_down) {
+                       mask |= IFF_UP;
+                       flags &= ~IFF_UP;
+               }
+               if (key == ARG_name) {
+                       NEXT_ARG();
+                       newname = *argv;
+               }
+               if (key == ARG_mtu) {
+                       NEXT_ARG();
+                       if (mtu != -1)
+                               duparg("mtu", *argv);
+                       mtu = get_unsigned(*argv, "mtu");
+               }
+               if (key == ARG_multicast) {
+                       int param;
+                       NEXT_ARG();
+                       mask |= IFF_MULTICAST;
+                       param = index_in_strings(str_on_off, *argv);
+                       if (param < 0)
+                               die_must_be_on_off("multicast");
+                       if (param == PARM_on)
+                               flags |= IFF_MULTICAST;
+                       else
+                               flags &= ~IFF_MULTICAST;
+               }
+               if (key == ARG_arp) {
+                       int param;
+                       NEXT_ARG();
+                       mask |= IFF_NOARP;
+                       param = index_in_strings(str_on_off, *argv);
+                       if (param < 0)
+                               die_must_be_on_off("arp");
+                       if (param == PARM_on)
+                               flags &= ~IFF_NOARP;
+                       else
+                               flags |= IFF_NOARP;
+               }
+               if (key == ARG_addr) {
+                       NEXT_ARG();
+                       newaddr = *argv;
+               }
+               if (key >= ARG_dev) {
+                       if (key == ARG_dev) {
+                               NEXT_ARG();
+                       }
+                       if (dev)
+                               duparg2("dev", *argv);
+                       dev = *argv;
+               }
+               argv++;
+       }
+
+       if (!dev) {
+               bb_error_msg_and_die(bb_msg_requires_arg, "\"dev\"");
+       }
+
+       if (newaddr || newbrd) {
+               halen = get_address(dev, &htype);
+               if (newaddr) {
+                       parse_address(dev, htype, halen, newaddr, &ifr0);
+               }
+               if (newbrd) {
+                       parse_address(dev, htype, halen, newbrd, &ifr1);
+               }
+       }
+
+       if (newname && strcmp(dev, newname)) {
+               do_changename(dev, newname);
+               dev = newname;
+       }
+       if (qlen != -1) {
+               set_qlen(dev, qlen);
+       }
+       if (mtu != -1) {
+               set_mtu(dev, mtu);
+       }
+       if (newaddr || newbrd) {
+               if (newbrd) {
+                       set_address(&ifr1, 1);
+               }
+               if (newaddr) {
+                       set_address(&ifr0, 0);
+               }
+       }
+       if (mask)
+               do_chflags(dev, flags, mask);
+       return 0;
+}
+
+static int ipaddr_list_link(char **argv)
+{
+       preferred_family = AF_PACKET;
+       return ipaddr_list_or_flush(argv, 0);
+}
+
+/* Return value becomes exitcode. It's okay to not return at all */
+int do_iplink(char **argv)
+{
+       static const char keywords[] ALIGN1 =
+               "set\0""show\0""lst\0""list\0";
+       int key;
+       if (!*argv)
+               return ipaddr_list_link(argv);
+       key = index_in_substrings(keywords, *argv);
+       if (key < 0)
+               bb_error_msg_and_die(bb_msg_invalid_arg, *argv, applet_name);
+       argv++;
+       if (key == 0) /* set */
+               return do_set(argv);
+       /* show, lst, list */
+       return ipaddr_list_link(argv);
+}
diff --git a/networking/libiproute/iproute.c b/networking/libiproute/iproute.c
new file mode 100644 (file)
index 0000000..66557d8
--- /dev/null
@@ -0,0 +1,902 @@
+/* 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, <kuznet@ms2.inr.ac.ru>
+ *
+ *
+ * Changes:
+ *
+ * Rani Assaf <rani@magic.metawire.com> 980929:        resolve addresses
+ * Kunihiro Ishiguro <kunihiro@zebra.org> 001102: rtnh_ifindex was not initialized
+ */
+
+#include "ip_common.h" /* #include "libbb.h" is inside */
+#include "rt_names.h"
+#include "utils.h"
+
+#ifndef RTAX_RTTVAR
+#define RTAX_RTTVAR RTAX_HOPS
+#endif
+
+
+typedef struct filter_t {
+       int tb;
+       smallint 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_t;
+
+#define filter (*(filter_t*)&bb_common_bufsiz1)
+
+static int flush_update(void)
+{
+       if (rtnl_send(filter.rth, filter.flushb, filter.flushp) < 0) {
+               bb_perror_msg("failed to send flush request");
+               return -1;
+       }
+       filter.flushp = 0;
+       return 0;
+}
+
+static unsigned get_hz(void)
+{
+       static unsigned hz_internal;
+       FILE *fp;
+
+       if (hz_internal)
+               return hz_internal;
+
+       fp = fopen_for_read("/proc/net/psched");
+       if (fp) {
+               unsigned nom, denom;
+
+               if (fscanf(fp, "%*08x%*08x%08x%08x", &nom, &denom) == 2)
+                       if (nom == 1000000)
+                               hz_internal = denom;
+               fclose(fp);
+       }
+       if (!hz_internal)
+               hz_internal = sysconf(_SC_CLK_TCK);
+       return hz_internal;
+}
+
+static int print_route(const struct sockaddr_nl *who UNUSED_PARAM,
+               struct nlmsghdr *n, void *arg UNUSED_PARAM)
+{
+       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_and_die("wrong nlmsg len %d", len);
+
+       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())
+                               bb_error_msg_and_die("flush");
+               }
+               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 = 1;
+               return 0;
+       }
+
+       if (n->nlmsg_type == RTM_DELROUTE) {
+               printf("Deleted ");
+       }
+       if (r->rtm_type != RTN_UNICAST && !filter.type) {
+               printf("%s ", rtnl_rtntype_n2a(r->rtm_type, b1, sizeof(b1)));
+       }
+
+       if (tb[RTA_DST]) {
+               if (r->rtm_dst_len != host_len) {
+                       printf("%s/%u ", rt_addr_n2a(r->rtm_family,
+                                               RTA_DATA(tb[RTA_DST]),
+                                               abuf, sizeof(abuf)),
+                                       r->rtm_dst_len
+                                       );
+               } else {
+                       printf("%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) {
+               printf("0/%d ", r->rtm_dst_len);
+       } else {
+               printf("default ");
+       }
+       if (tb[RTA_SRC]) {
+               if (r->rtm_src_len != host_len) {
+                       printf("from %s/%u ", rt_addr_n2a(r->rtm_family,
+                                               RTA_DATA(tb[RTA_SRC]),
+                                               abuf, sizeof(abuf)),
+                                       r->rtm_src_len
+                                       );
+               } else {
+                       printf("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) {
+               printf("from 0/%u ", r->rtm_src_len);
+       }
+       if (tb[RTA_GATEWAY] && filter.rvia.bitlen != host_len) {
+               printf("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) {
+               printf("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.
+                */
+               printf(" src %s ", rt_addr_n2a(r->rtm_family,
+                                       RTA_DATA(tb[RTA_PREFSRC]),
+                                       abuf, sizeof(abuf)));
+       }
+       if (tb[RTA_PRIORITY]) {
+               printf(" metric %d ", *(uint32_t*)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)) {
+                       if (r->rtm_flags & RTM_F_CLONED) {
+                               printf("%c    cache ", _SL_);
+                       }
+                       if (ci->rta_expires) {
+                               printf(" expires %dsec", ci->rta_expires / get_hz());
+                       }
+                       if (ci->rta_error != 0) {
+                               printf(" error %d", ci->rta_error);
+                       }
+               } else if (ci) {
+                       if (ci->rta_error != 0)
+                               printf(" error %d", ci->rta_error);
+               }
+       }
+       if (tb[RTA_IIF] && filter.iifmask != -1) {
+               printf(" iif %s", ll_index_to_name(*(int*)RTA_DATA(tb[RTA_IIF])));
+       }
+       bb_putchar('\n');
+       return 0;
+}
+
+/* Return value becomes exitcode. It's okay to not return at all */
+static int iproute_modify(int cmd, unsigned flags, char **argv)
+{
+       static const char keywords[] ALIGN1 =
+               "src\0""via\0""mtu\0""lock\0""protocol\0"USE_FEATURE_IP_RULE("table\0")
+               "dev\0""oif\0""to\0""metric\0";
+       enum {
+               ARG_src,
+               ARG_via,
+               ARG_mtu, PARM_lock,
+               ARG_protocol,
+USE_FEATURE_IP_RULE(ARG_table,)
+               ARG_dev,
+               ARG_oif,
+               ARG_to,
+               ARG_metric,
+       };
+       enum {
+               gw_ok = 1 << 0,
+               dst_ok = 1 << 1,
+               proto_ok = 1 << 2,
+               type_ok = 1 << 3
+       };
+       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;
+       smalluint ok = 0;
+       int arg;
+
+       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;
+       if (RT_TABLE_MAIN) /* if it is zero, memset already did it */
+               req.r.rtm_table = RT_TABLE_MAIN;
+       if (RT_SCOPE_NOWHERE)
+               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 (*argv) {
+               arg = index_in_substrings(keywords, *argv);
+               if (arg == ARG_src) {
+                       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 (arg == ARG_via) {
+                       inet_prefix addr;
+                       ok |= gw_ok;
+                       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 (arg == ARG_mtu) {
+                       unsigned mtu;
+                       NEXT_ARG();
+                       if (index_in_strings(keywords, *argv) == PARM_lock) {
+                               mxlock |= (1 << RTAX_MTU);
+                               NEXT_ARG();
+                       }
+                       mtu = get_unsigned(*argv, "mtu");
+                       rta_addattr32(mxrta, sizeof(mxbuf), RTAX_MTU, mtu);
+               } else if (arg == ARG_protocol) {
+                       uint32_t prot;
+                       NEXT_ARG();
+                       if (rtnl_rtprot_a2n(&prot, *argv))
+                               invarg(*argv, "protocol");
+                       req.r.rtm_protocol = prot;
+                       ok |= proto_ok;
+#if ENABLE_FEATURE_IP_RULE
+               } else if (arg == ARG_table) {
+                       uint32_t tid;
+                       NEXT_ARG();
+                       if (rtnl_rttable_a2n(&tid, *argv))
+                               invarg(*argv, "table");
+                       req.r.rtm_table = tid;
+#endif
+               } else if (arg == ARG_dev || arg == ARG_oif) {
+                       NEXT_ARG();
+                       d = *argv;
+               } else if (arg == ARG_metric) {
+                       uint32_t metric;
+                       NEXT_ARG();
+                       metric = get_u32(*argv, "metric");
+                       addattr32(&req.n, sizeof(req), RTA_PRIORITY, metric);
+               } else {
+                       int type;
+                       inet_prefix dst;
+
+                       if (arg == ARG_to) {
+                               NEXT_ARG();
+                       }
+                       if ((**argv < '0' || **argv > '9')
+                        && rtnl_rtntype_a2n(&type, *argv) == 0) {
+                               NEXT_ARG();
+                               req.r.rtm_type = type;
+                               ok |= type_ok;
+                       }
+
+                       if (ok & 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;
+                       ok |= dst_ok;
+                       if (dst.bytelen) {
+                               addattr_l(&req.n, sizeof(req), RTA_DST, &dst.data, dst.bytelen);
+                       }
+               }
+               argv++;
+       }
+
+       xrtnl_open(&rth);
+
+       if (d)  {
+               int idx;
+
+               ll_init_map(&rth);
+
+               if (d) {
+                       idx = xll_name_to_index(d);
+                       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_type == RTN_LOCAL || req.r.rtm_type == RTN_NAT)
+               req.r.rtm_scope = RT_SCOPE_HOST;
+       else if (req.r.rtm_type == RTN_BROADCAST ||
+                       req.r.rtm_type == RTN_MULTICAST ||
+                       req.r.rtm_type == RTN_ANYCAST)
+               req.r.rtm_scope = RT_SCOPE_LINK;
+       else if (req.r.rtm_type == RTN_UNICAST || req.r.rtm_type == RTN_UNSPEC) {
+               if (cmd == RTM_DELROUTE)
+                       req.r.rtm_scope = RT_SCOPE_NOWHERE;
+               else if (!(ok & gw_ok))
+                       req.r.rtm_scope = RT_SCOPE_LINK;
+       }
+
+       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) {
+               return 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);
+       if (RTM_GETROUTE)
+               req.nlh.nlmsg_type = RTM_GETROUTE;
+       if (NLM_F_ROOT | NLM_F_REQUEST)
+               req.nlh.nlmsg_flags = NLM_F_ROOT | NLM_F_REQUEST;
+       /*req.nlh.nlmsg_pid = 0; - memset did it already */
+       req.nlh.nlmsg_seq = rth->dump = ++rth->seq;
+       req.rtm.rtm_family = family;
+       if (RTM_F_CLONED)
+               req.rtm.rtm_flags = RTM_F_CLONED;
+
+       return xsendto(rth->fd, (void*)&req, sizeof(req), (struct sockaddr*)&nladdr, sizeof(nladdr));
+}
+
+static void iproute_flush_cache(void)
+{
+       static const char fn[] ALIGN1 = "/proc/sys/net/ipv4/route/flush";
+       int flush_fd = open_or_warn(fn, O_WRONLY);
+
+       if (flush_fd < 0) {
+               return;
+       }
+
+       if (write(flush_fd, "-1", 2) < 2) {
+               bb_perror_msg("cannot flush routing cache");
+               return;
+       }
+       close(flush_fd);
+}
+
+static void iproute_reset_filter(void)
+{
+       memset(&filter, 0, sizeof(filter));
+       filter.mdst.bitlen = -1;
+       filter.msrc.bitlen = -1;
+}
+
+/* Return value becomes exitcode. It's okay to not return at all */
+static int iproute_list_or_flush(char **argv, int flush)
+{
+       int do_ipv6 = preferred_family;
+       struct rtnl_handle rth;
+       char *id = NULL;
+       char *od = NULL;
+       static const char keywords[] ALIGN1 =
+               /* "ip route list/flush" parameters: */
+               "protocol\0" "dev\0"   "oif\0"   "iif\0"
+               "via\0"      "table\0" "cache\0"
+               "from\0"     "to\0"
+               /* and possible further keywords */
+               "all\0"
+               "root\0"
+               "match\0"
+               "exact\0"
+               "main\0"
+               ;
+       enum {
+               KW_proto, KW_dev,   KW_oif,  KW_iif,
+               KW_via,   KW_table, KW_cache,
+               KW_from,  KW_to,
+               /* */
+               KW_all,
+               KW_root,
+               KW_match,
+               KW_exact,
+               KW_main,
+       };
+       int arg, parm;
+
+       iproute_reset_filter();
+       filter.tb = RT_TABLE_MAIN;
+
+       if (flush && !*argv)
+               bb_error_msg_and_die(bb_msg_requires_arg, "\"ip route flush\"");
+
+       while (*argv) {
+               arg = index_in_substrings(keywords, *argv);
+               if (arg == KW_proto) {
+                       uint32_t prot = 0;
+                       NEXT_ARG();
+                       filter.protocolmask = -1;
+                       if (rtnl_rtprot_a2n(&prot, *argv)) {
+                               if (index_in_strings(keywords, *argv) != KW_all)
+                                       invarg(*argv, "protocol");
+                               prot = 0;
+                               filter.protocolmask = 0;
+                       }
+                       filter.protocol = prot;
+               } else if (arg == KW_dev || arg == KW_oif) {
+                       NEXT_ARG();
+                       od = *argv;
+               } else if (arg == KW_iif) {
+                       NEXT_ARG();
+                       id = *argv;
+               } else if (arg == KW_via) {
+                       NEXT_ARG();
+                       get_prefix(&filter.rvia, *argv, do_ipv6);
+               } else if (arg == KW_table) { /* table all/cache/main */
+                       NEXT_ARG();
+                       parm = index_in_substrings(keywords, *argv);
+                       if (parm == KW_cache)
+                               filter.tb = -1;
+                       else if (parm == KW_all)
+                               filter.tb = 0;
+                       else if (parm != KW_main) {
+#if ENABLE_FEATURE_IP_RULE
+                               uint32_t tid;
+                               if (rtnl_rttable_a2n(&tid, *argv))
+                                       invarg(*argv, "table");
+                               filter.tb = tid;
+#else
+                               invarg(*argv, "table");
+#endif
+                       }
+               } else if (arg == KW_cache) {
+                       /* The command 'ip route flush cache' is used by OpenSWAN.
+                        * Assuming it's a synonym for 'ip route flush table cache' */
+                       filter.tb = -1;
+               } else if (arg == KW_from) {
+                       NEXT_ARG();
+                       parm = index_in_substrings(keywords, *argv);
+                       if (parm == KW_root) {
+                               NEXT_ARG();
+                               get_prefix(&filter.rsrc, *argv, do_ipv6);
+                       } else if (parm == KW_match) {
+                               NEXT_ARG();
+                               get_prefix(&filter.msrc, *argv, do_ipv6);
+                       } else {
+                               if (parm == KW_exact)
+                                       NEXT_ARG();
+                               get_prefix(&filter.msrc, *argv, do_ipv6);
+                               filter.rsrc = filter.msrc;
+                       }
+               } else { /* "to" is the default parameter */
+                       if (arg == KW_to) {
+                               NEXT_ARG();
+                               arg = index_in_substrings(keywords, *argv);
+                       }
+                       /* parm = arg; - would be more plausible, but we reuse 'arg' here */
+                       if (arg == KW_root) {
+                               NEXT_ARG();
+                               get_prefix(&filter.rdst, *argv, do_ipv6);
+                       } else if (arg == KW_match) {
+                               NEXT_ARG();
+                               get_prefix(&filter.mdst, *argv, do_ipv6);
+                       } else { /* "to exact" is the default */
+                               if (arg == KW_exact)
+                                       NEXT_ARG();
+                               get_prefix(&filter.mdst, *argv, do_ipv6);
+                               filter.rdst = filter.mdst;
+                       }
+               }
+               argv++;
+       }
+
+       if (do_ipv6 == AF_UNSPEC && filter.tb) {
+               do_ipv6 = AF_INET;
+       }
+
+       xrtnl_open(&rth);
+       ll_init_map(&rth);
+
+       if (id || od)  {
+               int idx;
+
+               if (id) {
+                       idx = xll_name_to_index(id);
+                       filter.iif = idx;
+                       filter.iifmask = -1;
+               }
+               if (od) {
+                       idx = xll_name_to_index(od);
+                       filter.oif = idx;
+                       filter.oifmask = -1;
+               }
+       }
+
+       if (flush) {
+               char flushb[4096-512];
+
+               if (filter.tb == -1) { /* "flush table cache" */
+                       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 (;;) {
+                       xrtnl_wilddump_request(&rth, do_ipv6, RTM_GETROUTE);
+                       filter.flushed = 0;
+                       xrtnl_dump_filter(&rth, print_route, NULL);
+                       if (filter.flushed == 0)
+                               return 0;
+                       if (flush_update())
+                               return 1;
+               }
+       }
+
+       if (filter.tb != -1) {
+               xrtnl_wilddump_request(&rth, do_ipv6, RTM_GETROUTE);
+       } else if (rtnl_rtcache_request(&rth, do_ipv6) < 0) {
+               bb_perror_msg_and_die("cannot send dump request");
+       }
+       xrtnl_dump_filter(&rth, print_route, NULL);
+
+       return 0;
+}
+
+
+/* Return value becomes exitcode. It's okay to not return at all */
+static int iproute_get(char **argv)
+{
+       struct rtnl_handle rth;
+       struct {
+               struct nlmsghdr n;
+               struct rtmsg    r;
+               char            buf[1024];
+       } req;
+       char *idev = NULL;
+       char *odev = NULL;
+       bool connected = 0;
+       bool from_ok = 0;
+       static const char options[] ALIGN1 =
+               "from\0""iif\0""oif\0""dev\0""notify\0""connected\0""to\0";
+
+       memset(&req, 0, sizeof(req));
+
+       iproute_reset_filter();
+
+       req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg));
+       if (NLM_F_REQUEST)
+               req.n.nlmsg_flags = NLM_F_REQUEST;
+       if (RTM_GETROUTE)
+               req.n.nlmsg_type = RTM_GETROUTE;
+       req.r.rtm_family = preferred_family;
+       /*req.r.rtm_table = 0; - memset did this already */
+       /*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 (*argv) {
+               switch (index_in_strings(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;
+                       }
+                       argv++;
+               }
+       }
+
+       if (req.r.rtm_dst_len == 0) {
+               bb_error_msg_and_die("need at least destination address");
+       }
+
+       xrtnl_open(&rth);
+
+       ll_init_map(&rth);
+
+       if (idev || odev)  {
+               int idx;
+
+               if (idev) {
+                       idx = xll_name_to_index(idev);
+                       addattr32(&req.n, sizeof(req), RTA_IIF, idx);
+               }
+               if (odev) {
+                       idx = xll_name_to_index(odev);
+                       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) {
+               return 2;
+       }
+
+       if (connected && !from_ok) {
+               struct rtmsg *r = NLMSG_DATA(&req.n);
+               int len = req.n.nlmsg_len;
+               struct rtattr * tb[RTA_MAX+1];
+
+               print_route(NULL, &req.n, NULL);
+
+               if (req.n.nlmsg_type != RTM_NEWROUTE) {
+                       bb_error_msg_and_die("not a route?");
+               }
+               len -= NLMSG_LENGTH(sizeof(*r));
+               if (len < 0) {
+                       bb_error_msg_and_die("wrong len %d", len);
+               }
+
+               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_and_die("failed to connect the route");
+               }
+               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) {
+                       return 2;
+               }
+       }
+       print_route(NULL, &req.n, NULL);
+       return 0;
+}
+
+/* Return value becomes exitcode. It's okay to not return at all */
+int do_iproute(char **argv)
+{
+       static const char ip_route_commands[] ALIGN1 =
+       /*0-3*/ "add\0""append\0""change\0""chg\0"
+       /*4-7*/ "delete\0""get\0""list\0""show\0"
+       /*8..*/ "prepend\0""replace\0""test\0""flush\0";
+       int command_num;
+       unsigned flags = 0;
+       int cmd = RTM_NEWROUTE;
+
+       if (!*argv)
+               return iproute_list_or_flush(argv, 0);
+
+       /* "Standard" 'ip r a' treats 'a' as 'add', not 'append' */
+       /* It probably means that it is using "first match" rule */
+       command_num = index_in_substrings(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 */
+                       cmd = RTM_DELROUTE;
+                       break;
+               case 5: /* get */
+                       return iproute_get(argv+1);
+               case 6: /* list */
+               case 7: /* show */
+                       return iproute_list_or_flush(argv+1, 0);
+               case 8: /* prepend */
+                       flags = NLM_F_CREATE;
+                       break;
+               case 9: /* replace */
+                       flags = NLM_F_CREATE|NLM_F_REPLACE;
+                       break;
+               case 10: /* test */
+                       flags = NLM_F_EXCL;
+                       break;
+               case 11: /* flush */
+                       return iproute_list_or_flush(argv+1, 1);
+               default:
+                       bb_error_msg_and_die("unknown command %s", *argv);
+       }
+
+       return iproute_modify(cmd, flags, argv+1);
+}
diff --git a/networking/libiproute/iprule.c b/networking/libiproute/iprule.c
new file mode 100644 (file)
index 0000000..6c90c6d
--- /dev/null
@@ -0,0 +1,330 @@
+/* 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, <kuznet@ms2.inr.ac.ru>
+ *
+ *
+ * Changes:
+ *
+ * Rani Assaf <rani@magic.metawire.com> 980929:        resolve addresses
+ * initially integrated into busybox by Bernhard Reutner-Fischer
+ */
+
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <arpa/inet.h>
+
+#include "ip_common.h"  /* #include "libbb.h" is inside */
+#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(const struct sockaddr_nl *who UNUSED_PARAM,
+                                       struct nlmsghdr *n, void *arg UNUSED_PARAM)
+{
+       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])
+               printf("%u:\t", *(unsigned*)RTA_DATA(tb[RTA_PRIORITY]));
+       else
+               printf("0:\t");
+
+       printf("from ");
+       if (tb[RTA_SRC]) {
+               if (r->rtm_src_len != host_len) {
+                       printf("%s/%u", rt_addr_n2a(r->rtm_family,
+                                                        RTA_DATA(tb[RTA_SRC]),
+                                                        abuf, sizeof(abuf)),
+                               r->rtm_src_len
+                               );
+               } else {
+                       fputs(format_host(r->rtm_family,
+                                                      RTA_PAYLOAD(tb[RTA_SRC]),
+                                                      RTA_DATA(tb[RTA_SRC]),
+                                                      abuf, sizeof(abuf)), stdout);
+               }
+       } else if (r->rtm_src_len) {
+               printf("0/%d", r->rtm_src_len);
+       } else {
+               printf("all");
+       }
+       bb_putchar(' ');
+
+       if (tb[RTA_DST]) {
+               if (r->rtm_dst_len != host_len) {
+                       printf("to %s/%u ", rt_addr_n2a(r->rtm_family,
+                                                        RTA_DATA(tb[RTA_DST]),
+                                                        abuf, sizeof(abuf)),
+                               r->rtm_dst_len
+                               );
+               } else {
+                       printf("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) {
+               printf("to 0/%d ", r->rtm_dst_len);
+       }
+
+       if (r->rtm_tos) {
+               printf("tos %s ", rtnl_dsfield_n2a(r->rtm_tos, b1, sizeof(b1)));
+       }
+       if (tb[RTA_PROTOINFO]) {
+               printf("fwmark %#x ", *(uint32_t*)RTA_DATA(tb[RTA_PROTOINFO]));
+       }
+
+       if (tb[RTA_IIF]) {
+               printf("iif %s ", (char*)RTA_DATA(tb[RTA_IIF]));
+       }
+
+       if (r->rtm_table)
+               printf("lookup %s ", rtnl_rttable_n2a(r->rtm_table, b1, sizeof(b1)));
+
+       if (tb[RTA_FLOW]) {
+               uint32_t to = *(uint32_t*)RTA_DATA(tb[RTA_FLOW]);
+               uint32_t from = to>>16;
+               to &= 0xFFFF;
+               if (from) {
+                       printf("realms %s/",
+                               rtnl_rtrealm_n2a(from, b1, sizeof(b1)));
+               }
+               printf("%s ",
+                       rtnl_rtrealm_n2a(to, b1, sizeof(b1)));
+       }
+
+       if (r->rtm_type == RTN_NAT) {
+               if (tb[RTA_GATEWAY]) {
+                       printf("map-to %s ",
+                               format_host(r->rtm_family,
+                                           RTA_PAYLOAD(tb[RTA_GATEWAY]),
+                                           RTA_DATA(tb[RTA_GATEWAY]),
+                                           abuf, sizeof(abuf)));
+               } else
+                       printf("masquerade");
+       } else if (r->rtm_type != RTN_UNICAST)
+               fputs(rtnl_rtntype_n2a(r->rtm_type, b1, sizeof(b1)), stdout);
+
+       bb_putchar('\n');
+       /*fflush(stdout);*/
+       return 0;
+}
+
+/* Return value becomes exitcode. It's okay to not return at all */
+static int iprule_list(char **argv)
+{
+       struct rtnl_handle rth;
+       int af = preferred_family;
+
+       if (af == AF_UNSPEC)
+               af = AF_INET;
+
+       if (*argv) {
+               //bb_error_msg("\"rule show\" needs no arguments");
+               bb_warn_ignoring_args(1);
+               return -1;
+       }
+
+       xrtnl_open(&rth);
+
+       xrtnl_wilddump_request(&rth, af, RTM_GETRULE);
+       xrtnl_dump_filter(&rth, print_rule, NULL);
+
+       return 0;
+}
+
+/* Return value becomes exitcode. It's okay to not return at all */
+static int iprule_modify(int cmd, char **argv)
+{
+       static const char keywords[] ALIGN1 =
+               "from\0""to\0""preference\0""order\0""priority\0"
+               "tos\0""fwmark\0""realms\0""table\0""lookup\0""dev\0"
+               "iif\0""nat\0""map-to\0""type\0""help\0";
+       enum {
+               ARG_from = 1, ARG_to, ARG_preference, ARG_order, ARG_priority,
+               ARG_tos, ARG_fwmark, ARG_realms, ARG_table, ARG_lookup, ARG_dev,
+               ARG_iif, ARG_nat, ARG_map_to, ARG_type, ARG_help
+       };
+       bool table_ok = 0;
+       struct rtnl_handle rth;
+       struct {
+               struct nlmsghdr n;
+               struct rtmsg    r;
+               char            buf[1024];
+       } req;
+       smalluint key;
+
+       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 (*argv) {
+               key = index_in_substrings(keywords, *argv) + 1;
+               if (key == 0) /* no match found in keywords array, bail out. */
+                       bb_error_msg_and_die(bb_msg_invalid_arg, *argv, applet_name);
+               if (key == ARG_from) {
+                       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 (key == ARG_to) {
+                       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 (key == ARG_preference ||
+                          key == ARG_order ||
+                          key == ARG_priority) {
+                       uint32_t pref;
+                       NEXT_ARG();
+                       pref = get_u32(*argv, "preference");
+                       addattr32(&req.n, sizeof(req), RTA_PRIORITY, pref);
+               } else if (key == ARG_tos) {
+                       uint32_t tos;
+                       NEXT_ARG();
+                       if (rtnl_dsfield_a2n(&tos, *argv))
+                               invarg(*argv, "TOS");
+                       req.r.rtm_tos = tos;
+               } else if (key == ARG_fwmark) {
+                       uint32_t fwmark;
+                       NEXT_ARG();
+                       fwmark = get_u32(*argv, "fwmark");
+                       addattr32(&req.n, sizeof(req), RTA_PROTOINFO, fwmark);
+               } else if (key == ARG_realms) {
+                       uint32_t realm;
+                       NEXT_ARG();
+                       if (get_rt_realms(&realm, *argv))
+                               invarg(*argv, "realms");
+                       addattr32(&req.n, sizeof(req), RTA_FLOW, realm);
+               } else if (key == ARG_table ||
+                          key == ARG_lookup) {
+                       uint32_t tid;
+                       NEXT_ARG();
+                       if (rtnl_rttable_a2n(&tid, *argv))
+                               invarg(*argv, "table ID");
+                       req.r.rtm_table = tid;
+                       table_ok = 1;
+               } else if (key == ARG_dev ||
+                          key == ARG_iif) {
+                       NEXT_ARG();
+                       addattr_l(&req.n, sizeof(req), RTA_IIF, *argv, strlen(*argv)+1);
+               } else if (key == ARG_nat ||
+                          key == ARG_map_to) {
+                       NEXT_ARG();
+                       addattr32(&req.n, sizeof(req), RTA_GATEWAY, get_addr32(*argv));
+                       req.r.rtm_type = RTN_NAT;
+               } else {
+                       int type;
+
+                       if (key == ARG_type) {
+                               NEXT_ARG();
+                       }
+                       if (key == ARG_help)
+                               bb_show_usage();
+                       if (rtnl_rtntype_a2n(&type, *argv))
+                               invarg(*argv, "type");
+                       req.r.rtm_type = type;
+               }
+               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;
+
+       xrtnl_open(&rth);
+
+       if (rtnl_talk(&rth, &req.n, 0, 0, NULL, NULL, NULL) < 0)
+               return 2;
+
+       return 0;
+}
+
+/* Return value becomes exitcode. It's okay to not return at all */
+int do_iprule(char **argv)
+{
+       static const char ip_rule_commands[] ALIGN1 =
+               "add\0""delete\0""list\0""show\0";
+       int cmd = 2; /* list */
+
+       if (!*argv)
+               return iprule_list(argv);
+
+       cmd = index_in_substrings(ip_rule_commands, *argv);
+       switch (cmd) {
+               case 0: /* add */
+                       cmd = RTM_NEWRULE;
+                       break;
+               case 1: /* delete */
+                       cmd = RTM_DELRULE;
+                       break;
+               case 2: /* list */
+               case 3: /* show */
+                       return iprule_list(argv+1);
+                       break;
+               default:
+                       bb_error_msg_and_die("unknown command %s", *argv);
+       }
+       return iprule_modify(cmd, argv+1);
+}
diff --git a/networking/libiproute/iptunnel.c b/networking/libiproute/iptunnel.c
new file mode 100644 (file)
index 0000000..6a841aa
--- /dev/null
@@ -0,0 +1,572 @@
+/* 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, <kuznet@ms2.inr.ac.ru>
+ *
+ * Changes:
+ *
+ * Rani Assaf <rani@magic.metawire.com> 980929:        resolve addresses
+ * Rani Assaf <rani@magic.metawire.com> 980930:        do not allow key for ipip/sit
+ * Phil Karn <karn@ka9q.ampr.org>      990408: "pmtudisc" flag
+ */
+
+#include <netinet/ip.h>
+#include <net/if.h>
+#include <net/if_arp.h>
+#include <asm/types.h>
+
+#ifndef __constant_htons
+#define __constant_htons htons
+#endif
+
+// FYI: #define SIOCDEVPRIVATE 0x89F0
+
+/* From linux/if_tunnel.h. #including it proved troublesome
+ * (redefiniton errors due to name collisions in linux/ and net[inet]/) */
+#define SIOCGETTUNNEL   (SIOCDEVPRIVATE + 0)
+#define SIOCADDTUNNEL   (SIOCDEVPRIVATE + 1)
+#define SIOCDELTUNNEL   (SIOCDEVPRIVATE + 2)
+#define SIOCCHGTUNNEL   (SIOCDEVPRIVATE + 3)
+//#define SIOCGETPRL      (SIOCDEVPRIVATE + 4)
+//#define SIOCADDPRL      (SIOCDEVPRIVATE + 5)
+//#define SIOCDELPRL      (SIOCDEVPRIVATE + 6)
+//#define SIOCCHGPRL      (SIOCDEVPRIVATE + 7)
+#define GRE_CSUM        __constant_htons(0x8000)
+//#define GRE_ROUTING     __constant_htons(0x4000)
+#define GRE_KEY         __constant_htons(0x2000)
+#define GRE_SEQ         __constant_htons(0x1000)
+//#define GRE_STRICT      __constant_htons(0x0800)
+//#define GRE_REC         __constant_htons(0x0700)
+//#define GRE_FLAGS       __constant_htons(0x00F8)
+//#define GRE_VERSION     __constant_htons(0x0007)
+struct ip_tunnel_parm {
+       char            name[IFNAMSIZ];
+       int             link;
+       uint16_t        i_flags;
+       uint16_t        o_flags;
+       uint32_t        i_key;
+       uint32_t        o_key;
+       struct iphdr    iph;
+};
+/* SIT-mode i_flags */
+//#define SIT_ISATAP 0x0001
+//struct ip_tunnel_prl {
+//     uint32_t          addr;
+//     uint16_t          flags;
+//     uint16_t          __reserved;
+//     uint32_t          datalen;
+//     uint32_t          __reserved2;
+//     /* data follows */
+//};
+///* PRL flags */
+//#define PRL_DEFAULT 0x0001
+
+#include "ip_common.h"  /* #include "libbb.h" is inside */
+#include "rt_names.h"
+#include "utils.h"
+
+
+/* Dies on error */
+static int do_ioctl_get_ifindex(char *dev)
+{
+       struct ifreq ifr;
+       int fd;
+
+       strncpy_IFNAMSIZ(ifr.ifr_name, dev);
+       fd = xsocket(AF_INET, SOCK_DGRAM, 0);
+       xioctl(fd, SIOCGIFINDEX, &ifr);
+       close(fd);
+       return ifr.ifr_ifindex;
+}
+
+static int do_ioctl_get_iftype(char *dev)
+{
+       struct ifreq ifr;
+       int fd;
+       int err;
+
+       strncpy_IFNAMSIZ(ifr.ifr_name, dev);
+       fd = xsocket(AF_INET, SOCK_DGRAM, 0);
+       err = ioctl_or_warn(fd, SIOCGIFHWADDR, &ifr);
+       close(fd);
+       return err ? -1 : ifr.ifr_addr.sa_family;
+}
+
+static char *do_ioctl_get_ifname(int idx)
+{
+       struct ifreq ifr;
+       int fd;
+       int err;
+
+       ifr.ifr_ifindex = idx;
+       fd = xsocket(AF_INET, SOCK_DGRAM, 0);
+       err = ioctl_or_warn(fd, SIOCGIFNAME, &ifr);
+       close(fd);
+       return err ? NULL : xstrndup(ifr.ifr_name, sizeof(ifr.ifr_name));
+}
+
+static int do_get_ioctl(const char *basedev, struct ip_tunnel_parm *p)
+{
+       struct ifreq ifr;
+       int fd;
+       int err;
+
+       strncpy_IFNAMSIZ(ifr.ifr_name, basedev);
+       ifr.ifr_ifru.ifru_data = (void*)p;
+       fd = xsocket(AF_INET, SOCK_DGRAM, 0);
+       err = ioctl_or_warn(fd, SIOCGETTUNNEL, &ifr);
+       close(fd);
+       return err;
+}
+
+/* Dies on error, otherwise returns 0 */
+static int do_add_ioctl(int cmd, const char *basedev, struct ip_tunnel_parm *p)
+{
+       struct ifreq ifr;
+       int fd;
+
+       if (cmd == SIOCCHGTUNNEL && p->name[0]) {
+               strncpy_IFNAMSIZ(ifr.ifr_name, p->name);
+       } else {
+               strncpy_IFNAMSIZ(ifr.ifr_name, basedev);
+       }
+       ifr.ifr_ifru.ifru_data = (void*)p;
+       fd = xsocket(AF_INET, SOCK_DGRAM, 0);
+#if ENABLE_IOCTL_HEX2STR_ERROR
+       /* #define magic will turn ioctl# into string */
+       if (cmd == SIOCCHGTUNNEL)
+               xioctl(fd, SIOCCHGTUNNEL, &ifr);
+       else
+               xioctl(fd, SIOCADDTUNNEL, &ifr);
+#else
+       xioctl(fd, cmd, &ifr);
+#endif
+       close(fd);
+       return 0;
+}
+
+/* Dies on error, otherwise returns 0 */
+static int do_del_ioctl(const char *basedev, struct ip_tunnel_parm *p)
+{
+       struct ifreq ifr;
+       int fd;
+
+       if (p->name[0]) {
+               strncpy_IFNAMSIZ(ifr.ifr_name, p->name);
+       } else {
+               strncpy_IFNAMSIZ(ifr.ifr_name, basedev);
+       }
+       ifr.ifr_ifru.ifru_data = (void*)p;
+       fd = xsocket(AF_INET, SOCK_DGRAM, 0);
+       xioctl(fd, SIOCDELTUNNEL, &ifr);
+       close(fd);
+       return 0;
+}
+
+/* Dies on error */
+static void parse_args(char **argv, int cmd, struct ip_tunnel_parm *p)
+{
+       static const char keywords[] ALIGN1 =
+               "mode\0""ipip\0""ip/ip\0""gre\0""gre/ip\0""sit\0""ipv6/ip\0"
+               "key\0""ikey\0""okey\0""seq\0""iseq\0""oseq\0"
+               "csum\0""icsum\0""ocsum\0""nopmtudisc\0""pmtudisc\0"
+               "remote\0""any\0""local\0""dev\0"
+               "ttl\0""inherit\0""tos\0""dsfield\0"
+               "name\0";
+       enum {
+               ARG_mode, ARG_ipip, ARG_ip_ip, ARG_gre, ARG_gre_ip, ARG_sit, ARG_ip6_ip,
+               ARG_key, ARG_ikey, ARG_okey, ARG_seq, ARG_iseq, ARG_oseq,
+               ARG_csum, ARG_icsum, ARG_ocsum, ARG_nopmtudisc, ARG_pmtudisc,
+               ARG_remote, ARG_any, ARG_local, ARG_dev,
+               ARG_ttl, ARG_inherit, ARG_tos, ARG_dsfield,
+               ARG_name
+       };
+       int count = 0;
+       char medium[IFNAMSIZ];
+       int key;
+
+       memset(p, 0, sizeof(*p));
+       medium[0] = '\0';
+
+       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 (*argv) {
+               key = index_in_strings(keywords, *argv);
+               if (key == ARG_mode) {
+                       NEXT_ARG();
+                       key = index_in_strings(keywords, *argv);
+                       if (key == ARG_ipip ||
+                           key == ARG_ip_ip) {
+                               if (p->iph.protocol && p->iph.protocol != IPPROTO_IPIP) {
+                                       bb_error_msg_and_die("%s tunnel mode", "you managed to ask for more than one");
+                               }
+                               p->iph.protocol = IPPROTO_IPIP;
+                       } else if (key == ARG_gre ||
+                                  key == ARG_gre_ip) {
+                               if (p->iph.protocol && p->iph.protocol != IPPROTO_GRE) {
+                                       bb_error_msg_and_die("%s tunnel mode", "you managed to ask for more than one");
+                               }
+                               p->iph.protocol = IPPROTO_GRE;
+                       } else if (key == ARG_sit ||
+                                  key == ARG_ip6_ip) {
+                               if (p->iph.protocol && p->iph.protocol != IPPROTO_IPV6) {
+                                       bb_error_msg_and_die("%s tunnel mode", "you managed to ask for more than one");
+                               }
+                               p->iph.protocol = IPPROTO_IPV6;
+                       } else {
+                               bb_error_msg_and_die("%s tunnel mode", "cannot guess");
+                       }
+               } else if (key == ARG_key) {
+                       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 {
+                               uval = get_unsigned(*argv, "key");
+                               p->i_key = p->o_key = htonl(uval);
+                       }
+               } else if (key == ARG_ikey) {
+                       unsigned uval;
+                       NEXT_ARG();
+                       p->i_flags |= GRE_KEY;
+                       if (strchr(*argv, '.'))
+                               p->o_key = get_addr32(*argv);
+                       else {
+                               uval = get_unsigned(*argv, "ikey");
+                               p->i_key = htonl(uval);
+                       }
+               } else if (key == ARG_okey) {
+                       unsigned uval;
+                       NEXT_ARG();
+                       p->o_flags |= GRE_KEY;
+                       if (strchr(*argv, '.'))
+                               p->o_key = get_addr32(*argv);
+                       else {
+                               uval = get_unsigned(*argv, "okey");
+                               p->o_key = htonl(uval);
+                       }
+               } else if (key == ARG_seq) {
+                       p->i_flags |= GRE_SEQ;
+                       p->o_flags |= GRE_SEQ;
+               } else if (key == ARG_iseq) {
+                       p->i_flags |= GRE_SEQ;
+               } else if (key == ARG_oseq) {
+                       p->o_flags |= GRE_SEQ;
+               } else if (key == ARG_csum) {
+                       p->i_flags |= GRE_CSUM;
+                       p->o_flags |= GRE_CSUM;
+               } else if (key == ARG_icsum) {
+                       p->i_flags |= GRE_CSUM;
+               } else if (key == ARG_ocsum) {
+                       p->o_flags |= GRE_CSUM;
+               } else if (key == ARG_nopmtudisc) {
+                       p->iph.frag_off = 0;
+               } else if (key == ARG_pmtudisc) {
+                       p->iph.frag_off = htons(IP_DF);
+               } else if (key == ARG_remote) {
+                       NEXT_ARG();
+                       key = index_in_strings(keywords, *argv);
+                       if (key != ARG_any)
+                               p->iph.daddr = get_addr32(*argv);
+               } else if (key == ARG_local) {
+                       NEXT_ARG();
+                       key = index_in_strings(keywords, *argv);
+                       if (key != ARG_any)
+                               p->iph.saddr = get_addr32(*argv);
+               } else if (key == ARG_dev) {
+                       NEXT_ARG();
+                       strncpy_IFNAMSIZ(medium, *argv);
+               } else if (key == ARG_ttl) {
+                       unsigned uval;
+                       NEXT_ARG();
+                       key = index_in_strings(keywords, *argv);
+                       if (key != ARG_inherit) {
+                               uval = get_unsigned(*argv, "TTL");
+                               if (uval > 255)
+                                       invarg(*argv, "TTL must be <=255");
+                               p->iph.ttl = uval;
+                       }
+               } else if (key == ARG_tos ||
+                          key == ARG_dsfield) {
+                       uint32_t uval;
+                       NEXT_ARG();
+                       key = index_in_strings(keywords, *argv);
+                       if (key != ARG_inherit) {
+                               if (rtnl_dsfield_a2n(&uval, *argv))
+                                       invarg(*argv, "TOS");
+                               p->iph.tos = uval;
+                       } else
+                               p->iph.tos = 1;
+               } else {
+                       if (key == ARG_name) {
+                               NEXT_ARG();
+                       }
+                       if (p->name[0])
+                               duparg2("name", *argv);
+                       strncpy_IFNAMSIZ(p->name, *argv);
+                       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))
+                                       exit(EXIT_FAILURE);
+                               *p = old_p;
+                       }
+               }
+               count++;
+               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_and_die("keys are not allowed with ipip and sit");
+               }
+       }
+
+       if (medium[0]) {
+               p->link = do_ioctl_get_ifindex(medium);
+       }
+
+       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_and_die("broadcast tunnel requires a source address");
+       }
+}
+
+/* Return value becomes exitcode. It's okay to not return at all */
+static int do_add(int cmd, char **argv)
+{
+       struct ip_tunnel_parm p;
+
+       parse_args(argv, cmd, &p);
+
+       if (p.iph.ttl && p.iph.frag_off == 0) {
+               bb_error_msg_and_die("ttl != 0 and noptmudisc are incompatible");
+       }
+
+       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_and_die("cannot determine tunnel mode (ipip, gre or sit)");
+       }
+}
+
+/* Return value becomes exitcode. It's okay to not return at all */
+static int do_del(char **argv)
+{
+       struct ip_tunnel_parm p;
+
+       parse_args(argv, SIOCDELTUNNEL, &p);
+
+       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);
+       }
+}
+
+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);
+                       free(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("%c  Drop packets out of sequence.\n", _SL_);
+       if (p->i_flags & GRE_CSUM)
+               printf("%c  Checksum in received packet is required.", _SL_);
+       if (p->o_flags & GRE_SEQ)
+               printf("%c  Sequence packets on output.", _SL_);
+       if (p->o_flags & GRE_CSUM)
+               printf("%c  Checksum output packets.", _SL_);
+}
+
+static void 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_or_warn("/proc/net/dev", "r");
+
+       if (fp == NULL) {
+               return;
+       }
+       /* skip headers */
+       fgets(buf, sizeof(buf), fp);
+       fgets(buf, sizeof(buf), fp);
+
+       while (fgets(buf, sizeof(buf), fp) != NULL) {
+               char *ptr;
+
+               /*buf[sizeof(buf) - 1] = 0; - fgets is safe anyway */
+               ptr = strchr(buf, ':');
+               if (ptr == NULL ||
+                   (*ptr++ = 0, sscanf(buf, "%s", name) != 1)) {
+                       bb_error_msg("wrong format of /proc/net/dev");
+                       return;
+               }
+               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("cannot 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);
+               bb_putchar('\n');
+       }
+}
+
+/* Return value becomes exitcode. It's okay to not return at all */
+static int do_show(char **argv)
+{
+       int err;
+       struct ip_tunnel_parm p;
+
+       parse_args(argv, SIOCGETTUNNEL, &p);
+
+       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);
+       bb_putchar('\n');
+       return 0;
+}
+
+/* Return value becomes exitcode. It's okay to not return at all */
+int do_iptunnel(char **argv)
+{
+       static const char keywords[] ALIGN1 =
+               "add\0""change\0""delete\0""show\0""list\0""lst\0";
+       enum { ARG_add = 0, ARG_change, ARG_del, ARG_show, ARG_list, ARG_lst };
+       int key;
+
+       if (*argv) {
+               key = index_in_substrings(keywords, *argv);
+               if (key < 0)
+                       bb_error_msg_and_die(bb_msg_invalid_arg, *argv, applet_name);
+               argv++;
+               if (key == ARG_add)
+                       return do_add(SIOCADDTUNNEL, argv);
+               if (key == ARG_change)
+                       return do_add(SIOCCHGTUNNEL, argv);
+               if (key == ARG_del)
+                       return do_del(argv);
+       }
+       return do_show(argv);
+}
diff --git a/networking/libiproute/libnetlink.c b/networking/libiproute/libnetlink.c
new file mode 100644 (file)
index 0000000..6d51d8d
--- /dev/null
@@ -0,0 +1,409 @@
+/* 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, <kuznet@ms2.inr.ac.ru>
+ *
+ */
+
+#include <sys/socket.h>
+#include <sys/uio.h>
+
+#include "libbb.h"
+#include "libnetlink.h"
+
+void FAST_FUNC rtnl_close(struct rtnl_handle *rth)
+{
+       close(rth->fd);
+}
+
+int FAST_FUNC xrtnl_open(struct rtnl_handle *rth/*, unsigned subscriptions*/)
+{
+       socklen_t addr_len;
+
+       memset(rth, 0, sizeof(rth));
+
+       rth->fd = xsocket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
+
+       memset(&rth->local, 0, sizeof(rth->local));
+       rth->local.nl_family = AF_NETLINK;
+       /*rth->local.nl_groups = subscriptions;*/
+
+       xbind(rth->fd, (struct sockaddr*)&rth->local, sizeof(rth->local));
+       addr_len = sizeof(rth->local);
+       if (getsockname(rth->fd, (struct sockaddr*)&rth->local, &addr_len) < 0)
+               bb_perror_msg_and_die("getsockname");
+       if (addr_len != sizeof(rth->local))
+               bb_error_msg_and_die("wrong address length %d", addr_len);
+       if (rth->local.nl_family != AF_NETLINK)
+               bb_error_msg_and_die("wrong address family %d", rth->local.nl_family);
+       rth->seq = time(NULL);
+       return 0;
+}
+
+int FAST_FUNC xrtnl_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 xsendto(rth->fd, (void*)&req, sizeof(req),
+                                (struct sockaddr*)&nladdr, sizeof(nladdr));
+}
+
+int FAST_FUNC 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 xsendto(rth->fd, buf, len, (struct sockaddr*)&nladdr, sizeof(nladdr));
+}
+
+int FAST_FUNC 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);
+}
+
+static int rtnl_dump_filter(struct rtnl_handle *rth,
+               int (*filter)(const struct sockaddr_nl *, struct nlmsghdr *n, void *),
+               void *arg1/*,
+               int (*junk)(struct sockaddr_nl *, struct nlmsghdr *n, void *),
+               void *arg2*/)
+{
+       int retval = -1;
+       char *buf = xmalloc(8*1024); /* avoid big stack buffer */
+       struct sockaddr_nl nladdr;
+       struct iovec iov = { buf, 8*1024 };
+
+       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");
+                       goto ret;
+               }
+               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) {
+//                                             retval = err;
+//                                             goto ret;
+//                                     }
+//                             }
+                               goto skip_it;
+                       }
+
+                       if (h->nlmsg_type == NLMSG_DONE) {
+                               goto ret_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");
+                               }
+                               goto ret;
+                       }
+                       err = filter(&nladdr, h, arg1);
+                       if (err < 0) {
+                               retval = err;
+                               goto ret;
+                       }
+
+ 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);
+               }
+       } /* while (1) */
+ ret_0:
+       retval++; /* = 0 */
+ ret:
+       free(buf);
+       return retval;
+}
+
+int FAST_FUNC xrtnl_dump_filter(struct rtnl_handle *rth,
+               int (*filter)(const struct sockaddr_nl *, struct nlmsghdr *, void *),
+               void *arg1)
+{
+       int ret = rtnl_dump_filter(rth, filter, arg1/*, NULL, NULL*/);
+       if (ret < 0)
+               bb_error_msg_and_die("dump terminated");
+       return ret;
+}
+
+int FAST_FUNC rtnl_talk(struct rtnl_handle *rtnl, struct nlmsghdr *n,
+             pid_t peer, unsigned groups,
+             struct nlmsghdr *answer,
+             int (*junk)(struct sockaddr_nl *, struct nlmsghdr *, void *),
+             void *jarg)
+{
+/* bbox doesn't use parameters no. 3, 4, 6, 7, they are stubbed out */
+#define peer   0
+#define groups 0
+#define junk   NULL
+#define jarg   NULL
+       int retval = -1;
+       int status;
+       unsigned seq;
+       struct nlmsghdr *h;
+       struct sockaddr_nl nladdr;
+       struct iovec iov = { (void*)n, n->nlmsg_len };
+       char   *buf = xmalloc(8*1024); /* avoid big stack buffer */
+       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");
+               goto ret;
+       }
+
+       iov.iov_base = buf;
+
+       while (1) {
+               iov.iov_len = 8*1024;
+               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");
+                       goto ret;
+               }
+               if (msg.msg_namelen != sizeof(nladdr)) {
+                       bb_error_msg_and_die("sender address length == %d", msg.msg_namelen);
+               }
+               for (h = (struct nlmsghdr*)buf; status >= (int)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");
+                                       goto ret;
+                               }
+                               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) {
+//                                             retval = l_err;
+//                                             goto ret;
+//                                     }
+//                             }
+                               continue;
+                       }
+
+                       if (h->nlmsg_type == NLMSG_ERROR) {
+                               struct nlmsgerr *err = (struct nlmsgerr*)NLMSG_DATA(h);
+                               if (l < (int)sizeof(struct nlmsgerr)) {
+                                       bb_error_msg("ERROR truncated");
+                               } else {
+                                       errno = - err->error;
+                                       if (errno == 0) {
+                                               if (answer) {
+                                                       memcpy(answer, h, h->nlmsg_len);
+                                               }
+                                               goto ret_0;
+                                       }
+                                       bb_perror_msg("RTNETLINK answers");
+                               }
+                               goto ret;
+                       }
+                       if (answer) {
+                               memcpy(answer, h, h->nlmsg_len);
+                               goto ret_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);
+               }
+       } /* while (1) */
+ ret_0:
+       retval++; /* = 0 */
+ ret:
+       free(buf);
+       return retval;
+}
+
+int FAST_FUNC addattr32(struct nlmsghdr *n, int maxlen, int type, uint32_t data)
+{
+       int len = RTA_LENGTH(4);
+       struct rtattr *rta;
+       if ((int)(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;
+       move_to_unaligned32(RTA_DATA(rta), data);
+       n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + len;
+       return 0;
+}
+
+int FAST_FUNC addattr_l(struct nlmsghdr *n, int maxlen, int type, void *data, int alen)
+{
+       int len = RTA_LENGTH(alen);
+       struct rtattr *rta;
+
+       if ((int)(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 FAST_FUNC rta_addattr32(struct rtattr *rta, int maxlen, int type, uint32_t 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;
+       move_to_unaligned32(RTA_DATA(subrta), data);
+       rta->rta_len = NLMSG_ALIGN(rta->rta_len) + len;
+       return 0;
+}
+
+int FAST_FUNC 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 FAST_FUNC 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 (file)
index 0000000..e5fee4d
--- /dev/null
@@ -0,0 +1,50 @@
+/* vi: set sw=4 ts=4: */
+#ifndef LIBNETLINK_H
+#define LIBNETLINK_H 1
+
+#include <linux/types.h>
+/* We need linux/types.h because older kernels use __u32 etc
+ * in linux/[rt]netlink.h. 2.6.19 seems to be ok, though */
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+
+PUSH_AND_SET_FUNCTION_VISIBILITY_TO_HIDDEN
+
+struct rtnl_handle
+{
+       int                     fd;
+       struct sockaddr_nl      local;
+       struct sockaddr_nl      peer;
+       uint32_t                seq;
+       uint32_t                dump;
+};
+
+extern int xrtnl_open(struct rtnl_handle *rth) FAST_FUNC;
+extern void rtnl_close(struct rtnl_handle *rth) FAST_FUNC;
+extern int xrtnl_wilddump_request(struct rtnl_handle *rth, int fam, int type) FAST_FUNC;
+extern int rtnl_dump_request(struct rtnl_handle *rth, int type, void *req, int len) FAST_FUNC;
+extern int xrtnl_dump_filter(struct rtnl_handle *rth,
+                       int (*filter)(const struct sockaddr_nl*, struct nlmsghdr *n, void*),
+                       void *arg1) FAST_FUNC;
+
+/* bbox doesn't use parameters no. 3, 4, 6, 7, stub them out */
+#define rtnl_talk(rtnl, n, peer, groups, answer, junk, jarg) \
+       rtnl_talk(rtnl, n, answer)
+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) FAST_FUNC;
+
+extern int rtnl_send(struct rtnl_handle *rth, char *buf, int) FAST_FUNC;
+
+
+extern int addattr32(struct nlmsghdr *n, int maxlen, int type, uint32_t data) FAST_FUNC;
+extern int addattr_l(struct nlmsghdr *n, int maxlen, int type, void *data, int alen) FAST_FUNC;
+extern int rta_addattr32(struct rtattr *rta, int maxlen, int type, uint32_t data) FAST_FUNC;
+extern int rta_addattr_l(struct rtattr *rta, int maxlen, int type, void *data, int alen) FAST_FUNC;
+
+extern int parse_rtattr(struct rtattr *tb[], int max, struct rtattr *rta, int len) FAST_FUNC;
+
+POP_SAVED_FUNCTION_VISIBILITY
+
+#endif
diff --git a/networking/libiproute/ll_addr.c b/networking/libiproute/ll_addr.c
new file mode 100644 (file)
index 0000000..f50e371
--- /dev/null
@@ -0,0 +1,79 @@
+/* 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, <kuznet@ms2.inr.ac.ru>
+ */
+
+#include <net/if_arp.h>
+
+#include "libbb.h"
+#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<alen; i++) {
+               if (i==0) {
+                       snprintf(buf+l, blen, ":%02x"+1, addr[i]);
+                       blen -= 2;
+                       l += 2;
+               } else {
+                       snprintf(buf+l, blen, ":%02x", addr[i]);
+                       blen -= 3;
+                       l += 3;
+               }
+       }
+       return buf;
+}
+
+int ll_addr_a2n(unsigned char *lladdr, int len, char *arg)
+{
+       int i;
+
+       if (strchr(arg, '.')) {
+               inet_prefix pfx;
+               if (get_addr_1(&pfx, arg, AF_INET)) {
+                       bb_error_msg("\"%s\" is invalid lladdr", arg);
+                       return -1;
+               }
+               if (len < 4) {
+                       return -1;
+               }
+               memcpy(lladdr, pfx.data, 4);
+               return 4;
+       }
+
+       for (i = 0; i < len; i++) {
+               int temp;
+               char *cp = strchr(arg, ':');
+               if (cp) {
+                       *cp = 0;
+                       cp++;
+               }
+               if (sscanf(arg, "%x", &temp) != 1 || (temp < 0 || temp > 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 (file)
index 0000000..2ed7fbb
--- /dev/null
@@ -0,0 +1,200 @@
+/* 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, <kuznet@ms2.inr.ac.ru>
+ *
+ */
+
+#include <net/if.h>    /* struct ifreq and co. */
+
+#include "libbb.h"
+#include "libnetlink.h"
+#include "ll_map.h"
+
+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];
+
+static struct idxmap *find_by_index(int idx)
+{
+       struct idxmap *im;
+
+       for (im = idxmap[idx & 0xF]; im; im = im->next)
+               if (im->index == idx)
+                       return im;
+       return NULL;
+}
+
+int ll_remember_index(const struct sockaddr_nl *who UNUSED_PARAM,
+               struct nlmsghdr *n,
+               void *arg UNUSED_PARAM)
+{
+       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)
+                       goto found;
+
+       im = xmalloc(sizeof(*im));
+       im->next = *imp;
+       im->index = ifi->ifi_index;
+       *imp = im;
+ found:
+       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 > (int)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 "*";
+       im = find_by_index(idx);
+       if (im)
+               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);
+}
+
+#ifdef UNUSED
+int ll_index_to_type(int idx)
+{
+       struct idxmap *im;
+
+       if (idx == 0)
+               return -1;
+       im = find_by_index(idx);
+       if (im)
+               return im->type;
+       return -1;
+}
+#endif
+
+unsigned ll_index_to_flags(int idx)
+{
+       struct idxmap *im;
+
+       if (idx == 0)
+               return 0;
+       im = find_by_index(idx);
+       if (im)
+               return im->flags;
+       return 0;
+}
+
+int xll_name_to_index(const char *const name)
+{
+       int ret = 0;
+       int sock_fd;
+
+/* caching is not warranted - no users which repeatedly call it */
+#ifdef UNUSED
+       static char ncache[16];
+       static int icache;
+
+       struct idxmap *im;
+       int i;
+
+       if (name == NULL)
+               goto out;
+       if (icache && strcmp(name, ncache) == 0) {
+               ret = icache;
+               goto out;
+       }
+       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);
+                               ret = im->index;
+                               goto out;
+                       }
+               }
+       }
+       /* 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 */
+#endif
+
+       sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
+       if (sock_fd >= 0) {
+               struct ifreq ifr;
+               int tmp;
+
+               strncpy_IFNAMSIZ(ifr.ifr_name, name);
+               ifr.ifr_ifindex = -1;
+               tmp = ioctl(sock_fd, SIOCGIFINDEX, &ifr);
+               close(sock_fd);
+               if (tmp >= 0)
+                       /* In theory, we should redump the interface list
+                        * to update our cache, this is left as an exercise
+                        * to the reader... Jean II */
+                       ret = ifr.ifr_ifindex;
+       }
+/* out:*/
+       if (ret <= 0)
+               bb_error_msg_and_die("cannot find device \"%s\"", name);
+       return ret;
+}
+
+int ll_init_map(struct rtnl_handle *rth)
+{
+       xrtnl_wilddump_request(rth, AF_UNSPEC, RTM_GETLINK);
+       xrtnl_dump_filter(rth, ll_remember_index, &idxmap);
+       return 0;
+}
diff --git a/networking/libiproute/ll_map.h b/networking/libiproute/ll_map.h
new file mode 100644 (file)
index 0000000..3966def
--- /dev/null
@@ -0,0 +1,17 @@
+/* vi: set sw=4 ts=4: */
+#ifndef LL_MAP_H
+#define LL_MAP_H 1
+
+PUSH_AND_SET_FUNCTION_VISIBILITY_TO_HIDDEN
+
+int ll_remember_index(const struct sockaddr_nl *who, struct nlmsghdr *n, void *arg);
+int ll_init_map(struct rtnl_handle *rth);
+int xll_name_to_index(const char *const name);
+const char *ll_index_to_name(int idx);
+const char *ll_idx_n2a(int idx, char *buf);
+/* int ll_index_to_type(int idx); */
+unsigned ll_index_to_flags(int idx);
+
+POP_SAVED_FUNCTION_VISIBILITY
+
+#endif
diff --git a/networking/libiproute/ll_proto.c b/networking/libiproute/ll_proto.c
new file mode 100644 (file)
index 0000000..a934935
--- /dev/null
@@ -0,0 +1,129 @@
+/* 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, <kuznet@ms2.inr.ac.ru>
+ */
+
+#include "libbb.h"
+#include "rt_names.h"
+#include "utils.h"
+
+#if defined(__GLIBC__) && __GLIBC__ >=2 && __GLIBC_MINOR__ >= 1
+#include <net/ethernet.h>
+#else
+#include <linux/if_ether.h>
+#endif
+
+#if !ENABLE_WERROR
+#warning de-bloat
+#endif
+/* Before re-enabling this, please (1) conditionalize exotic protocols
+ * on CONFIG_something, and (2) decouple strings and numbers
+ * (use llproto_ids[] = n,n,n..; and llproto_names[] = "loop\0" "pup\0" ...;)
+ */
+
+#define __PF(f,n) { ETH_P_##f, #n },
+static struct {
+       int id;
+       const 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)
+{
+       unsigned i;
+       id = ntohs(id);
+       for (i = 0; i < ARRAY_SIZE(llproto_names); i++) {
+                if (llproto_names[i].id == id)
+                       return llproto_names[i].name;
+       }
+       snprintf(buf, len, "[%d]", id);
+       return buf;
+}
+
+int ll_proto_a2n(unsigned short *id, char *buf)
+{
+       unsigned i;
+       for (i = 0; i < ARRAY_SIZE(llproto_names); i++) {
+                if (strcasecmp(llproto_names[i].name, buf) == 0) {
+                        i = llproto_names[i].id;
+                        goto good;
+                }
+       }
+       i = bb_strtou(buf, NULL, 0);
+       if (errno || i > 0xffff)
+               return -1;
+ good:
+       *id = htons(i);
+       return 0;
+}
+
diff --git a/networking/libiproute/ll_types.c b/networking/libiproute/ll_types.c
new file mode 100644 (file)
index 0000000..d5d2a1f
--- /dev/null
@@ -0,0 +1,205 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * ll_types.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, <kuznet@ms2.inr.ac.ru>
+ */
+#include <arpa/inet.h>
+#include <linux/if_arp.h>
+
+#include "libbb.h"
+#include "rt_names.h"
+
+const char *ll_type_n2a(int type, char *buf, int len)
+{
+       static const char arphrd_name[] =
+       /* 0,                  */ "generic" "\0"
+       /* ARPHRD_LOOPBACK,    */ "loopback" "\0"
+       /* ARPHRD_ETHER,       */ "ether" "\0"
+#ifdef ARPHRD_INFINIBAND
+       /* ARPHRD_INFINIBAND,  */ "infiniband" "\0"
+#endif
+#ifdef ARPHRD_IEEE802_TR
+       /* ARPHRD_IEEE802,     */ "ieee802" "\0"
+       /* ARPHRD_IEEE802_TR,  */ "tr" "\0"
+#else
+       /* ARPHRD_IEEE802,     */ "tr" "\0"
+#endif
+#ifdef ARPHRD_IEEE80211
+       /* ARPHRD_IEEE80211,   */ "ieee802.11" "\0"
+#endif
+#ifdef ARPHRD_IEEE1394
+       /* ARPHRD_IEEE1394,    */ "ieee1394" "\0"
+#endif
+       /* ARPHRD_IRDA,        */ "irda" "\0"
+       /* ARPHRD_SLIP,        */ "slip" "\0"
+       /* ARPHRD_CSLIP,       */ "cslip" "\0"
+       /* ARPHRD_SLIP6,       */ "slip6" "\0"
+       /* ARPHRD_CSLIP6,      */ "cslip6" "\0"
+       /* ARPHRD_PPP,         */ "ppp" "\0"
+       /* ARPHRD_TUNNEL,      */ "ipip" "\0"
+       /* ARPHRD_TUNNEL6,     */ "tunnel6" "\0"
+       /* ARPHRD_SIT,         */ "sit" "\0"
+       /* ARPHRD_IPGRE,       */ "gre" "\0"
+#ifdef ARPHRD_VOID
+       /* ARPHRD_VOID,        */ "void" "\0"
+#endif
+
+#if ENABLE_FEATURE_IP_RARE_PROTOCOLS
+       /* ARPHRD_EETHER,      */ "eether" "\0"
+       /* ARPHRD_AX25,        */ "ax25" "\0"
+       /* ARPHRD_PRONET,      */ "pronet" "\0"
+       /* ARPHRD_CHAOS,       */ "chaos" "\0"
+       /* ARPHRD_ARCNET,      */ "arcnet" "\0"
+       /* ARPHRD_APPLETLK,    */ "atalk" "\0"
+       /* ARPHRD_DLCI,        */ "dlci" "\0"
+#ifdef ARPHRD_ATM
+       /* ARPHRD_ATM,         */ "atm" "\0"
+#endif
+       /* ARPHRD_METRICOM,    */ "metricom" "\0"
+       /* ARPHRD_RSRVD,       */ "rsrvd" "\0"
+       /* ARPHRD_ADAPT,       */ "adapt" "\0"
+       /* ARPHRD_ROSE,        */ "rose" "\0"
+       /* ARPHRD_X25,         */ "x25" "\0"
+#ifdef ARPHRD_HWX25
+       /* ARPHRD_HWX25,       */ "hwx25" "\0"
+#endif
+       /* ARPHRD_HDLC,        */ "hdlc" "\0"
+       /* ARPHRD_LAPB,        */ "lapb" "\0"
+#ifdef ARPHRD_DDCMP
+       /* ARPHRD_DDCMP,       */ "ddcmp" "\0"
+       /* ARPHRD_RAWHDLC,     */ "rawhdlc" "\0"
+#endif
+       /* ARPHRD_FRAD,        */ "frad" "\0"
+       /* ARPHRD_SKIP,        */ "skip" "\0"
+       /* ARPHRD_LOCALTLK,    */ "ltalk" "\0"
+       /* ARPHRD_FDDI,        */ "fddi" "\0"
+       /* ARPHRD_BIF,         */ "bif" "\0"
+       /* ARPHRD_IPDDP,       */ "ip/ddp" "\0"
+       /* ARPHRD_PIMREG,      */ "pimreg" "\0"
+       /* ARPHRD_HIPPI,       */ "hippi" "\0"
+       /* ARPHRD_ASH,         */ "ash" "\0"
+       /* ARPHRD_ECONET,      */ "econet" "\0"
+       /* ARPHRD_FCPP,        */ "fcpp" "\0"
+       /* ARPHRD_FCAL,        */ "fcal" "\0"
+       /* ARPHRD_FCPL,        */ "fcpl" "\0"
+       /* ARPHRD_FCFABRIC,    */ "fcfb0" "\0"
+       /* ARPHRD_FCFABRIC+1,  */ "fcfb1" "\0"
+       /* ARPHRD_FCFABRIC+2,  */ "fcfb2" "\0"
+       /* ARPHRD_FCFABRIC+3,  */ "fcfb3" "\0"
+       /* ARPHRD_FCFABRIC+4,  */ "fcfb4" "\0"
+       /* ARPHRD_FCFABRIC+5,  */ "fcfb5" "\0"
+       /* ARPHRD_FCFABRIC+6,  */ "fcfb6" "\0"
+       /* ARPHRD_FCFABRIC+7,  */ "fcfb7" "\0"
+       /* ARPHRD_FCFABRIC+8,  */ "fcfb8" "\0"
+       /* ARPHRD_FCFABRIC+9,  */ "fcfb9" "\0"
+       /* ARPHRD_FCFABRIC+10, */ "fcfb10" "\0"
+       /* ARPHRD_FCFABRIC+11, */ "fcfb11" "\0"
+       /* ARPHRD_FCFABRIC+12, */ "fcfb12" "\0"
+#endif /* FEATURE_IP_RARE_PROTOCOLS */
+       ;
+
+       /* Keep these arrays in sync! */
+
+       static const uint16_t arphrd_type[] = {
+       0,                  /* "generic" "\0" */
+       ARPHRD_LOOPBACK,    /* "loopback" "\0" */
+       ARPHRD_ETHER,       /* "ether" "\0" */
+#ifdef ARPHRD_INFINIBAND
+       ARPHRD_INFINIBAND,  /* "infiniband" "\0" */
+#endif
+#ifdef ARPHRD_IEEE802_TR
+       ARPHRD_IEEE802,     /* "ieee802" "\0" */
+       ARPHRD_IEEE802_TR,  /* "tr" "\0" */
+#else
+       ARPHRD_IEEE802,     /* "tr" "\0" */
+#endif
+#ifdef ARPHRD_IEEE80211
+       ARPHRD_IEEE80211,   /* "ieee802.11" "\0" */
+#endif
+#ifdef ARPHRD_IEEE1394
+       ARPHRD_IEEE1394,    /* "ieee1394" "\0" */
+#endif
+       ARPHRD_IRDA,        /* "irda" "\0" */
+       ARPHRD_SLIP,        /* "slip" "\0" */
+       ARPHRD_CSLIP,       /* "cslip" "\0" */
+       ARPHRD_SLIP6,       /* "slip6" "\0" */
+       ARPHRD_CSLIP6,      /* "cslip6" "\0" */
+       ARPHRD_PPP,         /* "ppp" "\0" */
+       ARPHRD_TUNNEL,      /* "ipip" "\0" */
+       ARPHRD_TUNNEL6,     /* "tunnel6" "\0" */
+       ARPHRD_SIT,         /* "sit" "\0" */
+       ARPHRD_IPGRE,       /* "gre" "\0" */
+#ifdef ARPHRD_VOID
+       ARPHRD_VOID,        /* "void" "\0" */
+#endif
+
+#if ENABLE_FEATURE_IP_RARE_PROTOCOLS
+       ARPHRD_EETHER,      /* "eether" "\0" */
+       ARPHRD_AX25,        /* "ax25" "\0" */
+       ARPHRD_PRONET,      /* "pronet" "\0" */
+       ARPHRD_CHAOS,       /* "chaos" "\0" */
+       ARPHRD_ARCNET,      /* "arcnet" "\0" */
+       ARPHRD_APPLETLK,    /* "atalk" "\0" */
+       ARPHRD_DLCI,        /* "dlci" "\0" */
+#ifdef ARPHRD_ATM
+       ARPHRD_ATM,         /* "atm" "\0" */
+#endif
+       ARPHRD_METRICOM,    /* "metricom" "\0" */
+       ARPHRD_RSRVD,       /* "rsrvd" "\0" */
+       ARPHRD_ADAPT,       /* "adapt" "\0" */
+       ARPHRD_ROSE,        /* "rose" "\0" */
+       ARPHRD_X25,         /* "x25" "\0" */
+#ifdef ARPHRD_HWX25
+       ARPHRD_HWX25,       /* "hwx25" "\0" */
+#endif
+       ARPHRD_HDLC,        /* "hdlc" "\0" */
+       ARPHRD_LAPB,        /* "lapb" "\0" */
+#ifdef ARPHRD_DDCMP
+       ARPHRD_DDCMP,       /* "ddcmp" "\0" */
+       ARPHRD_RAWHDLC,     /* "rawhdlc" "\0" */
+#endif
+       ARPHRD_FRAD,        /* "frad" "\0" */
+       ARPHRD_SKIP,        /* "skip" "\0" */
+       ARPHRD_LOCALTLK,    /* "ltalk" "\0" */
+       ARPHRD_FDDI,        /* "fddi" "\0" */
+       ARPHRD_BIF,         /* "bif" "\0" */
+       ARPHRD_IPDDP,       /* "ip/ddp" "\0" */
+       ARPHRD_PIMREG,      /* "pimreg" "\0" */
+       ARPHRD_HIPPI,       /* "hippi" "\0" */
+       ARPHRD_ASH,         /* "ash" "\0" */
+       ARPHRD_ECONET,      /* "econet" "\0" */
+       ARPHRD_FCPP,        /* "fcpp" "\0" */
+       ARPHRD_FCAL,        /* "fcal" "\0" */
+       ARPHRD_FCPL,        /* "fcpl" "\0" */
+       ARPHRD_FCFABRIC,    /* "fcfb0" "\0" */
+       ARPHRD_FCFABRIC+1,  /* "fcfb1" "\0" */
+       ARPHRD_FCFABRIC+2,  /* "fcfb2" "\0" */
+       ARPHRD_FCFABRIC+3,  /* "fcfb3" "\0" */
+       ARPHRD_FCFABRIC+4,  /* "fcfb4" "\0" */
+       ARPHRD_FCFABRIC+5,  /* "fcfb5" "\0" */
+       ARPHRD_FCFABRIC+6,  /* "fcfb6" "\0" */
+       ARPHRD_FCFABRIC+7,  /* "fcfb7" "\0" */
+       ARPHRD_FCFABRIC+8,  /* "fcfb8" "\0" */
+       ARPHRD_FCFABRIC+9,  /* "fcfb9" "\0" */
+       ARPHRD_FCFABRIC+10, /* "fcfb10" "\0" */
+       ARPHRD_FCFABRIC+11, /* "fcfb11" "\0" */
+       ARPHRD_FCFABRIC+12, /* "fcfb12" "\0" */
+#endif /* FEATURE_IP_RARE_PROTOCOLS */
+       };
+
+       unsigned i;
+       const char *aname = arphrd_name;
+       for (i = 0; i < ARRAY_SIZE(arphrd_type); i++) {
+               if (arphrd_type[i] == type)
+                       return aname;
+               aname += strlen(aname) + 1;
+       }
+       snprintf(buf, len, "[%d]", type);
+       return buf;
+}
diff --git a/networking/libiproute/rt_names.c b/networking/libiproute/rt_names.c
new file mode 100644 (file)
index 0000000..e4d1061
--- /dev/null
@@ -0,0 +1,349 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * rt_names.c          rtnetlink names DB.
+ *
+ *             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, <kuznet@ms2.inr.ac.ru>
+ */
+
+#include "libbb.h"
+#include "rt_names.h"
+
+/* so far all callers have size == 256 */
+#define rtnl_tab_initialize(file, tab, size) rtnl_tab_initialize(file, tab)
+#define size 256
+static void rtnl_tab_initialize(const char *file, const char **tab, int size)
+{
+       char *token[2];
+       parser_t *parser = config_open2(file, fopen_for_read);
+       while (config_read(parser, token, 2, 2, "# \t", PARSE_NORMAL)) {
+               int id = bb_strtou(token[0], NULL, 0);
+               if (id < 0 || id > size) {
+                       bb_error_msg("database %s is corrupted at line %d",
+                               file, parser->lineno);
+                       break;
+               }
+               tab[id] = xstrdup(token[1]);
+       }
+       config_close(parser);
+}
+#undef size
+
+static const char **rtnl_rtprot_tab; /* [256] */
+
+static void rtnl_rtprot_initialize(void)
+{
+       static const char *const init_tab[] = {
+               "none",
+               "redirect",
+               "kernel",
+               "boot",
+               "static",
+               NULL,
+               NULL,
+               NULL,
+               "gated",
+               "ra",
+               "mrt",
+               "zebra",
+               "bird",
+       };
+       if (rtnl_rtprot_tab) return;
+       rtnl_rtprot_tab = xzalloc(256 * sizeof(rtnl_rtprot_tab[0]));
+       memcpy(rtnl_rtprot_tab, init_tab, sizeof(init_tab));
+       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;
+       }
+
+       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;
+       int i;
+
+       if (cache && strcmp(cache, arg) == 0) {
+               *id = res;
+               return 0;
+       }
+
+       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 = bb_strtoul(arg, NULL, 0);
+       if (errno || res > 255)
+               return -1;
+       *id = res;
+       return 0;
+}
+
+
+static const char **rtnl_rtscope_tab; /* [256] */
+
+static void rtnl_rtscope_initialize(void)
+{
+       if (rtnl_rtscope_tab) return;
+       rtnl_rtscope_tab = xzalloc(256 * sizeof(rtnl_rtscope_tab[0]));
+       rtnl_rtscope_tab[0] = "global";
+       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;
+       }
+
+       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;
+       int i;
+
+       if (cache && strcmp(cache, arg) == 0) {
+               *id = res;
+               return 0;
+       }
+
+       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 = bb_strtoul(arg, NULL, 0);
+       if (errno || res > 255)
+               return -1;
+       *id = res;
+       return 0;
+}
+
+
+static const char **rtnl_rtrealm_tab; /* [256] */
+
+static void rtnl_rtrealm_initialize(void)
+{
+       if (rtnl_rtrealm_tab) return;
+       rtnl_rtrealm_tab = xzalloc(256 * sizeof(rtnl_rtrealm_tab[0]));
+       rtnl_rtrealm_tab[0] = "unknown";
+       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;
+       int i;
+
+       if (cache && strcmp(cache, arg) == 0) {
+               *id = res;
+               return 0;
+       }
+
+       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 = bb_strtoul(arg, NULL, 0);
+       if (errno || 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;
+       }
+
+       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] */
+
+static void rtnl_rtdsfield_initialize(void)
+{
+       if (rtnl_rtdsfield_tab) return;
+       rtnl_rtdsfield_tab = xzalloc(256 * sizeof(rtnl_rtdsfield_tab[0]));
+       rtnl_rtdsfield_tab[0] = "0";
+       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;
+       }
+
+       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;
+       int i;
+
+       if (cache && strcmp(cache, arg) == 0) {
+               *id = res;
+               return 0;
+       }
+
+       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 = bb_strtoul(arg, NULL, 16);
+       if (errno || res > 255)
+               return -1;
+       *id = res;
+       return 0;
+}
+
+
+#if ENABLE_FEATURE_IP_RULE
+static const char **rtnl_rttable_tab; /* [256] */
+
+static void rtnl_rttable_initialize(void)
+{
+       if (rtnl_rtdsfield_tab) return;
+       rtnl_rttable_tab = xzalloc(256 * sizeof(rtnl_rttable_tab[0]));
+       rtnl_rttable_tab[0] = "unspec";
+       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;
+       }
+
+       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;
+       int i;
+
+       if (cache && strcmp(cache, arg) == 0) {
+               *id = res;
+               return 0;
+       }
+
+       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 = bb_strtoul(arg, NULL, 0);
+       if (errno || 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 (file)
index 0000000..a2d4fd1
--- /dev/null
@@ -0,0 +1,29 @@
+/* vi: set sw=4 ts=4: */
+#ifndef RT_NAMES_H
+#define RT_NAMES_H 1
+
+PUSH_AND_SET_FUNCTION_VISIBILITY_TO_HIDDEN
+
+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);
+
+POP_SAVED_FUNCTION_VISIBILITY
+
+#endif
diff --git a/networking/libiproute/rtm_map.c b/networking/libiproute/rtm_map.c
new file mode 100644 (file)
index 0000000..ca2f443
--- /dev/null
@@ -0,0 +1,118 @@
+/* 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, <kuznet@ms2.inr.ac.ru>
+ *
+ */
+
+#include "libbb.h"
+#include "rt_names.h"
+#include "utils.h"
+
+const 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)
+{
+       static const char keywords[] ALIGN1 =
+               "local\0""nat\0""broadcast\0""brd\0""anycast\0"
+               "multicast\0""prohibit\0""unreachable\0""blackhole\0"
+               "xresolve\0""unicast\0""throw\0";
+       enum {
+               ARG_local = 1, ARG_nat, ARG_broadcast, ARG_brd, ARG_anycast,
+               ARG_multicast, ARG_prohibit, ARG_unreachable, ARG_blackhole,
+               ARG_xresolve, ARG_unicast, ARG_throw
+       };
+       const smalluint key = index_in_substrings(keywords, arg) + 1;
+       char *end;
+       unsigned long res;
+
+       if (key == ARG_local)
+               res = RTN_LOCAL;
+       else if (key == ARG_nat)
+               res = RTN_NAT;
+       else if (key == ARG_broadcast || key == ARG_brd)
+               res = RTN_BROADCAST;
+       else if (key == ARG_anycast)
+               res = RTN_ANYCAST;
+       else if (key == ARG_multicast)
+               res = RTN_MULTICAST;
+       else if (key == ARG_prohibit)
+               res = RTN_PROHIBIT;
+       else if (key == ARG_unreachable)
+               res = RTN_UNREACHABLE;
+       else if (key == ARG_blackhole)
+               res = RTN_BLACKHOLE;
+       else if (key == ARG_xresolve)
+               res = RTN_XRESOLVE;
+       else if (key == ARG_unicast)
+               res = RTN_UNICAST;
+       else if (key == ARG_throw)
+               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(uint32_t *realms, char *arg)
+{
+       uint32_t 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 (file)
index 0000000..ab1b70e
--- /dev/null
@@ -0,0 +1,14 @@
+/* vi: set sw=4 ts=4: */
+#ifndef RTM_MAP_H
+#define RTM_MAP_H 1
+
+PUSH_AND_SET_FUNCTION_VISIBILITY_TO_HIDDEN
+
+const char *rtnl_rtntype_n2a(int id, char *buf, int len);
+int rtnl_rtntype_a2n(int *id, char *arg);
+
+int get_rt_realms(uint32_t *realms, char *arg);
+
+POP_SAVED_FUNCTION_VISIBILITY
+
+#endif
diff --git a/networking/libiproute/utils.c b/networking/libiproute/utils.c
new file mode 100644 (file)
index 0000000..c84d018
--- /dev/null
@@ -0,0 +1,273 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * utils.c
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ *
+ * Authors:    Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ *
+ * Changes:
+ *
+ * Rani Assaf <rani@magic.metawire.com> 980929:        resolve addresses
+ */
+
+#include "libbb.h"
+#include "utils.h"
+#include "inet_common.h"
+
+unsigned get_unsigned(char *arg, const char *errmsg)
+{
+       unsigned long res;
+       char *ptr;
+
+       if (*arg) {
+               res = strtoul(arg, &ptr, 0);
+               if (!*ptr && res <= UINT_MAX) {
+                       return res;
+               }
+       }
+       invarg(arg, errmsg); /* does not return */
+}
+
+uint32_t get_u32(char *arg, const char *errmsg)
+{
+       unsigned long res;
+       char *ptr;
+
+       if (*arg) {
+               res = strtoul(arg, &ptr, 0);
+               if (!*ptr && res <= 0xFFFFFFFFUL) {
+                       return res;
+               }
+       }
+       invarg(arg, errmsg); /* does not return */
+}
+
+uint16_t get_u16(char *arg, const char *errmsg)
+{
+       unsigned long res;
+       char *ptr;
+
+       if (*arg) {
+               res = strtoul(arg, &ptr, 0);
+               if (!*ptr && res <= 0xFFFF) {
+                       return res;
+               }
+       }
+       invarg(arg, errmsg); /* does not return */
+}
+
+int get_addr_1(inet_prefix *addr, char *name, int family)
+{
+       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;
+       if (inet_pton(AF_INET, name, addr->data) <= 0)
+               return -1;
+       addr->bytelen = 4;
+       addr->bitlen = -1;
+       return 0;
+}
+
+static int get_prefix_1(inet_prefix *dst, char *arg, int family)
+{
+       int err;
+       unsigned plen;
+       char *slash;
+
+       memset(dst, 0, sizeof(*dst));
+
+       if (strcmp(arg, bb_str_default) == 0
+        || strcmp(arg, "all") == 0
+        || strcmp(arg, "any") == 0
+       ) {
+               dst->family = family;
+               /*dst->bytelen = 0; - done by memset */
+               /*dst->bitlen = 0;*/
+               return 0;
+       }
+
+       slash = strchr(arg, '/');
+       if (slash)
+               *slash = '\0';
+       err = get_addr_1(dst, arg, family);
+       if (err == 0) {
+               dst->bitlen = (dst->family == AF_INET6) ? 128 : 32;
+               if (slash) {
+                       inet_prefix netmask_pfx;
+
+                       netmask_pfx.family = AF_UNSPEC;
+                       plen = bb_strtou(slash + 1, NULL, 0);
+                       if ((errno || plen > dst->bitlen)
+                        && (get_addr_1(&netmask_pfx, slash + 1, family)))
+                               err = -1;
+                       else if (netmask_pfx.family == AF_INET) {
+                               /* fill in prefix length of dotted quad */
+                               uint32_t mask = ntohl(netmask_pfx.data[0]);
+                               uint32_t host = ~mask;
+
+                               /* a valid netmask must be 2^n - 1 */
+                               if (!(host & (host + 1))) {
+                                       for (plen = 0; mask; mask <<= 1)
+                                               ++plen;
+                                       if (plen >= 0 && plen <= dst->bitlen) {
+                                               dst->bitlen = plen;
+                                               /* dst->flags |= PREFIXLEN_SPECIFIED; */
+                                       } else
+                                               err = -1;
+                               } else
+                                       err = -1;
+                       } else {
+                               /* plain prefix */
+                               dst->bitlen = plen;
+                       }
+               }
+       }
+       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 %s, but it is not allowed in this context", arg, "address");
+       }
+       if (get_addr_1(dst, arg, family)) {
+               bb_error_msg_and_die("an %s %s is expected rather than \"%s\"", "inet", "address", 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 %s, but it is not allowed in this context", arg, "prefix");
+       }
+       if (get_prefix_1(dst, arg, family)) {
+               bb_error_msg_and_die("an %s %s is expected rather than \"%s\"", "inet", "prefix", arg);
+       }
+       return 0;
+}
+
+uint32_t get_addr32(char *name)
+{
+       inet_prefix addr;
+
+       if (get_addr_1(&addr, name, AF_INET)) {
+               bb_error_msg_and_die("an %s %s is expected rather than \"%s\"", "IP", "address", name);
+       }
+       return addr.data[0];
+}
+
+void incomplete_command(void)
+{
+       bb_error_msg_and_die("command line is not complete, try option \"help\"");
+}
+
+void invarg(const char *arg, const char *opt)
+{
+       bb_error_msg_and_die(bb_msg_invalid_arg, arg, opt);
+}
+
+void duparg(const char *key, const char *arg)
+{
+       bb_error_msg_and_die("duplicate \"%s\": \"%s\" is the second value", key, arg);
+}
+
+void duparg2(const char *key, const char *arg)
+{
+       bb_error_msg_and_die("either \"%s\" is duplicate, or \"%s\" is garbage", key, arg);
+}
+
+int inet_addr_match(inet_prefix *a, inet_prefix *b, int bits)
+{
+       uint32_t *a1 = a->data;
+       uint32_t *a2 = b->data;
+       int words = bits >> 5;
+
+       bits &= 0x1f;
+
+       if (words)
+               if (memcmp(a1, a2, words << 2))
+                       return -1;
+
+       if (bits) {
+               uint32_t w1, w2;
+               uint32_t mask;
+
+               w1 = a1[words];
+               w2 = a2[words];
+
+               mask = htonl((0xffffffff) << (0x20 - bits));
+
+               if ((w1 ^ w2) & mask)
+                       return 1;
+       }
+
+       return 0;
+}
+
+const char *rt_addr_n2a(int af,
+               void *addr, char *buf, int buflen)
+{
+       switch (af) {
+       case AF_INET:
+       case AF_INET6:
+               return inet_ntop(af, addr, buf, buflen);
+       default:
+               return "???";
+       }
+}
+
+#ifdef RESOLVE_HOSTNAMES
+const char *format_host(int af, int len, void *addr, char *buf, int buflen)
+{
+       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);
+                       if (h_ent != NULL) {
+                               safe_strncpy(buf, h_ent->h_name, buflen);
+                               return buf;
+                       }
+               }
+       }
+       return rt_addr_n2a(af, addr, buf, buflen);
+}
+#endif
diff --git a/networking/libiproute/utils.h b/networking/libiproute/utils.h
new file mode 100644 (file)
index 0000000..ed03e78
--- /dev/null
@@ -0,0 +1,90 @@
+/* vi: set sw=4 ts=4: */
+#ifndef UTILS_H
+#define UTILS_H 1
+
+#include "libnetlink.h"
+#include "ll_map.h"
+#include "rtm_map.h"
+
+PUSH_AND_SET_FUNCTION_VISIBILITY_TO_HIDDEN
+
+extern family_t preferred_family;
+extern smallint show_stats;    /* UNUSED */
+extern smallint show_details;  /* UNUSED */
+extern smallint show_raw;      /* UNUSED */
+extern smallint resolve_hosts; /* UNUSED */
+extern smallint 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) NORETURN;
+
+#define NEXT_ARG() do { if (!*++argv) incomplete_command(); } while (0)
+
+typedef struct {
+       uint8_t family;
+       uint8_t bytelen;
+       int16_t bitlen;
+       uint32_t data[4];
+} inet_prefix;
+
+#define PREFIXLEN_SPECIFIED 1
+
+#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 uint32_t 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 unsigned get_unsigned(char *arg, const char *errmsg);
+extern uint32_t get_u32(char *arg, const char *errmsg);
+extern uint16_t get_u16(char *arg, const char *errmsg);
+
+extern const char *rt_addr_n2a(int af, void *addr, char *buf, int buflen);
+#ifdef RESOLVE_HOSTNAMES
+extern const char *format_host(int af, int len, void *addr, char *buf, int buflen);
+#else
+#define format_host(af, len, addr, buf, buflen) \
+       rt_addr_n2a(af, addr, buf, buflen)
+#endif
+
+void invarg(const char *, const char *) NORETURN;
+void duparg(const char *, const char *) NORETURN;
+void duparg2(const char *, const char *) NORETURN;
+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);
+
+POP_SAVED_FUNCTION_VISIBILITY
+
+#endif
diff --git a/networking/nameif.c b/networking/nameif.c
new file mode 100644 (file)
index 0000000..fb31fbf
--- /dev/null
@@ -0,0 +1,234 @@
+/* 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 <nick@fedchik.org.ua>
+ *                     Glenn McGrath
+ * Extended matching support 2008 by Nico Erfurth <masta@perlgolf.de>
+ *
+ * Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
+ */
+
+#include "libbb.h"
+#include <syslog.h>
+#include <net/if.h>
+#include <netinet/ether.h>
+#include <linux/sockios.h>
+
+#ifndef IFNAMSIZ
+#define IFNAMSIZ 16
+#endif
+
+/* Taken from linux/sockios.h */
+#define SIOCSIFNAME    0x8923  /* set interface name */
+
+/* Octets in one Ethernet addr, from <linux/if_ether.h> */
+#define ETH_ALEN       6
+
+#ifndef ifr_newname
+#define ifr_newname ifr_ifru.ifru_slave
+#endif
+
+typedef struct ethtable_s {
+       struct ethtable_s *next;
+       struct ethtable_s *prev;
+       char *ifname;
+       struct ether_addr *mac;
+#if ENABLE_FEATURE_NAMEIF_EXTENDED
+       char *bus_info;
+       char *driver;
+#endif
+} ethtable_t;
+
+#if ENABLE_FEATURE_NAMEIF_EXTENDED
+/* Cut'n'paste from ethtool.h */
+#define ETHTOOL_BUSINFO_LEN 32
+/* these strings are set to whatever the driver author decides... */
+struct ethtool_drvinfo {
+       uint32_t cmd;
+       char  driver[32]; /* driver short name, "tulip", "eepro100" */
+       char  version[32];  /* driver version string */
+       char  fw_version[32]; /* firmware version string, if applicable */
+       char  bus_info[ETHTOOL_BUSINFO_LEN];  /* Bus info for this IF. */
+       /* For PCI devices, use pci_dev->slot_name. */
+       char  reserved1[32];
+       char  reserved2[16];
+       uint32_t n_stats;  /* number of u64's from ETHTOOL_GSTATS */
+       uint32_t testinfo_len;
+       uint32_t eedump_len; /* Size of data from ETHTOOL_GEEPROM (bytes) */
+       uint32_t regdump_len;  /* Size of data from ETHTOOL_GREGS (bytes) */
+};
+#define ETHTOOL_GDRVINFO  0x00000003 /* Get driver info. */
+#endif
+
+
+static void nameif_parse_selector(ethtable_t *ch, char *selector)
+{
+       struct ether_addr *lmac;
+#if ENABLE_FEATURE_NAMEIF_EXTENDED
+       int found_selector = 0;
+
+       while (*selector) {
+               char *next;
+#endif
+               selector = skip_whitespace(selector);
+#if ENABLE_FEATURE_NAMEIF_EXTENDED
+               if (*selector == '\0')
+                       break;
+               /* Search for the end .... */
+               next = skip_non_whitespace(selector);
+               if (*next)
+                       *next++ = '\0';
+               /* Check for selectors, mac= is assumed */
+               if (strncmp(selector, "bus=", 4) == 0) {
+                       ch->bus_info = xstrdup(selector + 4);
+                       found_selector++;
+               } else if (strncmp(selector, "driver=", 7) == 0) {
+                       ch->driver = xstrdup(selector + 7);
+                       found_selector++;
+               } else {
+#endif
+                       lmac = xmalloc(ETH_ALEN);
+                       ch->mac = ether_aton_r(selector + (strncmp(selector, "mac=", 4) ? 0 : 4), lmac);
+                       if (ch->mac == NULL)
+                               bb_error_msg_and_die("cannot parse %s", selector);
+#if  ENABLE_FEATURE_NAMEIF_EXTENDED
+                       found_selector++;
+               };
+               selector = next;
+       }
+       if (found_selector == 0)
+               bb_error_msg_and_die("no selectors found for %s", ch->ifname);
+#endif
+}
+
+static void prepend_new_eth_table(ethtable_t **clist, char *ifname, char *selector)
+{
+       ethtable_t *ch;
+       if (strlen(ifname) >= IFNAMSIZ)
+               bb_error_msg_and_die("interface name '%s' too long", ifname);
+       ch = xzalloc(sizeof(*ch));
+       ch->ifname = xstrdup(ifname);
+       nameif_parse_selector(ch, selector);
+       ch->next = *clist;
+       if (*clist)
+               (*clist)->prev = ch;
+       *clist = ch;
+}
+
+#if ENABLE_FEATURE_CLEAN_UP
+static void delete_eth_table(ethtable_t *ch)
+{
+       free(ch->ifname);
+#if ENABLE_FEATURE_NAMEIF_EXTENDED
+       free(ch->bus_info);
+       free(ch->driver);
+#endif
+       free(ch->mac);
+       free(ch);
+};
+#else
+void delete_eth_table(ethtable_t *ch);
+#endif
+
+int nameif_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int nameif_main(int argc, char **argv)
+{
+       ethtable_t *clist = NULL;
+       const char *fname = "/etc/mactab";
+       int ctl_sk;
+       ethtable_t *ch;
+       parser_t *parser;
+       char *token[2];
+
+       if (1 & getopt32(argv, "sc:", &fname)) {
+               openlog(applet_name, 0, LOG_LOCAL0);
+               /* Why not just "="? I assume logging to stderr
+                * can't hurt. 2>/dev/null if you don't like it: */
+               logmode |= LOGMODE_SYSLOG;
+       }
+       argc -= optind;
+       argv += optind;
+
+       if (argc & 1)
+               bb_show_usage();
+
+       if (argc) {
+               while (*argv) {
+                       char *ifname = xstrdup(*argv++);
+                       prepend_new_eth_table(&clist, ifname, *argv++);
+               }
+       } else {
+               parser = config_open(fname);
+               while (config_read(parser, token, 2, 2, "# \t", PARSE_NORMAL))
+                       prepend_new_eth_table(&clist, token[0], token[1]);
+               config_close(parser);
+       }
+
+       ctl_sk = xsocket(PF_INET, SOCK_DGRAM, 0);
+       parser = config_open2("/proc/net/dev", xfopen_for_read);
+
+       while (clist && config_read(parser, token, 2, 2, "\0: \t", PARSE_NORMAL)) {
+               struct ifreq ifr;
+#if  ENABLE_FEATURE_NAMEIF_EXTENDED
+               struct ethtool_drvinfo drvinfo;
+#endif
+               if (parser->lineno < 2)
+                       continue; /* Skip the first two lines */
+
+               /* Find the current interface name and copy it to ifr.ifr_name */
+               memset(&ifr, 0, sizeof(struct ifreq));
+               strncpy_IFNAMSIZ(ifr.ifr_name, token[0]);
+
+#if ENABLE_FEATURE_NAMEIF_EXTENDED
+               /* Check for driver etc. */
+               memset(&drvinfo, 0, sizeof(struct ethtool_drvinfo));
+               drvinfo.cmd = ETHTOOL_GDRVINFO;
+               ifr.ifr_data = (caddr_t) &drvinfo;
+               /* Get driver and businfo first, so we have it in drvinfo */
+               ioctl(ctl_sk, SIOCETHTOOL, &ifr);
+#endif
+               ioctl(ctl_sk, SIOCGIFHWADDR, &ifr);
+
+               /* Search the list for a matching device */
+               for (ch = clist; ch; ch = ch->next) {
+#if ENABLE_FEATURE_NAMEIF_EXTENDED
+                       if (ch->bus_info && strcmp(ch->bus_info, drvinfo.bus_info) != 0)
+                               continue;
+                       if (ch->driver && strcmp(ch->driver, drvinfo.driver) != 0)
+                               continue;
+#endif
+                       if (ch->mac && memcmp(ch->mac, ifr.ifr_hwaddr.sa_data, ETH_ALEN) != 0)
+                               continue;
+                       /* if we came here, all selectors have matched */
+                       break;
+               }
+               /* Nothing found for current interface */
+               if (!ch)
+                       continue;
+
+               if (strcmp(ifr.ifr_name, ch->ifname) != 0) {
+                       strcpy(ifr.ifr_newname, ch->ifname);
+                       ioctl_or_perror_and_die(ctl_sk, SIOCSIFNAME, &ifr,
+                                       "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)
+                       delete_eth_table(ch);
+       }
+       if (ENABLE_FEATURE_CLEAN_UP) {
+               for (ch = clist; ch; ch = ch->next)
+                       delete_eth_table(ch);
+               config_close(parser);
+       };
+
+       return 0;
+}
diff --git a/networking/nc.c b/networking/nc.c
new file mode 100644 (file)
index 0000000..fe845f5
--- /dev/null
@@ -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 "libbb.h"
+
+#if ENABLE_DESKTOP
+#include "nc_bloaty.c"
+#else
+
+/* Lots of small differences in features
+ * when compared to "standard" nc
+ */
+
+static void timeout(int signum UNUSED_PARAM)
+{
+       bb_error_msg_and_die("timed out");
+}
+
+int nc_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int nc_main(int argc, char **argv)
+{
+       /* sfd sits _here_ only because of "repeat" option (-l -l). */
+       int sfd = sfd; /* for gcc */
+       int cfd = 0;
+       unsigned lport = 0;
+       SKIP_NC_SERVER(const) unsigned do_listen = 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;)
+       len_and_sockaddr *lsa;
+       fd_set readfds, testfds;
+       int opt; /* must be signed (getopt returns -1) */
+
+       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
+               if (do_listen || cfd) {
+                       if (argc) bb_show_usage();
+               } else {
+                       if (!argc || argc > 2) bb_show_usage();
+               }
+       } else {
+               if (argc != 3) bb_show_usage();
+               argc--;
+               argv++;
+       }
+
+       if (wsecs) {
+               signal(SIGALRM, timeout);
+               alarm(wsecs);
+       }
+
+       if (!cfd) {
+               if (do_listen) {
+                       /* create_and_bind_stream_or_die(NULL, lport)
+                        * would've work wonderfully, but we need
+                        * to know lsa */
+                       sfd = xsocket_stream(&lsa);
+                       if (lport)
+                               set_nport(lsa, htons(lport));
+                       setsockopt_reuseaddr(sfd);
+                       xbind(sfd, &lsa->u.sa, lsa->len);
+                       xlisten(sfd, do_listen); /* can be > 1 */
+                       /* If we didn't specify a port number,
+                        * query and print it after listen() */
+                       if (!lport) {
+                               socklen_t addrlen = lsa->len;
+                               getsockname(sfd, &lsa->u.sa, &addrlen);
+                               lport = get_nport(&lsa->u.sa);
+                               fdprintf(2, "%d\n", ntohs(lport));
+                       }
+                       close_on_exec_on(sfd);
+ accept_again:
+                       cfd = accept(sfd, NULL, 0);
+                       if (cfd < 0)
+                               bb_perror_msg_and_die("accept");
+                       if (!execparam)
+                               close(sfd);
+               } else {
+                       cfd = create_and_connect_stream_or_die(argv[0],
+                               argv[1] ? bb_lookup_port(argv[1], "tcp", 0) : 0);
+               }
+       }
+
+       if (wsecs) {
+               alarm(0);
+               /* Non-ignored siganls revert to SIG_DFL on exec anyway */
+               /*signal(SIGALRM, SIG_DFL);*/
+       }
+
+       /* -e given? */
+       if (execparam) {
+               signal(SIGCHLD, SIG_IGN);
+               // With more than one -l, repeatedly act as server.
+               if (do_listen > 1 && vfork()) {
+                       /* parent */
+                       // 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 accept_again;
+               }
+               /* child (or main thread if no multiple -l) */
+               xmove_fd(cfd, 0);
+               xdup2(0, 1);
+               xdup2(0, 2);
+               USE_NC_EXTRA(BB_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");
+
+#define iobuf bb_common_bufsiz1
+               for (fd = 0; fd < FD_SETSIZE; fd++) {
+                       if (FD_ISSET(fd, &testfds)) {
+                               nread = safe_read(fd, iobuf, sizeof(iobuf));
+                               if (fd == cfd) {
+                                       if (nread < 1)
+                                               exit(EXIT_SUCCESS);
+                                       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, iobuf, nread);
+                               if (delay > 0) sleep(delay);
+                       }
+               }
+       }
+}
+#endif
diff --git a/networking/nc_bloaty.c b/networking/nc_bloaty.c
new file mode 100644 (file)
index 0000000..41db945
--- /dev/null
@@ -0,0 +1,832 @@
+/* Based on netcat 1.10 RELEASE 960320 written by hobbit@avian.org.
+ * Released into public domain by the author.
+ *
+ * Copyright (C) 2007 Denys Vlasenko.
+ *
+ * Licensed under GPLv2, see file LICENSE in this tarball for details.
+ */
+
+/* Author's comments from nc 1.10:
+ * =====================
+ * Netcat is entirely my own creation, although plenty of other code was used as
+ * examples.  It is freely given away to the Internet community in the hope that
+ * it will be useful, with no restrictions except giving credit where it is due.
+ * No GPLs, Berkeley copyrights or any of that nonsense.  The author assumes NO
+ * responsibility for how anyone uses it.  If netcat makes you rich somehow and
+ * you're feeling generous, mail me a check.  If you are affiliated in any way
+ * with Microsoft Network, get a life.  Always ski in control.  Comments,
+ * questions, and patches to hobbit@avian.org.
+ * ...
+ * Netcat and the associated package is a product of Avian Research, and is freely
+ * available in full source form with no restrictions save an obligation to give
+ * credit where due.
+ * ...
+ * A damn useful little "backend" utility begun 950915 or thereabouts,
+ * as *Hobbit*'s first real stab at some sockets programming.  Something that
+ * should have and indeed may have existed ten years ago, but never became a
+ * standard Unix utility.  IMHO, "nc" could take its place right next to cat,
+ * cp, rm, mv, dd, ls, and all those other cryptic and Unix-like things.
+ * =====================
+ *
+ * Much of author's comments are still retained in the code.
+ *
+ * Functionality removed (rationale):
+ * - miltiple-port ranges, randomized port scanning (use nmap)
+ * - telnet support (use telnet)
+ * - source routing
+ * - multiple DNS checks
+ * Functionalty which is different from nc 1.10:
+ * - Prog in '-e prog' can have prog's parameters and options.
+ *   Because of this -e option must be last.
+ * - nc doesn't redirect stderr to the network socket for the -e prog.
+ * - numeric addresses are printed in (), not [] (IPv6 looks better),
+ *   port numbers are inside (): (1.2.3.4:5678)
+ * - network read errors are reported on verbose levels > 1
+ *   (nc 1.10 treats them as EOF)
+ * - TCP connects from wrong ip/ports (if peer ip:port is specified
+ *   on the command line, but accept() says that it came from different addr)
+ *   are closed, but nc doesn't exit - continues to listen/accept.
+ */
+
+/* done in nc.c: #include "libbb.h" */
+
+enum {
+       SLEAZE_PORT = 31337,               /* for UDP-scan RTT trick, change if ya want */
+       BIGSIZ = 8192,                     /* big buffers */
+
+       netfd = 3,
+       ofd = 4,
+};
+
+struct globals {
+       /* global cmd flags: */
+       unsigned o_verbose;
+       unsigned o_wait;
+#if ENABLE_NC_EXTRA
+       unsigned o_interval;
+#endif
+
+       /*int netfd;*/
+       /*int ofd;*/                     /* hexdump output fd */
+#if ENABLE_LFS
+#define SENT_N_RECV_M "sent %llu, rcvd %llu\n"
+       unsigned long long wrote_out;          /* total stdout bytes */
+       unsigned long long wrote_net;          /* total net bytes */
+#else
+#define SENT_N_RECV_M "sent %u, rcvd %u\n"
+       unsigned wrote_out;          /* total stdout bytes */
+       unsigned wrote_net;          /* total net bytes */
+#endif
+       /* ouraddr is never NULL and goes through three states as we progress:
+        1 - local address before bind (IP/port possibly zero)
+        2 - local address after bind (port is nonzero)
+        3 - local address after connect??/recv/accept (IP and port are nonzero) */
+       struct len_and_sockaddr *ouraddr;
+       /* themaddr is NULL if no peer hostname[:port] specified on command line */
+       struct len_and_sockaddr *themaddr;
+       /* remend is set after connect/recv/accept to the actual ip:port of peer */
+       struct len_and_sockaddr remend;
+
+       jmp_buf jbuf;                /* timer crud */
+
+       /* will malloc up the following globals: */
+       fd_set ding1;                /* for select loop */
+       fd_set ding2;
+       char bigbuf_in[BIGSIZ];      /* data buffers */
+       char bigbuf_net[BIGSIZ];
+};
+
+#define G (*ptr_to_globals)
+#define wrote_out  (G.wrote_out )
+#define wrote_net  (G.wrote_net )
+#define ouraddr    (G.ouraddr   )
+#define themaddr   (G.themaddr  )
+#define remend     (G.remend    )
+#define jbuf       (G.jbuf      )
+#define ding1      (G.ding1     )
+#define ding2      (G.ding2     )
+#define bigbuf_in  (G.bigbuf_in )
+#define bigbuf_net (G.bigbuf_net)
+#define o_verbose  (G.o_verbose )
+#define o_wait     (G.o_wait    )
+#if ENABLE_NC_EXTRA
+#define o_interval (G.o_interval)
+#else
+#define o_interval 0
+#endif
+#define INIT_G() do { \
+       SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
+} while (0)
+
+
+/* Must match getopt32 call! */
+enum {
+       OPT_h = (1 << 0),
+       OPT_n = (1 << 1),
+       OPT_p = (1 << 2),
+       OPT_s = (1 << 3),
+       OPT_u = (1 << 4),
+       OPT_v = (1 << 5),
+       OPT_w = (1 << 6),
+       OPT_l = (1 << 7) * ENABLE_NC_SERVER,
+       OPT_i = (1 << (7+ENABLE_NC_SERVER)) * ENABLE_NC_EXTRA,
+       OPT_o = (1 << (8+ENABLE_NC_SERVER)) * ENABLE_NC_EXTRA,
+       OPT_z = (1 << (9+ENABLE_NC_SERVER)) * ENABLE_NC_EXTRA,
+};
+
+#define o_nflag   (option_mask32 & OPT_n)
+#define o_udpmode (option_mask32 & OPT_u)
+#if ENABLE_NC_SERVER
+#define o_listen  (option_mask32 & OPT_l)
+#else
+#define o_listen  0
+#endif
+#if ENABLE_NC_EXTRA
+#define o_ofile   (option_mask32 & OPT_o)
+#define o_zero    (option_mask32 & OPT_z)
+#else
+#define o_ofile   0
+#define o_zero    0
+#endif
+
+/* Debug: squirt whatever message and sleep a bit so we can see it go by. */
+/* Beware: writes to stdOUT... */
+#if 0
+#define Debug(...) do { printf(__VA_ARGS__); printf("\n"); fflush(stdout); sleep(1); } while (0)
+#else
+#define Debug(...) do { } while (0)
+#endif
+
+#define holler_error(...)  do { if (o_verbose) bb_error_msg(__VA_ARGS__); } while (0)
+#define holler_perror(...) do { if (o_verbose) bb_perror_msg(__VA_ARGS__); } while (0)
+
+/* catch: no-brainer interrupt handler */
+static void catch(int sig)
+{
+       if (o_verbose > 1)                /* normally we don't care */
+               fprintf(stderr, SENT_N_RECV_M, wrote_net, wrote_out);
+       fprintf(stderr, "punt!\n");
+       kill_myself_with_sig(sig);
+}
+
+/* unarm  */
+static void unarm(void)
+{
+       signal(SIGALRM, SIG_IGN);
+       alarm(0);
+}
+
+/* timeout and other signal handling cruft */
+static void tmtravel(int sig UNUSED_PARAM)
+{
+       unarm();
+       longjmp(jbuf, 1);
+}
+
+/* arm: set the timer.  */
+static void arm(unsigned secs)
+{
+       signal(SIGALRM, tmtravel);
+       alarm(secs);
+}
+
+/* findline:
+ find the next newline in a buffer; return inclusive size of that "line",
+ or the entire buffer size, so the caller knows how much to then write().
+ Not distinguishing \n vs \r\n for the nonce; it just works as is... */
+static unsigned findline(char *buf, unsigned siz)
+{
+       char * p;
+       int x;
+       if (!buf)                        /* various sanity checks... */
+               return 0;
+       if (siz > BIGSIZ)
+               return 0;
+       x = siz;
+       for (p = buf; x > 0; x--) {
+               if (*p == '\n') {
+                       x = (int) (p - buf);
+                       x++;                        /* 'sokay if it points just past the end! */
+Debug("findline returning %d", x);
+                       return x;
+               }
+               p++;
+       } /* for */
+Debug("findline returning whole thing: %d", siz);
+       return siz;
+} /* findline */
+
+/* doexec:
+ fiddle all the file descriptors around, and hand off to another prog.  Sort
+ of like a one-off "poor man's inetd".  This is the only section of code
+ that would be security-critical, which is why it's ifdefed out by default.
+ Use at your own hairy risk; if you leave shells lying around behind open
+ listening ports you deserve to lose!! */
+static int doexec(char **proggie) NORETURN;
+static int doexec(char **proggie)
+{
+       xmove_fd(netfd, 0);
+       dup2(0, 1);
+       /* dup2(0, 2); - do we *really* want this? NO!
+        * exec'ed prog can do it yourself, if needed */
+       execvp(proggie[0], proggie);
+       bb_perror_msg_and_die("exec");
+}
+
+/* connect_w_timeout:
+ return an fd for one of
+ an open outbound TCP connection, a UDP stub-socket thingie, or
+ an unconnected TCP or UDP socket to listen on.
+ Examines various global o_blah flags to figure out what to do.
+ lad can be NULL, then socket is not bound to any local ip[:port] */
+static int connect_w_timeout(int fd)
+{
+       int rr;
+
+       /* wrap connect inside a timer, and hit it */
+       arm(o_wait);
+       if (setjmp(jbuf) == 0) {
+               rr = connect(fd, &themaddr->u.sa, themaddr->len);
+               unarm();
+       } else { /* setjmp: connect failed... */
+               rr = -1;
+               errno = ETIMEDOUT; /* fake it */
+       }
+       return rr;
+}
+
+/* dolisten:
+ listens for
+ incoming and returns an open connection *from* someplace.  If we were
+ given host/port args, any connections from elsewhere are rejected.  This
+ in conjunction with local-address binding should limit things nicely... */
+static void dolisten(void)
+{
+       int rr;
+
+       if (!o_udpmode)
+               xlisten(netfd, 1); /* TCP: gotta listen() before we can get */
+
+       /* Various things that follow temporarily trash bigbuf_net, which might contain
+        a copy of any recvfrom()ed packet, but we'll read() another copy later. */
+
+       /* I can't believe I have to do all this to get my own goddamn bound address
+        and port number.  It should just get filled in during bind() or something.
+        All this is only useful if we didn't say -p for listening, since if we
+        said -p we *know* what port we're listening on.  At any rate we won't bother
+        with it all unless we wanted to see it, although listening quietly on a
+        random unknown port is probably not very useful without "netstat". */
+       if (o_verbose) {
+               char *addr;
+               rr = getsockname(netfd, &ouraddr->u.sa, &ouraddr->len);
+               if (rr < 0)
+                       bb_perror_msg_and_die("getsockname after bind");
+               addr = xmalloc_sockaddr2dotted(&ouraddr->u.sa);
+               fprintf(stderr, "listening on %s ...\n", addr);
+               free(addr);
+       }
+
+       if (o_udpmode) {
+               /* UDP is a speeeeecial case -- we have to do I/O *and* get the calling
+                party's particulars all at once, listen() and accept() don't apply.
+                At least in the BSD universe, however, recvfrom/PEEK is enough to tell
+                us something came in, and we can set things up so straight read/write
+                actually does work after all.  Yow.  YMMV on strange platforms!  */
+
+               /* I'm not completely clear on how this works -- BSD seems to make UDP
+                just magically work in a connect()ed context, but we'll undoubtedly run
+                into systems this deal doesn't work on.  For now, we apparently have to
+                issue a connect() on our just-tickled socket so we can write() back.
+                Again, why the fuck doesn't it just get filled in and taken care of?!
+                This hack is anything but optimal.  Basically, if you want your listener
+                to also be able to send data back, you need this connect() line, which
+                also has the side effect that now anything from a different source or even a
+                different port on the other end won't show up and will cause ICMP errors.
+                I guess that's what they meant by "connect".
+                Let's try to remember what the "U" is *really* for, eh? */
+
+               /* If peer address is specified, connect to it */
+               remend.len = LSA_SIZEOF_SA;
+               if (themaddr) {
+                       remend = *themaddr;
+                       xconnect(netfd, &themaddr->u.sa, themaddr->len);
+               }
+               /* peek first packet and remember peer addr */
+               arm(o_wait);                /* might as well timeout this, too */
+               if (setjmp(jbuf) == 0) {       /* do timeout for initial connect */
+                       /* (*ouraddr) is prefilled with "default" address */
+                       /* and here we block... */
+                       rr = recv_from_to(netfd, NULL, 0, MSG_PEEK, /*was bigbuf_net, BIGSIZ*/
+                               &remend.u.sa, &ouraddr->u.sa, ouraddr->len);
+                       if (rr < 0)
+                               bb_perror_msg_and_die("recvfrom");
+                       unarm();
+               } else
+                       bb_error_msg_and_die("timeout");
+/* Now we learned *to which IP* peer has connected, and we want to anchor
+our socket on it, so that our outbound packets will have correct local IP.
+Unfortunately, bind() on already bound socket will fail now (EINVAL):
+       xbind(netfd, &ouraddr->u.sa, ouraddr->len);
+Need to read the packet, save data, close this socket and
+create new one, and bind() it. TODO */
+               if (!themaddr)
+                       xconnect(netfd, &remend.u.sa, ouraddr->len);
+       } else {
+               /* TCP */
+               arm(o_wait); /* wrap this in a timer, too; 0 = forever */
+               if (setjmp(jbuf) == 0) {
+ again:
+                       remend.len = LSA_SIZEOF_SA;
+                       rr = accept(netfd, &remend.u.sa, &remend.len);
+                       if (rr < 0)
+                               bb_perror_msg_and_die("accept");
+                       if (themaddr && memcmp(&remend.u.sa, &themaddr->u.sa, remend.len) != 0) {
+                               /* nc 1.10 bails out instead, and its error message
+                                * is not suppressed by o_verbose */
+                               if (o_verbose) {
+                                       char *remaddr = xmalloc_sockaddr2dotted(&remend.u.sa);
+                                       bb_error_msg("connect from wrong ip/port %s ignored", remaddr);
+                                       free(remaddr);
+                               }
+                               close(rr);
+                               goto again;
+                       }
+                       unarm();
+               } else
+                       bb_error_msg_and_die("timeout");
+               xmove_fd(rr, netfd); /* dump the old socket, here's our new one */
+               /* find out what address the connection was *to* on our end, in case we're
+                doing a listen-on-any on a multihomed machine.  This allows one to
+                offer different services via different alias addresses, such as the
+                "virtual web site" hack. */
+               rr = getsockname(netfd, &ouraddr->u.sa, &ouraddr->len);
+               if (rr < 0)
+                       bb_perror_msg_and_die("getsockname after accept");
+       }
+
+       if (o_verbose) {
+               char *lcladdr, *remaddr, *remhostname;
+
+#if ENABLE_NC_EXTRA && defined(IP_OPTIONS)
+       /* If we can, look for any IP options.  Useful for testing the receiving end of
+        such things, and is a good exercise in dealing with it.  We do this before
+        the connect message, to ensure that the connect msg is uniformly the LAST
+        thing to emerge after all the intervening crud.  Doesn't work for UDP on
+        any machines I've tested, but feel free to surprise me. */
+               char optbuf[40];
+               socklen_t x = sizeof(optbuf);
+
+               rr = getsockopt(netfd, IPPROTO_IP, IP_OPTIONS, optbuf, &x);
+               if (rr < 0)
+                       bb_perror_msg("getsockopt failed");
+               else if (x) {    /* we've got options, lessee em... */
+                       bin2hex(bigbuf_net, optbuf, x);
+                       bigbuf_net[2*x] = '\0';
+                       fprintf(stderr, "IP options: %s\n", bigbuf_net);
+               }
+#endif
+
+       /* now check out who it is.  We don't care about mismatched DNS names here,
+        but any ADDR and PORT we specified had better fucking well match the caller.
+        Converting from addr to inet_ntoa and back again is a bit of a kludge, but
+        gethostpoop wants a string and there's much gnarlier code out there already,
+        so I don't feel bad.
+        The *real* question is why BFD sockets wasn't designed to allow listens for
+        connections *from* specific hosts/ports, instead of requiring the caller to
+        accept the connection and then reject undesireable ones by closing.
+        In other words, we need a TCP MSG_PEEK. */
+       /* bbox: removed most of it */
+               lcladdr = xmalloc_sockaddr2dotted(&ouraddr->u.sa);
+               remaddr = xmalloc_sockaddr2dotted(&remend.u.sa);
+               remhostname = o_nflag ? remaddr : xmalloc_sockaddr2host(&remend.u.sa);
+               fprintf(stderr, "connect to %s from %s (%s)\n",
+                               lcladdr, remhostname, remaddr);
+               free(lcladdr);
+               free(remaddr);
+               if (!o_nflag)
+                       free(remhostname);
+       }
+}
+
+/* udptest:
+ fire a couple of packets at a UDP target port, just to see if it's really
+ there.  On BSD kernels, ICMP host/port-unreachable errors get delivered to
+ our socket as ECONNREFUSED write errors.  On SV kernels, we lose; we'll have
+ to collect and analyze raw ICMP ourselves a la satan's probe_udp_ports
+ backend.  Guess where one could swipe the appropriate code from...
+
+ Use the time delay between writes if given, otherwise use the "tcp ping"
+ trick for getting the RTT.  [I got that idea from pluvius, and warped it.]
+ Return either the original fd, or clean up and return -1. */
+#if ENABLE_NC_EXTRA
+static int udptest(void)
+{
+       int rr;
+
+       rr = write(netfd, bigbuf_in, 1);
+       if (rr != 1)
+               bb_perror_msg("udptest first write");
+
+       if (o_wait)
+               sleep(o_wait); // can be interrupted! while (t) nanosleep(&t)?
+       else {
+       /* use the tcp-ping trick: try connecting to a normally refused port, which
+        causes us to block for the time that SYN gets there and RST gets back.
+        Not completely reliable, but it *does* mostly work. */
+       /* Set a temporary connect timeout, so packet filtration doesnt cause
+        us to hang forever, and hit it */
+               o_wait = 5;                     /* enough that we'll notice?? */
+               rr = xsocket(ouraddr->u.sa.sa_family, SOCK_STREAM, 0);
+               set_nport(themaddr, htons(SLEAZE_PORT));
+               connect_w_timeout(rr);
+               /* don't need to restore themaddr's port, it's not used anymore */
+               close(rr);
+               o_wait = 0; /* restore */
+       }
+
+       rr = write(netfd, bigbuf_in, 1);
+       return (rr != 1); /* if rr == 1, return 0 (success) */
+}
+#else
+int udptest(void);
+#endif
+
+/* oprint:
+ Hexdump bytes shoveled either way to a running logfile, in the format:
+ D offset       -  - - - --- 16 bytes --- - - -  -     # .... ascii .....
+ where "which" sets the direction indicator, D:
+ 0 -- sent to network, or ">"
+ 1 -- rcvd and printed to stdout, or "<"
+ and "buf" and "n" are data-block and length.  If the current block generates
+ a partial line, so be it; we *want* that lockstep indication of who sent
+ what when.  Adapted from dgaudet's original example -- but must be ripping
+ *fast*, since we don't want to be too disk-bound... */
+#if ENABLE_NC_EXTRA
+static void oprint(int direction, unsigned char *p, unsigned bc)
+{
+       unsigned obc;           /* current "global" offset */
+       unsigned x;
+       unsigned char *op;      /* out hexdump ptr */
+       unsigned char *ap;      /* out asc-dump ptr */
+       unsigned char stage[100];
+
+       if (bc == 0)
+               return;
+
+       obc = wrote_net; /* use the globals! */
+       if (direction == '<')
+               obc = wrote_out;
+       stage[0] = direction;
+       stage[59] = '#'; /* preload separator */
+       stage[60] = ' ';
+
+       do {    /* for chunk-o-data ... */
+               x = 16;
+               if (bc < 16) {
+                       /* memset(&stage[bc*3 + 11], ' ', 16*3 - bc*3); */
+                       memset(&stage[11], ' ', 16*3);
+                       x = bc;
+               }
+               sprintf((char *)&stage[1], " %8.8x ", obc);  /* xxx: still slow? */
+               bc -= x;          /* fix current count */
+               obc += x;         /* fix current offset */
+               op = &stage[11];  /* where hex starts */
+               ap = &stage[61];  /* where ascii starts */
+
+               do {  /* for line of dump, however long ... */
+                       *op++ = 0x20 | bb_hexdigits_upcase[*p >> 4];
+                       *op++ = 0x20 | bb_hexdigits_upcase[*p & 0x0f];
+                       *op++ = ' ';
+                       if ((*p > 31) && (*p < 127))
+                               *ap = *p;   /* printing */
+                       else
+                               *ap = '.';  /* nonprinting, loose def */
+                       ap++;
+                       p++;
+               } while (--x);
+               *ap++ = '\n';  /* finish the line */
+               xwrite(ofd, stage, ap - stage);
+       } while (bc);
+}
+#else
+void oprint(int direction, unsigned char *p, unsigned bc);
+#endif
+
+/* readwrite:
+ handle stdin/stdout/network I/O.  Bwahaha!! -- the select loop from hell.
+ In this instance, return what might become our exit status. */
+static int readwrite(void)
+{
+       int rr;
+       char *zp = zp; /* gcc */  /* stdin buf ptr */
+       char *np = np;            /* net-in buf ptr */
+       unsigned rzleft;
+       unsigned rnleft;
+       unsigned netretry;              /* net-read retry counter */
+       unsigned wretry;                /* net-write sanity counter */
+       unsigned wfirst;                /* one-shot flag to skip first net read */
+
+       /* if you don't have all this FD_* macro hair in sys/types.h, you'll have to
+        either find it or do your own bit-bashing: *ding1 |= (1 << fd), etc... */
+       FD_SET(netfd, &ding1);                /* global: the net is open */
+       netretry = 2;
+       wfirst = 0;
+       rzleft = rnleft = 0;
+       if (o_interval)
+               sleep(o_interval);                /* pause *before* sending stuff, too */
+
+       errno = 0;                        /* clear from sleep, close, whatever */
+       /* and now the big ol' select shoveling loop ... */
+       while (FD_ISSET(netfd, &ding1)) {        /* i.e. till the *net* closes! */
+               wretry = 8200;                        /* more than we'll ever hafta write */
+               if (wfirst) {                        /* any saved stdin buffer? */
+                       wfirst = 0;                        /* clear flag for the duration */
+                       goto shovel;                        /* and go handle it first */
+               }
+               ding2 = ding1;                        /* FD_COPY ain't portable... */
+       /* some systems, notably linux, crap into their select timers on return, so
+        we create a expendable copy and give *that* to select.  */
+               if (o_wait) {
+                       struct timeval tmp_timer;
+                       tmp_timer.tv_sec = o_wait;
+                       tmp_timer.tv_usec = 0;
+               /* highest possible fd is netfd (3) */
+                       rr = select(netfd+1, &ding2, NULL, NULL, &tmp_timer);
+               } else
+                       rr = select(netfd+1, &ding2, NULL, NULL, NULL);
+               if (rr < 0 && errno != EINTR) {                /* might have gotten ^Zed, etc */
+                       holler_perror("select");
+                       close(netfd);
+                       return 1;
+               }
+       /* if we have a timeout AND stdin is closed AND we haven't heard anything
+        from the net during that time, assume it's dead and close it too. */
+               if (rr == 0) {
+                       if (!FD_ISSET(STDIN_FILENO, &ding1))
+                               netretry--;                        /* we actually try a coupla times. */
+                       if (!netretry) {
+                               if (o_verbose > 1)                /* normally we don't care */
+                                       fprintf(stderr, "net timeout\n");
+                               close(netfd);
+                               return 0;                        /* not an error! */
+                       }
+               } /* select timeout */
+       /* xxx: should we check the exception fds too?  The read fds seem to give
+        us the right info, and none of the examples I found bothered. */
+
+       /* Ding!!  Something arrived, go check all the incoming hoppers, net first */
+               if (FD_ISSET(netfd, &ding2)) {                /* net: ding! */
+                       rr = read(netfd, bigbuf_net, BIGSIZ);
+                       if (rr <= 0) {
+                               if (rr < 0 && o_verbose > 1) {
+                                       /* nc 1.10 doesn't do this */
+                                       bb_perror_msg("net read");
+                               }
+                               FD_CLR(netfd, &ding1);                /* net closed, we'll finish up... */
+                               rzleft = 0;                        /* can't write anymore: broken pipe */
+                       } else {
+                               rnleft = rr;
+                               np = bigbuf_net;
+                       }
+Debug("got %d from the net, errno %d", rr, errno);
+               } /* net:ding */
+
+       /* if we're in "slowly" mode there's probably still stuff in the stdin
+        buffer, so don't read unless we really need MORE INPUT!  MORE INPUT! */
+               if (rzleft)
+                       goto shovel;
+
+       /* okay, suck more stdin */
+               if (FD_ISSET(STDIN_FILENO, &ding2)) {                /* stdin: ding! */
+                       rr = read(STDIN_FILENO, bigbuf_in, BIGSIZ);
+       /* Considered making reads here smaller for UDP mode, but 8192-byte
+        mobygrams are kinda fun and exercise the reassembler. */
+                       if (rr <= 0) {                        /* at end, or fukt, or ... */
+                               FD_CLR(STDIN_FILENO, &ding1);                /* disable and close stdin */
+                               close(0);
+                       } else {
+                               rzleft = rr;
+                               zp = bigbuf_in;
+                       }
+               } /* stdin:ding */
+ shovel:
+       /* now that we've dingdonged all our thingdings, send off the results.
+        Geez, why does this look an awful lot like the big loop in "rsh"? ...
+        not sure if the order of this matters, but write net -> stdout first. */
+
+       /* sanity check.  Works because they're both unsigned... */
+               if ((rzleft > 8200) || (rnleft > 8200)) {
+                       holler_error("bogus buffers: %u, %u", rzleft, rnleft);
+                       rzleft = rnleft = 0;
+               }
+       /* net write retries sometimes happen on UDP connections */
+               if (!wretry) {                        /* is something hung? */
+                       holler_error("too many output retries");
+                       return 1;
+               }
+               if (rnleft) {
+                       rr = write(STDOUT_FILENO, np, rnleft);
+                       if (rr > 0) {
+                               if (o_ofile) /* log the stdout */
+                                       oprint('<', (unsigned char *)np, rr);
+                               np += rr;                        /* fix up ptrs and whatnot */
+                               rnleft -= rr;                        /* will get sanity-checked above */
+                               wrote_out += rr;                /* global count */
+                       }
+Debug("wrote %d to stdout, errno %d", rr, errno);
+               } /* rnleft */
+               if (rzleft) {
+                       if (o_interval)                        /* in "slowly" mode ?? */
+                               rr = findline(zp, rzleft);
+                       else
+                               rr = rzleft;
+                       rr = write(netfd, zp, rr);        /* one line, or the whole buffer */
+                       if (rr > 0) {
+                               if (o_ofile) /* log what got sent */
+                                       oprint('>', (unsigned char *)zp, rr);
+                               zp += rr;
+                               rzleft -= rr;
+                               wrote_net += rr;                /* global count */
+                       }
+Debug("wrote %d to net, errno %d", rr, errno);
+               } /* rzleft */
+               if (o_interval) {                        /* cycle between slow lines, or ... */
+                       sleep(o_interval);
+                       errno = 0;                        /* clear from sleep */
+                       continue;                        /* ...with hairy select loop... */
+               }
+               if ((rzleft) || (rnleft)) {                /* shovel that shit till they ain't */
+                       wretry--;                        /* none left, and get another load */
+                       goto shovel;
+               }
+       } /* while ding1:netfd is open */
+
+       /* XXX: maybe want a more graceful shutdown() here, or screw around with
+        linger times??  I suspect that I don't need to since I'm always doing
+        blocking reads and writes and my own manual "last ditch" efforts to read
+        the net again after a timeout.  I haven't seen any screwups yet, but it's
+        not like my test network is particularly busy... */
+       close(netfd);
+       return 0;
+} /* readwrite */
+
+/* main: now we pull it all together... */
+int nc_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int nc_main(int argc, char **argv)
+{
+       char *str_p, *str_s;
+       USE_NC_EXTRA(char *str_i, *str_o;)
+       char *themdotted = themdotted; /* gcc */
+       char **proggie;
+       int x;
+       unsigned o_lport = 0;
+
+       INIT_G();
+
+       /* catch a signal or two for cleanup */
+       bb_signals(0
+               + (1 << SIGINT)
+               + (1 << SIGQUIT)
+               + (1 << SIGTERM)
+               , catch);
+       /* and suppress others... */
+       bb_signals(0
+#ifdef SIGURG
+               + (1 << SIGURG)
+#endif
+               + (1 << SIGPIPE) /* important! */
+               , SIG_IGN);
+
+       proggie = argv;
+       while (*++proggie) {
+               if (strcmp(*proggie, "-e") == 0) {
+                       *proggie = NULL;
+                       argc = proggie - argv;
+                       proggie++;
+                       goto e_found;
+               }
+       }
+       proggie = NULL;
+ e_found:
+
+       // -g -G -t -r deleted, unimplemented -a deleted too
+       opt_complementary = "?2:vv:w+"; /* max 2 params; -v is a counter; -w N */
+       getopt32(argv, "hnp:s:uvw:" USE_NC_SERVER("l")
+                       USE_NC_EXTRA("i:o:z"),
+                       &str_p, &str_s, &o_wait
+                       USE_NC_EXTRA(, &str_i, &str_o, &o_verbose));
+       argv += optind;
+#if ENABLE_NC_EXTRA
+       if (option_mask32 & OPT_i) /* line-interval time */
+               o_interval = xatou_range(str_i, 1, 0xffff);
+#endif
+       //if (option_mask32 & OPT_l) /* listen mode */
+       //if (option_mask32 & OPT_n) /* numeric-only, no DNS lookups */
+       //if (option_mask32 & OPT_o) /* hexdump log */
+       if (option_mask32 & OPT_p) { /* local source port */
+               o_lport = bb_lookup_port(str_p, o_udpmode ? "udp" : "tcp", 0);
+               if (!o_lport)
+                       bb_error_msg_and_die("bad local port '%s'", str_p);
+       }
+       //if (option_mask32 & OPT_r) /* randomize various things */
+       //if (option_mask32 & OPT_u) /* use UDP */
+       //if (option_mask32 & OPT_v) /* verbose */
+       //if (option_mask32 & OPT_w) /* wait time */
+       //if (option_mask32 & OPT_z) /* little or no data xfer */
+
+       /* We manage our fd's so that they are never 0,1,2 */
+       /*bb_sanitize_stdio(); - not needed */
+
+       if (argv[0]) {
+               themaddr = xhost2sockaddr(argv[0],
+                       argv[1]
+                       ? bb_lookup_port(argv[1], o_udpmode ? "udp" : "tcp", 0)
+                       : 0);
+       }
+
+       /* create & bind network socket */
+       x = (o_udpmode ? SOCK_DGRAM : SOCK_STREAM);
+       if (option_mask32 & OPT_s) { /* local address */
+               /* if o_lport is still 0, then we will use random port */
+               ouraddr = xhost2sockaddr(str_s, o_lport);
+#ifdef BLOAT
+               /* prevent spurious "UDP listen needs !0 port" */
+               o_lport = get_nport(ouraddr);
+               o_lport = ntohs(o_lport);
+#endif
+               x = xsocket(ouraddr->u.sa.sa_family, x, 0);
+       } else {
+               /* We try IPv6, then IPv4, unless addr family is
+                * implicitly set by way of remote addr/port spec */
+               x = xsocket_type(&ouraddr,
+                               (themaddr ? themaddr->u.sa.sa_family : AF_UNSPEC),
+                               x);
+               if (o_lport)
+                       set_nport(ouraddr, htons(o_lport));
+       }
+       xmove_fd(x, netfd);
+       setsockopt_reuseaddr(netfd);
+       if (o_udpmode)
+               socket_want_pktinfo(netfd);
+       xbind(netfd, &ouraddr->u.sa, ouraddr->len);
+#if 0
+       setsockopt(netfd, SOL_SOCKET, SO_RCVBUF, &o_rcvbuf, sizeof o_rcvbuf);
+       setsockopt(netfd, SOL_SOCKET, SO_SNDBUF, &o_sndbuf, sizeof o_sndbuf);
+#endif
+
+#ifdef BLOAT
+       if (OPT_l && (option_mask32 & (OPT_u|OPT_l)) == (OPT_u|OPT_l)) {
+               /* apparently UDP can listen ON "port 0",
+                but that's not useful */
+               if (!o_lport)
+                       bb_error_msg_and_die("UDP listen needs nonzero -p port");
+       }
+#endif
+
+       FD_SET(STDIN_FILENO, &ding1);                        /* stdin *is* initially open */
+       if (proggie) {
+               close(0); /* won't need stdin */
+               option_mask32 &= ~OPT_o; /* -o with -e is meaningless! */
+       }
+#if ENABLE_NC_EXTRA
+       if (o_ofile)
+               xmove_fd(xopen(str_o, O_WRONLY|O_CREAT|O_TRUNC), ofd);
+#endif
+
+       if (o_listen) {
+               dolisten();
+               /* dolisten does its own connect reporting */
+               if (proggie) /* -e given? */
+                       doexec(proggie);
+               x = readwrite(); /* it even works with UDP! */
+       } else {
+               /* Outbound connects.  Now we're more picky about args... */
+               if (!themaddr)
+                       bb_error_msg_and_die("no destination");
+
+               remend = *themaddr;
+               if (o_verbose)
+                       themdotted = xmalloc_sockaddr2dotted(&themaddr->u.sa);
+
+               x = connect_w_timeout(netfd);
+               if (o_zero && x == 0 && o_udpmode)        /* if UDP scanning... */
+                       x = udptest();
+               if (x == 0) {                        /* Yow, are we OPEN YET?! */
+                       if (o_verbose)
+                               fprintf(stderr, "%s (%s) open\n", argv[0], themdotted);
+                       if (proggie)                        /* exec is valid for outbound, too */
+                               doexec(proggie);
+                       if (!o_zero)
+                               x = readwrite();
+               } else { /* connect or udptest wasn't successful */
+                       x = 1;                                /* exit status */
+                       /* if we're scanning at a "one -v" verbosity level, don't print refusals.
+                        Give it another -v if you want to see everything. */
+                       if (o_verbose > 1 || (o_verbose && errno != ECONNREFUSED))
+                               bb_perror_msg("%s (%s)", argv[0], themdotted);
+               }
+       }
+       if (o_verbose > 1)                /* normally we don't care */
+               fprintf(stderr, SENT_N_RECV_M, wrote_net, wrote_out);
+       return x;
+}
diff --git a/networking/netstat.c b/networking/netstat.c
new file mode 100644 (file)
index 0000000..b246280
--- /dev/null
@@ -0,0 +1,712 @@
+/* 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 <magick@linux-fan.com>
+ *
+ * 2002-04-20
+ * IPV6 support added by Bart Visscher <magick@linux-fan.com>
+ *
+ * 2008-07-10
+ * optional '-p' flag support ported from net-tools by G. Somlo <somlo@cmu.edu>
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.h"
+#include "inet_common.h"
+
+#define NETSTAT_OPTS "laentuwx" \
+       USE_ROUTE(               "r") \
+       USE_FEATURE_NETSTAT_WIDE("W") \
+       USE_FEATURE_NETSTAT_PRG( "p")
+
+enum {
+       OPTBIT_KEEP_OLD = 7,
+       USE_ROUTE(               OPTBIT_ROUTE,)
+       USE_FEATURE_NETSTAT_WIDE(OPTBIT_WIDE ,)
+       USE_FEATURE_NETSTAT_PRG( OPTBIT_PRG  ,)
+       OPT_sock_listen = 1 << 0, // l
+       OPT_sock_all    = 1 << 1, // a
+       OPT_extended    = 1 << 2, // e
+       OPT_noresolve   = 1 << 3, // n
+       OPT_sock_tcp    = 1 << 4, // t
+       OPT_sock_udp    = 1 << 5, // u
+       OPT_sock_raw    = 1 << 6, // w
+       OPT_sock_unix   = 1 << 7, // x
+       OPT_route       = USE_ROUTE(               (1 << OPTBIT_ROUTE)) + 0, // r
+       OPT_wide        = USE_FEATURE_NETSTAT_WIDE((1 << OPTBIT_WIDE )) + 0, // W
+       OPT_prg         = USE_FEATURE_NETSTAT_PRG( (1 << OPTBIT_PRG  )) + 0, // p
+};
+
+#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)
+
+
+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            */
+
+/* Standard printout size */
+#define PRINT_IP_MAX_SIZE           23
+#define PRINT_NET_CONN              "%s   %6ld %6ld %-23s %-23s %-12s"
+#define PRINT_NET_CONN_HEADER       "\nProto Recv-Q Send-Q %-23s %-23s State       "
+
+/* When there are IPv6 connections the IPv6 addresses will be
+ * truncated to none-recognition. The '-W' option makes the
+ * address columns wide enough to accomodate for longest possible
+ * IPv6 addresses, i.e. addresses of the form
+ * xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:ddd.ddd.ddd.ddd
+ */
+#define PRINT_IP_MAX_SIZE_WIDE      51  /* INET6_ADDRSTRLEN + 5 for the port number */
+#define PRINT_NET_CONN_WIDE         "%s   %6ld %6ld %-51s %-51s %-12s"
+#define PRINT_NET_CONN_HEADER_WIDE  "\nProto Recv-Q Send-Q %-51s %-51s State       "
+
+
+#define PROGNAME_WIDTH     20
+#define PROGNAME_WIDTH_STR "20"
+/* PROGNAME_WIDTH chars: 12345678901234567890 */
+#define PROGNAME_BANNER "PID/Program name    "
+
+struct prg_node {
+       struct prg_node *next;
+       long inode;
+       char name[PROGNAME_WIDTH];
+};
+
+#define PRG_HASH_SIZE 211
+
+
+struct globals {
+       const char *net_conn_line;
+       smallint flags;
+#if ENABLE_FEATURE_NETSTAT_PRG
+       smallint prg_cache_loaded;
+       struct prg_node *prg_hash[PRG_HASH_SIZE];
+#endif
+};
+#define G (*ptr_to_globals)
+#define flags            (G.flags           )
+#define net_conn_line    (G.net_conn_line   )
+#define prg_hash         (G.prg_hash        )
+#define prg_cache_loaded (G.prg_cache_loaded)
+#define INIT_G() do { \
+       SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
+       flags = NETSTAT_CONNECTED | NETSTAT_ALLPROTO; \
+       net_conn_line = PRINT_NET_CONN; \
+} while (0)
+
+
+#if ENABLE_FEATURE_NETSTAT_PRG
+
+/* Deliberately truncating long to unsigned *int* */
+#define PRG_HASHIT(x) ((unsigned)(x) % PRG_HASH_SIZE)
+
+#define print_progname_banner() do { \
+       if (option_mask32 & OPT_prg) printf(PROGNAME_BANNER); \
+} while (0)
+
+static void prg_cache_add(long inode, char *name)
+{
+       unsigned hi = PRG_HASHIT(inode);
+       struct prg_node **pnp, *pn;
+
+       prg_cache_loaded = 2;
+       for (pnp = prg_hash + hi; (pn = *pnp) != NULL; pnp = &pn->next) {
+               if (pn->inode == inode) {
+                       /* Some warning should be appropriate here
+                          as we got multiple processes for one i-node */
+                       return;
+               }
+       }
+       *pnp = xzalloc(sizeof(struct prg_node));
+       pn = *pnp;
+       pn->inode = inode;
+       safe_strncpy(pn->name, name, PROGNAME_WIDTH);
+}
+
+static const char *prg_cache_get(long inode)
+{
+       unsigned hi = PRG_HASHIT(inode);
+       struct prg_node *pn;
+
+       for (pn = prg_hash[hi]; pn; pn = pn->next)
+               if (pn->inode == inode)
+                       return pn->name;
+       return "-";
+}
+
+#if ENABLE_FEATURE_CLEAN_UP
+static void prg_cache_clear(void)
+{
+       struct prg_node **pnp, *pn;
+
+       for (pnp = prg_hash; pnp < prg_hash + PRG_HASH_SIZE; pnp++) {
+               while ((pn = *pnp) != NULL) {
+                       *pnp = pn->next;
+                       free(pn);
+               }
+       }
+}
+#else
+#define prg_cache_clear() ((void)0)
+#endif
+
+static long extract_socket_inode(const char *lname)
+{
+       long inode = -1;
+
+       if (strncmp(lname, "socket:[", sizeof("socket:[")-1) == 0) {
+               /* "socket:[12345]", extract the "12345" as inode */
+               inode = bb_strtol(lname + sizeof("socket:[")-1, (char**)&lname, 0);
+               if (*lname != ']')
+                       inode = -1;
+       } else if (strncmp(lname, "[0000]:", sizeof("[0000]:")-1) == 0) {
+               /* "[0000]:12345", extract the "12345" as inode */
+               inode = bb_strtol(lname + sizeof("[0000]:")-1, NULL, 0);
+               if (errno) /* not NUL terminated? */
+                       inode = -1;
+       }
+
+#if 0 /* bb_strtol returns all-ones bit pattern on ERANGE anyway */
+       if (errno == ERANGE)
+               inode = -1;
+#endif
+       return inode;
+}
+
+static int FAST_FUNC file_act(const char *fileName,
+               struct stat *statbuf UNUSED_PARAM,
+               void *userData,
+               int depth UNUSED_PARAM)
+{
+       char *linkname;
+       long inode;
+
+       linkname = xmalloc_readlink(fileName);
+       if (linkname != NULL) {
+               inode = extract_socket_inode(linkname);
+               free(linkname);
+               if (inode >= 0)
+                       prg_cache_add(inode, (char *)userData);
+       }
+       return TRUE;
+}
+
+static int FAST_FUNC dir_act(const char *fileName,
+               struct stat *statbuf UNUSED_PARAM,
+               void *userData UNUSED_PARAM,
+               int depth)
+{
+       const char *shortName;
+       char *p, *q;
+       char cmdline_buf[512];
+       int i;
+
+       if (depth == 0) /* "/proc" itself */
+               return TRUE; /* continue looking one level below /proc */
+
+       shortName = fileName + sizeof("/proc/")-1; /* point after "/proc/" */
+       if (!isdigit(shortName[0])) /* skip /proc entries whic aren't processes */
+               return SKIP;
+
+       p = concat_path_file(fileName, "cmdline"); /* "/proc/PID/cmdline" */
+       i = open_read_close(p, cmdline_buf, sizeof(cmdline_buf) - 1);
+       free(p);
+       if (i < 0)
+               return FALSE;
+       cmdline_buf[i] = '\0';
+       q = concat_path_file(shortName, bb_basename(cmdline_buf)); /* "PID/argv0" */
+
+       /* go through all files in /proc/PID/fd */
+       p = concat_path_file(fileName, "fd");
+       i = recursive_action(p, ACTION_RECURSE | ACTION_QUIET,
+                               file_act, NULL, (void *)q, 0);
+
+       free(p);
+       free(q);
+
+       if (!i)
+               return FALSE;   /* signal permissions error to caller */
+
+       return SKIP;            /* caller should not recurse further into this dir. */
+}
+
+static void prg_cache_load(void)
+{
+       int load_ok;
+
+       prg_cache_loaded = 1;
+       load_ok = recursive_action("/proc", ACTION_RECURSE | ACTION_QUIET,
+                               NULL, dir_act, NULL, 0);
+       if (load_ok)
+               return;
+
+       if (prg_cache_loaded == 1)
+               bb_error_msg("can't scan /proc - are you root?");
+       else
+               bb_error_msg("showing only processes with your user ID");
+}
+
+#else
+
+#define prg_cache_clear()       ((void)0)
+#define print_progname_banner() ((void)0)
+
+#endif //ENABLE_FEATURE_NETSTAT_PRG
+
+
+#if ENABLE_FEATURE_IPV6
+static void build_ipv6_addr(char* local_addr, struct sockaddr_in6* localaddr)
+{
+       char addr6[INET6_ADDRSTRLEN];
+       struct in6_addr in6;
+
+       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);
+
+       localaddr->sin6_family = AF_INET6;
+}
+#endif
+
+#if ENABLE_FEATURE_IPV6
+static void build_ipv4_addr(char* local_addr, struct sockaddr_in6* localaddr)
+#else
+static void build_ipv4_addr(char* local_addr, struct sockaddr_in* localaddr)
+#endif
+{
+       sscanf(local_addr, "%X",
+                  &((struct sockaddr_in *) localaddr)->sin_addr.s_addr);
+       ((struct sockaddr *) localaddr)->sa_family = AF_INET;
+}
+
+static const char *get_sname(int port, const char *proto, int numeric)
+{
+       if (!port)
+               return "*";
+       if (!numeric) {
+               struct servent *se = getservbyport(port, proto);
+               if (se)
+                       return se->s_name;
+       }
+       /* hummm, we may return static buffer here!! */
+       return itoa(ntohs(port));
+}
+
+static char *ip_port_str(struct sockaddr *addr, int port, const char *proto, int numeric)
+{
+       char *host, *host_port;
+
+       /* Code which used "*" for INADDR_ANY is removed: it's ambiguous
+        * in IPv6, while "0.0.0.0" is not. */
+
+       host = numeric ? xmalloc_sockaddr2dotted_noport(addr)
+                      : xmalloc_sockaddr2host_noport(addr);
+
+       host_port = xasprintf("%s:%s", host, get_sname(htons(port), proto, numeric));
+       free(host);
+       return host_port;
+}
+
+struct inet_params {
+       int local_port, rem_port, state, uid;
+#if ENABLE_FEATURE_IPV6
+       struct sockaddr_in6 localaddr, remaddr;
+#else
+       struct sockaddr_in localaddr, remaddr;
+#endif
+       unsigned long rxq, txq, inode;
+};
+
+static int scan_inet_proc_line(struct inet_params *param, char *line)
+{
+       int num;
+       char local_addr[64], rem_addr[64];
+
+       num = sscanf(line,
+                       "%*d: %64[0-9A-Fa-f]:%X "
+                       "%64[0-9A-Fa-f]:%X %X "
+                       "%lX:%lX %*X:%*X "
+                       "%*X %d %*d %ld ",
+                       local_addr, &param->local_port,
+                       rem_addr, &param->rem_port, &param->state,
+                       &param->txq, &param->rxq,
+                       &param->uid, &param->inode);
+       if (num < 9) {
+               return 1; /* error */
+       }
+
+       if (strlen(local_addr) > 8) {
+#if ENABLE_FEATURE_IPV6
+               build_ipv6_addr(local_addr, &param->localaddr);
+               build_ipv6_addr(rem_addr, &param->remaddr);
+#endif
+       } else {
+               build_ipv4_addr(local_addr, &param->localaddr);
+               build_ipv4_addr(rem_addr, &param->remaddr);
+       }
+       return 0;
+}
+
+static void print_inet_line(struct inet_params *param,
+               const char *state_str, const char *proto, int is_connected)
+{
+       if ((is_connected && (flags & NETSTAT_CONNECTED))
+        || (!is_connected && (flags & NETSTAT_LISTENING))
+       ) {
+               char *l = ip_port_str(
+                               (struct sockaddr *) &param->localaddr, param->local_port,
+                               proto, flags & NETSTAT_NUMERIC);
+               char *r = ip_port_str(
+                               (struct sockaddr *) &param->remaddr, param->rem_port,
+                               proto, flags & NETSTAT_NUMERIC);
+               printf(net_conn_line,
+                       proto, param->rxq, param->txq, l, r, state_str);
+#if ENABLE_FEATURE_NETSTAT_PRG
+               if (option_mask32 & OPT_prg)
+                       printf("%."PROGNAME_WIDTH_STR"s", prg_cache_get(param->inode));
+#endif
+               bb_putchar('\n');
+               free(l);
+               free(r);
+       }
+}
+
+static int FAST_FUNC tcp_do_one(char *line)
+{
+       struct inet_params param;
+
+       if (scan_inet_proc_line(&param, line))
+               return 1;
+
+       print_inet_line(&param, tcp_state[param.state], "tcp", param.rem_port);
+       return 0;
+}
+
+#if ENABLE_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
+
+static int FAST_FUNC udp_do_one(char *line)
+{
+       int have_remaddr;
+       const char *state_str;
+       struct inet_params param;
+
+       if (scan_inet_proc_line(&param, line))
+               return 1;
+
+       state_str = "UNKNOWN";
+       switch (param.state) {
+       case TCP_ESTABLISHED:
+               state_str = "ESTABLISHED";
+               break;
+       case TCP_CLOSE:
+               state_str = "";
+               break;
+       }
+
+       have_remaddr = notnull(param.remaddr);
+       print_inet_line(&param, state_str, "udp", have_remaddr);
+       return 0;
+}
+
+static int FAST_FUNC raw_do_one(char *line)
+{
+       int have_remaddr;
+       struct inet_params param;
+
+       if (scan_inet_proc_line(&param, line))
+               return 1;
+
+       have_remaddr = notnull(param.remaddr);
+       print_inet_line(&param, itoa(param.state), "raw", have_remaddr);
+       return 0;
+}
+
+static int FAST_FUNC unix_do_one(char *line)
+{
+       unsigned long refcnt, proto, unix_flags;
+       unsigned long inode;
+       int type, state;
+       int num, path_ofs;
+       const char *ss_proto, *ss_state, *ss_type;
+       char ss_flags[32];
+
+       /* 2.6.15 may report lines like "... @/tmp/fam-user-^@^@^@^@^@^@^@..."
+        * Other users report long lines filled by NUL bytes.
+        * (those ^@ are NUL bytes too). We see them as empty lines. */
+       if (!line[0])
+               return 0;
+
+       path_ofs = 0; /* paranoia */
+       num = sscanf(line, "%*p: %lX %lX %lX %X %X %lu %n",
+                       &refcnt, &proto, &unix_flags, &type, &state, &inode, &path_ofs);
+       if (num < 6) {
+               return 1; /* error */
+       }
+       if ((flags & (NETSTAT_LISTENING|NETSTAT_CONNECTED)) != (NETSTAT_LISTENING|NETSTAT_CONNECTED)) {
+               if ((state == SS_UNCONNECTED) && (unix_flags & SO_ACCEPTCON)) {
+                       if (!(flags & NETSTAT_LISTENING))
+                               return 0;
+               } else {
+                       if (!(flags & NETSTAT_CONNECTED))
+                               return 0;
+               }
+       }
+
+       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 %6lu ",
+               ss_proto, refcnt, ss_flags, ss_type, ss_state, inode
+               );
+
+#if ENABLE_FEATURE_NETSTAT_PRG
+       if (option_mask32 & OPT_prg)
+               printf("%-"PROGNAME_WIDTH_STR"s", prg_cache_get(inode));
+#endif
+
+       /* TODO: currently we stop at first NUL byte. Is it a problem? */
+       line += path_ofs;
+       *strchrnul(line, '\n') = '\0';
+       while (*line)
+               fputc_printable(*line++, stdout);
+       bb_putchar('\n');
+       return 0;
+}
+
+static void do_info(const char *file, int FAST_FUNC (*proc)(char *))
+{
+       int lnr;
+       FILE *procinfo;
+       char *buffer;
+
+       /* _stdin is just to save "r" param */
+       procinfo = fopen_or_warn_stdin(file);
+       if (procinfo == NULL) {
+               return;
+       }
+       lnr = 0;
+       /* Why xmalloc_fgets_str? because it doesn't stop on NULs */
+       while ((buffer = xmalloc_fgets_str(procinfo, "\n")) != NULL) {
+               /* line 0 is skipped */
+               if (lnr && proc(buffer))
+                       bb_error_msg("%s: bogus data on line %d", file, lnr + 1);
+               lnr++;
+               free(buffer);
+       }
+       fclose(procinfo);
+}
+
+int netstat_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int netstat_main(int argc UNUSED_PARAM, char **argv)
+{
+       const char *net_conn_line_header = PRINT_NET_CONN_HEADER;
+       unsigned opt;
+
+       INIT_G();
+
+       /* Option string must match NETSTAT_xxx constants */
+       opt = getopt32(argv, NETSTAT_OPTS);
+       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_route) { // -r
+#if ENABLE_ROUTE
+               bb_displayroutes(flags & NETSTAT_NUMERIC, !(opt & OPT_extended));
+               return 0;
+#else
+               bb_show_usage();
+#endif
+       }
+       if (opt & OPT_wide) { // -W
+               net_conn_line = PRINT_NET_CONN_WIDE;
+               net_conn_line_header = PRINT_NET_CONN_HEADER_WIDE;
+       }
+#if ENABLE_FEATURE_NETSTAT_PRG
+       if (opt & OPT_prg) { // -p
+               prg_cache_load();
+       }
+#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(net_conn_line_header, "Local Address", "Foreign Address");
+               print_progname_banner();
+               bb_putchar('\n');
+       }
+       if (flags & NETSTAT_TCP) {
+               do_info("/proc/net/tcp", tcp_do_one);
+#if ENABLE_FEATURE_IPV6
+               do_info("/proc/net/tcp6", tcp_do_one);
+#endif
+       }
+       if (flags & NETSTAT_UDP) {
+               do_info("/proc/net/udp", udp_do_one);
+#if ENABLE_FEATURE_IPV6
+               do_info("/proc/net/udp6", udp_do_one);
+#endif
+       }
+       if (flags & NETSTAT_RAW) {
+               do_info("/proc/net/raw", raw_do_one);
+#if ENABLE_FEATURE_IPV6
+               do_info("/proc/net/raw6", 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 ");
+               print_progname_banner();
+               printf("Path\n");
+               do_info("/proc/net/unix", unix_do_one);
+       }
+       prg_cache_clear();
+       return 0;
+}
diff --git a/networking/nslookup.c b/networking/nslookup.c
new file mode 100644 (file)
index 0000000..2628711
--- /dev/null
@@ -0,0 +1,175 @@
+/* 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 <beppu@codepoet.org>
+ *
+ * Correct default name server display and explicit name server option
+ * added by Ben Zeckel <bzeckel@hmc.edu> June 2001
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+#include <resolv.h>
+#include "libbb.h"
+
+/*
+ * I'm only implementing non-interactive mode;
+ * I totally forgot nslookup even had an interactive mode.
+ *
+ * This applet is the only user of res_init(). Without it,
+ * you may avoid pulling in _res global from libc.
+ */
+
+/* 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 print_host(const char *hostname, const char *header)
+{
+       /* We can't use xhost2sockaddr() - we want to get ALL addresses,
+        * not just one */
+       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;
+               unsigned cnt = 0;
+
+               printf("%-10s %s\n", header, hostname);
+               // puts(cur->ai_canonname); ?
+               while (cur) {
+                       char *dotted, *revhost;
+                       dotted = xmalloc_sockaddr2dotted_noport(cur->ai_addr);
+                       revhost = xmalloc_sockaddr2hostonly_noport(cur->ai_addr);
+
+                       printf("Address %u: %s%c", ++cnt, dotted, revhost ? ' ' : '\n');
+                       if (revhost) {
+                               puts(revhost);
+                               if (ENABLE_FEATURE_CLEAN_UP)
+                                       free(revhost);
+                       }
+                       if (ENABLE_FEATURE_CLEAN_UP)
+                               free(dotted);
+                       cur = cur->ai_next;
+               }
+       } else {
+#if ENABLE_VERBOSE_RESOLUTION_ERRORS
+               bb_error_msg("can't resolve '%s': %s", hostname, gai_strerror(rc));
+#else
+               bb_error_msg("can't resolve '%s'", hostname);
+#endif
+       }
+       if (ENABLE_FEATURE_CLEAN_UP)
+               freeaddrinfo(result);
+       return (rc != 0);
+}
+
+/* lookup the default nameserver and display it */
+static void server_print(void)
+{
+       char *server;
+       struct sockaddr *sa;
+
+#if ENABLE_FEATURE_IPV6
+       sa = (struct sockaddr*)_res._u._ext.nsaddrs[0];
+       if (!sa)
+#endif
+               sa = (struct sockaddr*)&_res.nsaddr_list[0];
+       server = xmalloc_sockaddr2dotted_noport(sa);
+
+       print_host(server, "Server:");
+       if (ENABLE_FEATURE_CLEAN_UP)
+               free(server);
+       bb_putchar('\n');
+}
+
+/* alter the global _res nameserver structure to use
+   an explicit dns server instead of what is in /etc/resolv.conf */
+static void set_default_dns(const char *server)
+{
+       len_and_sockaddr *lsa;
+
+       /* NB: this works even with, say, "[::1]:5353"! :) */
+       lsa = xhost2sockaddr(server, 53);
+
+       if (lsa->u.sa.sa_family == AF_INET) {
+               _res.nscount = 1;
+               /* struct copy */
+               _res.nsaddr_list[0] = lsa->u.sin;
+       }
+#if ENABLE_FEATURE_IPV6
+       /* Hoped libc can cope with IPv4 address there too.
+        * No such luck, glibc 2.4 segfaults even with IPv6,
+        * maybe I misunderstand how to make glibc use IPv6 addr?
+        * (uclibc 0.9.31+ should work) */
+       if (lsa->u.sa.sa_family == AF_INET6) {
+               // glibc neither SEGVs nor sends any dgrams with this
+               // (strace shows no socket ops):
+               //_res.nscount = 0;
+               _res._u._ext.nscount = 1;
+               /* store a pointer to part of malloc'ed lsa */
+               _res._u._ext.nsaddrs[0] = &lsa->u.sin6;
+               /* must not free(lsa)! */
+       }
+#endif
+}
+
+int nslookup_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int nslookup_main(int argc, char **argv)
+{
+       /* 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 (!argv[1] || argv[1][0] == '-' || argc > 3)
+               bb_show_usage();
+
+       /* initialize DNS structure _res used in printing the default
+        * name server and in the explicit name server option feature. */
+       res_init();
+       /* rfc2133 says this enables IPv6 lookups */
+       /* (but it also says "may be enabled in /etc/resolv.conf") */
+       /*_res.options |= RES_USE_INET6;*/
+
+       if (argv[2])
+               set_default_dns(argv[2]);
+
+       server_print();
+       return print_host(argv[1], "Name:");
+}
diff --git a/networking/ping.c b/networking/ping.c
new file mode 100644 (file)
index 0000000..f2a612f
--- /dev/null
@@ -0,0 +1,812 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Mini ping implementation for busybox
+ *
+ * Copyright (C) 1999 by Randolph Chung <tausq@debian.org>
+ *
+ * Adapted from the ping in netkit-base 0.10:
+ * 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.
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+/* from ping6.c:
+ * Copyright (C) 1999 by Randolph Chung <tausq@debian.org>
+ *
+ * This version of ping is adapted from the ping in netkit-base 0.10,
+ * which is:
+ *
+ * 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 <magick@linux-fan.com>
+ */
+
+#include <net/if.h>
+#include <netinet/ip_icmp.h>
+#include "libbb.h"
+
+#if ENABLE_PING6
+#include <netinet/icmp6.h>
+/* I see RENUMBERED constants in bits/in.h - !!?
+ * What a fuck is going on with libc? Is it a glibc joke? */
+#ifdef IPV6_2292HOPLIMIT
+#undef IPV6_HOPLIMIT
+#define IPV6_HOPLIMIT IPV6_2292HOPLIMIT
+#endif
+#endif
+
+enum {
+       DEFDATALEN = 56,
+       MAXIPLEN = 60,
+       MAXICMPLEN = 76,
+       MAXPACKET = 65468,
+       MAX_DUP_CHK = (8 * 128),
+       MAXWAIT = 10,
+       PINGINTERVAL = 1, /* 1 second */
+};
+
+/* 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;
+}
+
+#if !ENABLE_FEATURE_FANCY_PING
+
+/* simple version */
+
+static char *hostname;
+
+static void noresp(int ign UNUSED_PARAM)
+{
+       printf("No response from %s\n", hostname);
+       exit(EXIT_FAILURE);
+}
+
+static void ping4(len_and_sockaddr *lsa)
+{
+       struct sockaddr_in pingaddr;
+       struct icmp *pkt;
+       int pingsock, c;
+       char packet[DEFDATALEN + MAXIPLEN + MAXICMPLEN];
+
+       pingsock = create_icmp_socket();
+       pingaddr = lsa->u.sin;
+
+       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 = xsendto(pingsock, packet, DEFDATALEN + ICMP_MINLEN,
+                          (struct sockaddr *) &pingaddr, sizeof(pingaddr));
+
+       /* listen for replies */
+       while (1) {
+               struct sockaddr_in from;
+               socklen_t fromlen = sizeof(from);
+
+               c = recvfrom(pingsock, packet, sizeof(packet), 0,
+                               (struct sockaddr *) &from, &fromlen);
+               if (c < 0) {
+                       if (errno != EINTR)
+                               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);
+}
+
+#if ENABLE_PING6
+static void ping6(len_and_sockaddr *lsa)
+{
+       struct sockaddr_in6 pingaddr;
+       struct icmp6_hdr *pkt;
+       int pingsock, c;
+       int sockopt;
+       char packet[DEFDATALEN + MAXIPLEN + MAXICMPLEN];
+
+       pingsock = create_icmp6_socket();
+       pingaddr = lsa->u.sin6;
+
+       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, &sockopt, sizeof(sockopt));
+
+       c = xsendto(pingsock, packet, DEFDATALEN + sizeof (struct icmp6_hdr),
+                          (struct sockaddr *) &pingaddr, sizeof(pingaddr));
+
+       /* listen for replies */
+       while (1) {
+               struct sockaddr_in6 from;
+               socklen_t fromlen = sizeof(from);
+
+               c = recvfrom(pingsock, packet, sizeof(packet), 0,
+                               (struct sockaddr *) &from, &fromlen);
+               if (c < 0) {
+                       if (errno != EINTR)
+                               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);
+}
+#endif
+
+int ping_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int ping_main(int argc UNUSED_PARAM, char **argv)
+{
+       len_and_sockaddr *lsa;
+#if ENABLE_PING6
+       sa_family_t af = AF_UNSPEC;
+
+       while ((++argv)[0] && argv[0][0] == '-') {
+               if (argv[0][1] == '4') {
+                       af = AF_INET;
+                       continue;
+               }
+               if (argv[0][1] == '6') {
+                       af = AF_INET6;
+                       continue;
+               }
+               bb_show_usage();
+       }
+#else
+       argv++;
+#endif
+
+       hostname = *argv;
+       if (!hostname)
+               bb_show_usage();
+
+#if ENABLE_PING6
+       lsa = xhost_and_af2sockaddr(hostname, 0, af);
+#else
+       lsa = xhost_and_af2sockaddr(hostname, 0, AF_INET);
+#endif
+       /* Set timer _after_ DNS resolution */
+       signal(SIGALRM, noresp);
+       alarm(5); /* give the host 5000ms to respond */
+
+#if ENABLE_PING6
+       if (lsa->u.sa.sa_family == AF_INET6)
+               ping6(lsa);
+       else
+#endif
+               ping4(lsa);
+       printf("%s is alive!\n", hostname);
+       return EXIT_SUCCESS;
+}
+
+
+#else /* FEATURE_FANCY_PING */
+
+
+/* full(er) version */
+
+#define OPT_STRING ("qvc:s:w:W:I:4" USE_PING6("6"))
+enum {
+       OPT_QUIET = 1 << 0,
+       OPT_VERBOSE = 1 << 1,
+       OPT_c = 1 << 2,
+       OPT_s = 1 << 3,
+       OPT_w = 1 << 4,
+       OPT_W = 1 << 5,
+       OPT_I = 1 << 6,
+       OPT_IPV4 = 1 << 7,
+       OPT_IPV6 = (1 << 8) * ENABLE_PING6,
+};
+
+
+struct globals {
+       int pingsock;
+       int if_index;
+       char *str_I;
+       len_and_sockaddr *source_lsa;
+       unsigned datalen;
+       unsigned pingcount; /* must be int-sized */
+       unsigned long ntransmitted, nreceived, nrepeats;
+       uint16_t myid;
+       unsigned tmin, tmax; /* in us */
+       unsigned long long tsum; /* in us, sum of all times */
+       unsigned deadline;
+       unsigned timeout;
+       unsigned total_secs;
+       const char *hostname;
+       const char *dotted;
+       union {
+               struct sockaddr sa;
+               struct sockaddr_in sin;
+#if ENABLE_PING6
+               struct sockaddr_in6 sin6;
+#endif
+       } pingaddr;
+       char rcvd_tbl[MAX_DUP_CHK / 8];
+};
+#define G (*(struct globals*)&bb_common_bufsiz1)
+#define pingsock     (G.pingsock    )
+#define if_index     (G.if_index    )
+#define source_lsa   (G.source_lsa  )
+#define str_I        (G.str_I       )
+#define datalen      (G.datalen     )
+#define ntransmitted (G.ntransmitted)
+#define nreceived    (G.nreceived   )
+#define nrepeats     (G.nrepeats    )
+#define pingcount    (G.pingcount   )
+#define myid         (G.myid        )
+#define tmin         (G.tmin        )
+#define tmax         (G.tmax        )
+#define tsum         (G.tsum        )
+#define deadline     (G.deadline    )
+#define timeout      (G.timeout     )
+#define total_secs   (G.total_secs  )
+#define hostname     (G.hostname    )
+#define dotted       (G.dotted      )
+#define pingaddr     (G.pingaddr    )
+#define rcvd_tbl     (G.rcvd_tbl    )
+void BUG_ping_globals_too_big(void);
+#define INIT_G() do { \
+       if (sizeof(G) > COMMON_BUFSIZE) \
+               BUG_ping_globals_too_big(); \
+       pingsock = -1; \
+       datalen = DEFDATALEN; \
+       timeout = MAXWAIT; \
+       tmin = UINT_MAX; \
+} while (0)
+
+
+#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 print_stats_and_exit(int junk) NORETURN;
+static void print_stats_and_exit(int junk UNUSED_PARAM)
+{
+       signal(SIGINT, SIG_IGN);
+
+       printf("\n--- %s ping statistics ---\n", hostname);
+       printf("%lu packets transmitted, ", ntransmitted);
+       printf("%lu packets received, ", nreceived);
+       if (nrepeats)
+               printf("%lu duplicates, ", nrepeats);
+       if (ntransmitted)
+               ntransmitted = (ntransmitted - nreceived) * 100 / ntransmitted;
+       printf("%lu%% packet loss\n", ntransmitted);
+       if (tmin != UINT_MAX) {
+               unsigned tavg = tsum / (nreceived + nrepeats);
+               printf("round-trip min/avg/max = %u.%03u/%u.%03u/%u.%03u ms\n",
+                       tmin / 1000, tmin % 1000,
+                       tavg / 1000, tavg % 1000,
+                       tmax / 1000, tmax % 1000);
+       }
+       /* if condition is true, exit with 1 -- 'failure' */
+       exit(nreceived == 0 || (deadline && nreceived < pingcount));
+}
+
+static void sendping_tail(void (*sp)(int), const void *pkt, int size_pkt)
+{
+       int sz;
+
+       CLR((uint16_t)ntransmitted % MAX_DUP_CHK);
+       ntransmitted++;
+
+       /* sizeof(pingaddr) can be larger than real sa size, but I think
+        * it doesn't matter */
+       sz = xsendto(pingsock, pkt, size_pkt, &pingaddr.sa, sizeof(pingaddr));
+       if (sz != size_pkt)
+               bb_error_msg_and_die(bb_msg_write_error);
+
+       if (pingcount == 0 || deadline || ntransmitted < pingcount) {
+               /* Didn't send all pings yet - schedule next in 1s */
+               signal(SIGALRM, sp);
+               if (deadline) {
+                       total_secs += PINGINTERVAL;
+                       if (total_secs >= deadline)
+                               signal(SIGALRM, print_stats_and_exit);
+               }
+               alarm(PINGINTERVAL);
+       } else { /* -c NN, and all NN are sent (and no deadline) */
+               /* Wait for the last ping to come back.
+                * -W timeout: wait for a response in seconds.
+                * Affects only timeout in absense of any responses,
+                * otherwise ping waits for two RTTs. */
+               unsigned expire = timeout;
+
+               if (nreceived) {
+                       /* approx. 2*tmax, in seconds (2 RTT) */
+                       expire = tmax / (512*1024);
+                       if (expire == 0)
+                               expire = 1;
+               }
+               signal(SIGALRM, print_stats_and_exit);
+               alarm(expire);
+       }
+}
+
+static void sendping4(int junk UNUSED_PARAM)
+{
+       /* +4 reserves a place for timestamp, which may end up sitting
+        * *after* packet. Saves one if() */
+       struct icmp *pkt = alloca(datalen + ICMP_MINLEN + 4);
+
+       memset(pkt, 0, datalen + ICMP_MINLEN + 4);
+       pkt->icmp_type = ICMP_ECHO;
+       /*pkt->icmp_code = 0;*/
+       /*pkt->icmp_cksum = 0;*/
+       pkt->icmp_seq = htons(ntransmitted); /* don't ++ here, it can be a macro */
+       pkt->icmp_id = myid;
+
+       /* We don't do hton, because we will read it back on the same machine */
+       /*if (datalen >= 4)*/
+               *(uint32_t*)&pkt->icmp_dun = monotonic_us();
+
+       pkt->icmp_cksum = in_cksum((unsigned short *) pkt, datalen + ICMP_MINLEN);
+
+       sendping_tail(sendping4, pkt, datalen + ICMP_MINLEN);
+}
+#if ENABLE_PING6
+static void sendping6(int junk UNUSED_PARAM)
+{
+       struct icmp6_hdr *pkt = alloca(datalen + sizeof(struct icmp6_hdr) + 4);
+
+       memset(pkt, 0, datalen + sizeof(struct icmp6_hdr) + 4);
+       pkt->icmp6_type = ICMP6_ECHO_REQUEST;
+       /*pkt->icmp6_code = 0;*/
+       /*pkt->icmp6_cksum = 0;*/
+       pkt->icmp6_seq = htons(ntransmitted); /* don't ++ here, it can be a macro */
+       pkt->icmp6_id = myid;
+
+       /*if (datalen >= 4)*/
+               *(uint32_t*)(&pkt->icmp6_data8[4]) = monotonic_us();
+
+       sendping_tail(sendping6, pkt, datalen + sizeof(struct icmp6_hdr));
+}
+#endif
+
+static const 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";
+       }
+}
+#if ENABLE_PING6
+/* 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 const 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";
+       }
+}
+#endif
+
+static void unpack_tail(int sz, uint32_t *tp,
+               const char *from_str,
+               uint16_t recv_seq, int ttl)
+{
+       const char *dupmsg = " (DUP!)";
+       unsigned triptime = triptime; /* for gcc */
+
+       ++nreceived;
+
+       if (tp) {
+               /* (int32_t) cast is for hypothetical 64-bit unsigned */
+               /* (doesn't hurt 32-bit real-world anyway) */
+               triptime = (int32_t) ((uint32_t)monotonic_us() - *tp);
+               tsum += triptime;
+               if (triptime < tmin)
+                       tmin = triptime;
+               if (triptime > tmax)
+                       tmax = triptime;
+       }
+
+       if (TST(recv_seq % MAX_DUP_CHK)) {
+               ++nrepeats;
+               --nreceived;
+       } else {
+               SET(recv_seq % MAX_DUP_CHK);
+               dupmsg += 7;
+       }
+
+       if (option_mask32 & OPT_QUIET)
+               return;
+
+       printf("%d bytes from %s: seq=%u ttl=%d", sz,
+               from_str, recv_seq, ttl);
+       if (tp)
+               printf(" time=%u.%03u ms", triptime / 1000, triptime % 1000);
+       puts(dupmsg);
+       fflush(stdout);
+}
+static void unpack4(char *buf, int sz, struct sockaddr_in *from)
+{
+       struct icmp *icmppkt;
+       struct iphdr *iphdr;
+       int hlen;
+
+       /* 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) {
+               uint16_t recv_seq = ntohs(icmppkt->icmp_seq);
+               uint32_t *tp = NULL;
+
+               if (sz >= ICMP_MINLEN + sizeof(uint32_t))
+                       tp = (uint32_t *) icmppkt->icmp_data;
+               unpack_tail(sz, tp,
+                       inet_ntoa(*(struct in_addr *) &from->sin_addr.s_addr),
+                       recv_seq, iphdr->ttl);
+       } else if (icmppkt->icmp_type != ICMP_ECHO) {
+               bb_error_msg("warning: got ICMP %d (%s)",
+                               icmppkt->icmp_type,
+                               icmp_type_name(icmppkt->icmp_type));
+       }
+}
+#if ENABLE_PING6
+static void unpack6(char *packet, int sz, /*struct sockaddr_in6 *from,*/ int hoplimit)
+{
+       struct icmp6_hdr *icmppkt;
+       char buf[INET6_ADDRSTRLEN];
+
+       /* 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) {
+               uint16_t recv_seq = ntohs(icmppkt->icmp6_seq);
+               uint32_t *tp = NULL;
+
+               if (sz >= sizeof(struct icmp6_hdr) + sizeof(uint32_t))
+                       tp = (uint32_t *) &icmppkt->icmp6_data8[4];
+               unpack_tail(sz, tp,
+                       inet_ntop(AF_INET6, &pingaddr.sin6.sin6_addr,
+                                       buf, sizeof(buf)),
+                       recv_seq, hoplimit);
+       } 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));
+       }
+}
+#endif
+
+static void ping4(len_and_sockaddr *lsa)
+{
+       char packet[datalen + MAXIPLEN + MAXICMPLEN];
+       int sockopt;
+
+       pingsock = create_icmp_socket();
+       pingaddr.sin = lsa->u.sin;
+       if (source_lsa) {
+               if (setsockopt(pingsock, IPPROTO_IP, IP_MULTICAST_IF,
+                               &source_lsa->u.sa, source_lsa->len))
+                       bb_error_msg_and_die("can't set multicast source interface");
+               xbind(pingsock, &source_lsa->u.sa, source_lsa->len);
+       }
+       if (str_I)
+               setsockopt_bindtodevice(pingsock, str_I);
+
+       /* enable broadcast pings */
+       setsockopt_broadcast(pingsock);
+
+       /* set recv buf (needed if we can get lots of responses: flood ping,
+        * broadcast ping etc) */
+       sockopt = (datalen * 2) + 7 * 1024; /* giving it a bit of extra room */
+       setsockopt(pingsock, SOL_SOCKET, SO_RCVBUF, &sockopt, sizeof(sockopt));
+
+       signal(SIGINT, print_stats_and_exit);
+
+       /* start the ping's going ... */
+       sendping4(0);
+
+       /* listen for replies */
+       while (1) {
+               struct sockaddr_in from;
+               socklen_t fromlen = (socklen_t) sizeof(from);
+               int c;
+
+               c = recvfrom(pingsock, packet, sizeof(packet), 0,
+                               (struct sockaddr *) &from, &fromlen);
+               if (c < 0) {
+                       if (errno != EINTR)
+                               bb_perror_msg("recvfrom");
+                       continue;
+               }
+               unpack4(packet, c, &from);
+               if (pingcount && nreceived >= pingcount)
+                       break;
+       }
+}
+#if ENABLE_PING6
+extern int BUG_bad_offsetof_icmp6_cksum(void);
+static void ping6(len_and_sockaddr *lsa)
+{
+       char packet[datalen + MAXIPLEN + MAXICMPLEN];
+       int sockopt;
+       struct msghdr msg;
+       struct sockaddr_in6 from;
+       struct iovec iov;
+       char control_buf[CMSG_SPACE(36)];
+
+       pingsock = create_icmp6_socket();
+       pingaddr.sin6 = lsa->u.sin6;
+       /* untested whether "-I addr" really works for IPv6: */
+       if (source_lsa)
+               xbind(pingsock, &source_lsa->u.sa, source_lsa->len);
+       if (str_I)
+               setsockopt_bindtodevice(pingsock, str_I);
+
+#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 (needed if we can get lots of responses: flood ping,
+        * broadcast ping etc) */
+       sockopt = (datalen * 2) + 7 * 1024; /* giving it a bit of extra room */
+       setsockopt(pingsock, SOL_SOCKET, SO_RCVBUF, &sockopt, sizeof(sockopt));
+
+       sockopt = offsetof(struct icmp6_hdr, icmp6_cksum);
+       if (offsetof(struct icmp6_hdr, icmp6_cksum) != 2)
+               BUG_bad_offsetof_icmp6_cksum();
+       setsockopt(pingsock, SOL_RAW, IPV6_CHECKSUM, &sockopt, sizeof(sockopt));
+
+       /* request ttl info to be returned in ancillary data */
+       setsockopt(pingsock, SOL_IPV6, IPV6_HOPLIMIT, &const_int_1, sizeof(const_int_1));
+
+       if (if_index)
+               pingaddr.sin6.sin6_scope_id = if_index;
+
+       signal(SIGINT, print_stats_and_exit);
+
+       /* start the ping's going ... */
+       sendping6(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 *mp;
+               int hoplimit = -1;
+               msg.msg_controllen = sizeof(control_buf);
+
+               c = recvmsg(pingsock, &msg, 0);
+               if (c < 0) {
+                       if (errno != EINTR)
+                               bb_perror_msg("recvfrom");
+                       continue;
+               }
+               for (mp = CMSG_FIRSTHDR(&msg); mp; mp = CMSG_NXTHDR(&msg, mp)) {
+                       if (mp->cmsg_level == SOL_IPV6
+                        && mp->cmsg_type == IPV6_HOPLIMIT
+                        /* don't check len - we trust the kernel: */
+                        /* && mp->cmsg_len >= CMSG_LEN(sizeof(int)) */
+                       ) {
+                               hoplimit = *(int*)CMSG_DATA(mp);
+                       }
+               }
+               unpack6(packet, c, /*&from,*/ hoplimit);
+               if (pingcount && nreceived >= pingcount)
+                       break;
+       }
+}
+#endif
+
+static void ping(len_and_sockaddr *lsa)
+{
+       printf("PING %s (%s)", hostname, dotted);
+       if (source_lsa) {
+               printf(" from %s",
+                       xmalloc_sockaddr2dotted_noport(&source_lsa->u.sa));
+       }
+       printf(": %d data bytes\n", datalen);
+
+#if ENABLE_PING6
+       if (lsa->u.sa.sa_family == AF_INET6)
+               ping6(lsa);
+       else
+#endif
+               ping4(lsa);
+}
+
+int ping_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int ping_main(int argc UNUSED_PARAM, char **argv)
+{
+       len_and_sockaddr *lsa;
+       char *str_s;
+       int opt;
+
+       INIT_G();
+
+       /* exactly one argument needed; -v and -q don't mix; -c NUM, -w NUM, -W NUM */
+       opt_complementary = "=1:q--v:v--q:c+:w+:W+";
+       opt = getopt32(argv, OPT_STRING, &pingcount, &str_s, &deadline, &timeout, &str_I);
+       if (opt & OPT_s)
+               datalen = xatou16(str_s); // -s
+       if (opt & OPT_I) { // -I
+               if_index = if_nametoindex(str_I);
+               if (!if_index) {
+                       /* TODO: I'm not sure it takes IPv6 unless in [XX:XX..] format */
+                       source_lsa = xdotted2sockaddr(str_I, 0);
+                       str_I = NULL; /* don't try to bind to device later */
+               }
+       }
+       myid = (uint16_t) getpid();
+       hostname = argv[optind];
+#if ENABLE_PING6
+       {
+               sa_family_t af = AF_UNSPEC;
+               if (opt & OPT_IPV4)
+                       af = AF_INET;
+               if (opt & OPT_IPV6)
+                       af = AF_INET6;
+               lsa = xhost_and_af2sockaddr(hostname, 0, af);
+       }
+#else
+       lsa = xhost_and_af2sockaddr(hostname, 0, AF_INET);
+#endif
+
+       if (source_lsa && source_lsa->u.sa.sa_family != lsa->u.sa.sa_family)
+               /* leaking it here... */
+               source_lsa = NULL;
+
+       dotted = xmalloc_sockaddr2dotted_noport(&lsa->u.sa);
+       ping(lsa);
+       print_stats_and_exit(EXIT_SUCCESS);
+       /*return EXIT_SUCCESS;*/
+}
+#endif /* FEATURE_FANCY_PING */
+
+
+#if ENABLE_PING6
+int ping6_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int ping6_main(int argc UNUSED_PARAM, char **argv)
+{
+       argv[0] = (char*)"-6";
+       return ping_main(0 /* argc+1 - but it's unused anyway */,
+                       argv - 1);
+}
+#endif
+
+/* from ping6.c:
+ * 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. <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/networking/pscan.c b/networking/pscan.c
new file mode 100644 (file)
index 0000000..5fb6af0
--- /dev/null
@@ -0,0 +1,154 @@
+/*
+ * Pscan is a mini port scanner implementation for busybox
+ *
+ * Copyright 2007 Tito Ragusa <farmatito@tiscali.it>
+ *
+ * Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
+ */
+
+#include "libbb.h"
+
+/* debugging */
+#ifdef DEBUG_PSCAN
+#define DMSG(...) bb_error_msg(__VA_ARGS__)
+#define DERR(...) bb_perror_msg(__VA_ARGS__)
+#else
+#define DMSG(...) ((void)0)
+#define DERR(...) ((void)0)
+#endif
+
+static const char *port_name(unsigned port)
+{
+       struct servent *server;
+
+       server = getservbyport(htons(port), NULL);
+       if (server)
+               return server->s_name;
+       return "unknown";
+}
+
+/* We don't expect to see 1000+ seconds delay, unsigned is enough */
+#define MONOTONIC_US() ((unsigned)monotonic_us())
+
+int pscan_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int pscan_main(int argc UNUSED_PARAM, char **argv)
+{
+       const char *opt_max_port = "1024";      /* -P: default max port */
+       const char *opt_min_port = "1";         /* -p: default min port */
+       const char *opt_timeout = "5000";       /* -t: default timeout in msec */
+       /* We estimate rtt and wait rtt*4 before concluding that port is
+        * totally blocked. min rtt of 5 ms may be too low if you are
+        * scanning an Internet host behind saturated/traffic shaped link.
+        * Rule of thumb: with min_rtt of N msec, scanning 1000 ports
+        * will take N seconds at absolute minimum */
+       const char *opt_min_rtt = "5";          /* -T: default min rtt in msec */
+       const char *result_str;
+       len_and_sockaddr *lsap;
+       int s;
+       unsigned opt;
+       unsigned port, max_port, nports;
+       unsigned closed_ports = 0;
+       unsigned open_ports = 0;
+       /* all in usec */
+       unsigned timeout;
+       unsigned min_rtt;
+       unsigned rtt_4;
+       unsigned start, diff;
+
+       opt_complementary = "=1"; /* exactly one non-option */
+       opt = getopt32(argv, "cbp:P:t:T:", &opt_min_port, &opt_max_port, &opt_timeout, &opt_min_rtt);
+       argv += optind;
+       max_port = xatou_range(opt_max_port, 1, 65535);
+       port = xatou_range(opt_min_port, 1, max_port);
+       nports = max_port - port + 1;
+       min_rtt = xatou_range(opt_min_rtt, 1, INT_MAX/1000 / 4) * 1000;
+       timeout = xatou_range(opt_timeout, 1, INT_MAX/1000 / 4) * 1000;
+       /* Initial rtt is BIG: */
+       rtt_4 = timeout;
+
+       DMSG("min_rtt %u timeout %u", min_rtt, timeout);
+
+       lsap = xhost2sockaddr(*argv, port);
+       printf("Scanning %s ports %u to %u\n Port\tProto\tState\tService\n",
+                       *argv, port, max_port);
+
+       for (; port <= max_port; port++) {
+               DMSG("rtt %u", rtt_4);
+
+               /* The SOCK_STREAM socket type is implemented on the TCP/IP protocol. */
+               set_nport(lsap, htons(port));
+               s = xsocket(lsap->u.sa.sa_family, SOCK_STREAM, 0);
+               /* We need unblocking socket so we don't need to wait for ETIMEOUT. */
+               /* Nonblocking connect typically "fails" with errno == EINPROGRESS */
+               ndelay_on(s);
+
+               DMSG("connect to port %u", port);
+               result_str = NULL;
+               start = MONOTONIC_US();
+               if (connect(s, &lsap->u.sa, lsap->len) == 0) {
+                       /* Unlikely, for me even localhost fails :) */
+                       DMSG("connect succeeded");
+                       goto open;
+               }
+               /* Check for untypical errors... */
+               if (errno != EAGAIN && errno != EINPROGRESS
+                && errno != ECONNREFUSED
+               ) {
+                       bb_perror_nomsg_and_die();
+               }
+
+               diff = 0;
+               while (1) {
+                       if (errno == ECONNREFUSED) {
+                               if (opt & 1) /* -c: show closed too */
+                                       result_str = "closed";
+                               closed_ports++;
+                               break;
+                       }
+                       DERR("port %u errno %d @%u", port, errno, diff);
+
+                       if (diff > rtt_4) {
+                               if (opt & 2) /* -b: show blocked too */
+                                       result_str = "blocked";
+                               break;
+                       }
+                       /* Can sleep (much) longer than specified delay.
+                        * We check rtt BEFORE we usleep, otherwise
+                        * on localhost we'll have no writes done (!)
+                        * before we exceed (rather small) rtt */
+                       usleep(rtt_4/8);
+ open:
+                       diff = MONOTONIC_US() - start;
+                       DMSG("write to port %u @%u", port, diff - start);
+                       if (write(s, " ", 1) >= 0) { /* We were able to write to the socket */
+                               open_ports++;
+                               result_str = "open";
+                               break;
+                       }
+               }
+               DMSG("out of loop @%u", diff);
+               if (result_str)
+                       printf("%5u" "\t" "tcp" "\t" "%s" "\t" "%s" "\n",
+                                       port, result_str, port_name(port));
+
+               /* Estimate new rtt - we don't want to wait entire timeout
+                * for each port. *4 allows for rise in net delay.
+                * We increase rtt quickly (rtt_4*4), decrease slowly
+                * (diff is at least rtt_4/8, *4 == rtt_4/2)
+                * because we don't want to accidentally miss ports. */
+               rtt_4 = diff * 4;
+               if (rtt_4 < min_rtt)
+                       rtt_4 = min_rtt;
+               if (rtt_4 > timeout)
+                       rtt_4 = timeout;
+               /* Clean up */
+               close(s);
+       }
+       if (ENABLE_FEATURE_CLEAN_UP) free(lsap);
+
+       printf("%d closed, %d open, %d timed out (or blocked) ports\n",
+                                       closed_ports,
+                                       open_ports,
+                                       nports - (closed_ports + open_ports));
+       return EXIT_SUCCESS;
+}
diff --git a/networking/route.c b/networking/route.c
new file mode 100644 (file)
index 0000000..5d25408
--- /dev/null
@@ -0,0 +1,699 @@
+/* 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, <waltje@uwalt.nl.mugnet.org>
+ *              (derived from FvK's 'route.c     1.70    01/04/94')
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ *
+ *
+ * displayroute() code added by Vladimir N. Oleynik <dzo@simtreas.ru>
+ * adjustments by Larry Doolittle  <LRDoolittle@lbl.gov>
+ *
+ * IPV6 support added by Bart Visscher <magick@linux-fan.com>
+ */
+
+/* 2004/03/09  Manuel Novoa III <mjn3@codepoet.org>
+ *
+ * Rewritten to fix several bugs, add additional error checking, and
+ * remove ridiculous amounts of bloat.
+ */
+
+#include <net/route.h>
+#include <net/if.h>
+
+#include "libbb.h"
+#include "inet_common.h"
+
+
+#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[] ALIGN1 =
+       "\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[] ALIGN1 =
+       /* 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(&rt, 0, sizeof(rt));
+
+       {
+               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 = (char*)"lo";
+       }
+#endif
+
+       /* sanity checks.. */
+       if (mask_in_addr(rt)) {
+               uint32_t 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 & ~(uint32_t)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 (action == RTACTION_ADD)
+               xioctl(skfd, SIOCADDRT, &rt);
+       else
+               xioctl(skfd, SIOCDELRT, &rt);
+
+       if (ENABLE_FEATURE_CLEAN_UP) close(skfd);
+}
+
+#if ENABLE_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;
+                       cp = strchr(target, '/'); /* Yes... const to non is ok. */
+                       if (cp) {
+                               *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(&rt, 0, sizeof(rt));
+
+       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_IFNAMSIZ(ifr.ifr_name, devname);
+               xioctl(skfd, SIOGIFINDEX, &ifr);
+               rt.rtmsg_ifindex = ifr.ifr_ifindex;
+       }
+
+       /* Tell the kernel to accept this route. */
+       if (action == RTACTION_ADD)
+               xioctl(skfd, SIOCADDRT, &rt);
+       else
+               xioctl(skfd, SIOCDELRT, &rt);
+
+       if (ENABLE_FEATURE_CLEAN_UP) close(skfd);
+}
+#endif
+
+static const unsigned flagvals[] = { /* Must agree with flagchars[]. */
+       RTF_GATEWAY,
+       RTF_HOST,
+       RTF_REINSTATE,
+       RTF_DYNAMIC,
+       RTF_MODIFIED,
+#if ENABLE_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)
+
+/* Must agree with flagvals[]. */
+static const char flagchars[] ALIGN1 =
+       "GHRDM"
+#if ENABLE_FEATURE_IPV6
+       "DAC"
+#endif
+;
+
+static 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 FAST_FUNC bb_displayroutes(int noresolve, int netstatfmt)
+{
+       char devname[64], flags[16], *sdest, *sgw;
+       unsigned long d, g, m;
+       int flgs, ref, use, metric, mtu, win, ir;
+       struct sockaddr_in s_addr;
+       struct in_addr mask;
+
+       FILE *fp = xfopen_for_read("/proc/net/route");
+
+       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;
+               sdest = INET_rresolve(&s_addr, (noresolve | 0x8000), m); /* 'default' instead of '*' */
+               s_addr.sin_addr.s_addr = g;
+               sgw = INET_rresolve(&s_addr, (noresolve | 0x4000), m); /* Host instead of net */
+               mask.s_addr = m;
+               /* "%15.15s" truncates hostnames, do we really want that? */
+               printf("%-15.15s %-15.15s %-16s%-6s", sdest, sgw, inet_ntoa(mask), flags);
+               free(sdest);
+               free(sgw);
+               if (netstatfmt) {
+                       printf("%5d %-5d %6d %s\n", mtu, win, ir, devname);
+               } else {
+                       printf("%-6d %-2d %7d %s\n", metric, ref, use, devname);
+               }
+       }
+}
+
+#if ENABLE_FEATURE_IPV6
+
+static void INET6_displayroutes(void)
+{
+       char addr6[128], *naddr6;
+       /* 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_for_read("/proc/net/ipv6_route");
+
+       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;
+                       naddr6 = INET6_rresolve((struct sockaddr_in6 *) &snaddr6,
+                                                  0x0fff /* Apparently, upstream never resolves. */
+                                                  );
+
+                       if (!r) {                       /* 1st pass */
+                               snprintf(addr6, sizeof(addr6), "%s/%d", naddr6, prefix_len);
+                               r += 40;
+                               free(naddr6);
+                       } else {                        /* 2nd pass */
+                               /* Print the info. */
+                               printf("%-43s %-39s %-5s %-6d %-2d %7d %-8s\n",
+                                               addr6, naddr6, flags, metric, refcnt, use, iface);
+                               free(naddr6);
+                               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. */
+/* 2nd byte matches RTACTION_* code */
+static const char tbl_verb[] ALIGN1 =
+       "\006\001add\0"
+       "\006\002del\0"
+/*     "\011\002delete\0" */
+       "\010\002delete"  /* Since it's last, we can save a byte. */
+;
+
+int route_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int route_main(int argc UNUSED_PARAM, 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(argv, "A:ne", &family);
+
+       if ((opt & ROUTE_OPT_A) && strcmp(family, "inet") != 0) {
+#if ENABLE_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;
+#if ENABLE_FEATURE_IPV6
+               if (opt & ROUTE_OPT_INET6)
+                       INET6_displayroutes();
+               else
+#endif
+                       bb_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();
+       }
+
+#if ENABLE_FEATURE_IPV6
+       if (opt & ROUTE_OPT_INET6)
+               INET6_setroute(what, argv);
+       else
+#endif
+               INET_setroute(what, argv);
+
+       return EXIT_SUCCESS;
+}
diff --git a/networking/slattach.c b/networking/slattach.c
new file mode 100644 (file)
index 0000000..d3212bb
--- /dev/null
@@ -0,0 +1,245 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Stripped down version of net-tools for busybox.
+ *
+ * Author: Ignacio Garcia Perez (iggarpe at gmail dot com)
+ *
+ * License: GPLv2 or later, see LICENSE file in this tarball.
+ *
+ * There are some differences from the standard net-tools slattach:
+ *
+ * - The -l option is not supported.
+ *
+ * - The -F options allows disabling of RTS/CTS flow control.
+ */
+
+#include "libbb.h"
+#include "libiproute/utils.h" /* invarg() */
+
+struct globals {
+       int handle;
+       int saved_disc;
+       struct termios saved_state;
+};
+#define G (*(struct globals*)&bb_common_bufsiz1)
+#define handle       (G.handle      )
+#define saved_disc   (G.saved_disc  )
+#define saved_state  (G.saved_state )
+#define INIT_G() do { } while (0)
+
+
+/*
+ * Save tty state and line discipline
+ *
+ * It is fine here to bail out on errors, since we haven modified anything yet
+ */
+static void save_state(void)
+{
+       /* Save line status */
+       if (tcgetattr(handle, &saved_state) < 0)
+               bb_perror_msg_and_die("get state");
+
+       /* Save line discipline */
+       xioctl(handle, TIOCGETD, &saved_disc);
+}
+
+static int set_termios_state_or_warn(struct termios *state)
+{
+       int ret;
+
+       ret = tcsetattr(handle, TCSANOW, state);
+       if (ret < 0) {
+               bb_perror_msg("set state");
+               return 1; /* used as exitcode */
+       }
+       return 0;
+}
+
+/*
+ * Restore state and line discipline for ALL managed ttys
+ *
+ * Restoring ALL managed ttys is the only way to have a single
+ * hangup delay.
+ *
+ * Go on after errors: we want to restore as many controlled ttys
+ * as possible.
+ */
+static void restore_state_and_exit(int exitcode) NORETURN;
+static void restore_state_and_exit(int exitcode)
+{
+       struct termios state;
+
+       /* Restore line discipline */
+       if (ioctl_or_warn(handle, TIOCSETD, &saved_disc) < 0) {
+               exitcode = 1;
+       }
+
+       /* Hangup */
+       memcpy(&state, &saved_state, sizeof(state));
+       cfsetispeed(&state, B0);
+       cfsetospeed(&state, B0);
+       if (set_termios_state_or_warn(&state))
+               exitcode = 1;
+       sleep(1);
+
+       /* Restore line status */
+       if (set_termios_state_or_warn(&saved_state))
+               exit(EXIT_FAILURE);
+       if (ENABLE_FEATURE_CLEAN_UP)
+               close(handle);
+
+       exit(exitcode);
+}
+
+/*
+ * Set tty state, line discipline and encapsulation
+ */
+static void set_state(struct termios *state, int encap)
+{
+       int disc;
+
+       /* Set line status */
+       if (set_termios_state_or_warn(state))
+               goto bad;
+       /* Set line discliple (N_SLIP always) */
+       disc = N_SLIP;
+       if (ioctl_or_warn(handle, TIOCSETD, &disc) < 0) {
+               goto bad;
+       }
+
+       /* Set encapsulation (SLIP, CSLIP, etc) */
+       if (ioctl_or_warn(handle, SIOCSIFENCAP, &encap) < 0) {
+ bad:
+               restore_state_and_exit(EXIT_FAILURE);
+       }
+}
+
+static void sig_handler(int signo UNUSED_PARAM)
+{
+       restore_state_and_exit(EXIT_SUCCESS);
+}
+
+int slattach_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int slattach_main(int argc UNUSED_PARAM, char **argv)
+{
+       /* Line discipline code table */
+       static const char proto_names[] ALIGN1 =
+               "slip\0"        /* 0 */
+               "cslip\0"       /* 1 */
+               "slip6\0"       /* 2 */
+               "cslip6\0"      /* 3 */
+               "adaptive\0"    /* 8 */
+               ;
+
+       int i, encap, opt;
+       struct termios state;
+       const char *proto = "cslip";
+       const char *extcmd;                             /* Command to execute after hangup */
+       const char *baud_str;
+       int baud_code = -1;                             /* Line baud rate (system code) */
+
+       enum {
+               OPT_p_proto  = 1 << 0,
+               OPT_s_baud   = 1 << 1,
+               OPT_c_extcmd = 1 << 2,
+               OPT_e_quit   = 1 << 3,
+               OPT_h_watch  = 1 << 4,
+               OPT_m_nonraw = 1 << 5,
+               OPT_L_local  = 1 << 6,
+               OPT_F_noflow = 1 << 7
+       };
+
+       INIT_G();
+
+       /* Parse command line options */
+       opt = getopt32(argv, "p:s:c:ehmLF", &proto, &baud_str, &extcmd);
+       /*argc -= optind;*/
+       argv += optind;
+
+       if (!*argv)
+               bb_show_usage();
+
+       encap = index_in_strings(proto_names, proto);
+
+       if (encap < 0)
+               invarg(proto, "protocol");
+       if (encap > 3)
+               encap = 8;
+
+       /* We want to know if the baud rate is valid before we start touching the ttys */
+       if (opt & OPT_s_baud) {
+               baud_code = tty_value_to_baud(xatoi(baud_str));
+               if (baud_code < 0)
+                       invarg(baud_str, "baud rate");
+       }
+
+       /* Trap signals in order to restore tty states upon exit */
+       if (!(opt & OPT_e_quit)) {
+               bb_signals(0
+                       + (1 << SIGHUP)
+                       + (1 << SIGINT)
+                       + (1 << SIGQUIT)
+                       + (1 << SIGTERM)
+                       , sig_handler);
+       }
+
+       /* Open tty */
+       handle = open(*argv, O_RDWR | O_NDELAY);
+       if (handle < 0) {
+               char *buf = concat_path_file("/dev", *argv);
+               handle = xopen(buf, O_RDWR | O_NDELAY);
+               /* maybe if (ENABLE_FEATURE_CLEAN_UP) ?? */
+               free(buf);
+       }
+
+       /* Save current tty state */
+       save_state();
+
+       /* Configure tty */
+       memcpy(&state, &saved_state, sizeof(state));
+       if (!(opt & OPT_m_nonraw)) { /* raw not suppressed */
+               memset(&state.c_cc, 0, sizeof(state.c_cc));
+               state.c_cc[VMIN] = 1;
+               state.c_iflag = IGNBRK | IGNPAR;
+               state.c_oflag = 0;
+               state.c_lflag = 0;
+               state.c_cflag = CS8 | HUPCL | CREAD
+                             | ((opt & OPT_L_local) ? CLOCAL : 0)
+                             | ((opt & OPT_F_noflow) ? 0 : CRTSCTS);
+               cfsetispeed(&state, cfgetispeed(&saved_state));
+               cfsetospeed(&state, cfgetospeed(&saved_state));
+       }
+
+       if (opt & OPT_s_baud) {
+               cfsetispeed(&state, baud_code);
+               cfsetospeed(&state, baud_code);
+       }
+
+       set_state(&state, encap);
+
+       /* Exit now if option -e was passed */
+       if (opt & OPT_e_quit)
+               return 0;
+
+       /* If we're not requested to watch, just keep descriptor open
+        * until we are killed */
+       if (!(opt & OPT_h_watch))
+               while (1)
+                       sleep(24*60*60);
+
+       /* Watch line for hangup */
+       while (1) {
+               if (ioctl(handle, TIOCMGET, &i) < 0 || !(i & TIOCM_CAR))
+                       goto no_carrier;
+               sleep(15);
+       }
+
+ no_carrier:
+
+       /* Execute command on hangup */
+       if (opt & OPT_c_extcmd)
+               system(extcmd);
+
+       /* Restore states and exit */
+       restore_state_and_exit(EXIT_SUCCESS);
+}
diff --git a/networking/tc.c b/networking/tc.c
new file mode 100644 (file)
index 0000000..03f57f6
--- /dev/null
@@ -0,0 +1,543 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * tc.c                "tc" utility frontend.
+ *
+ * Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
+ *
+ * Authors:    Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ *
+ * Bernhard Reutner-Fischer adjusted for busybox
+ */
+
+#include "libbb.h"
+
+#include "libiproute/utils.h"
+#include "libiproute/ip_common.h"
+#include "libiproute/rt_names.h"
+#include <linux/pkt_sched.h> /* for the TC_H_* macros */
+
+#define parse_rtattr_nested(tb, max, rta) \
+       (parse_rtattr((tb), (max), RTA_DATA(rta), RTA_PAYLOAD(rta)))
+
+/* nullifies tb on error */
+#define __parse_rtattr_nested_compat(tb, max, rta, len) \
+       ({if ((RTA_PAYLOAD(rta) >= len) && \
+                (RTA_PAYLOAD(rta) >= RTA_ALIGN(len) + sizeof(struct rtattr))) { \
+                       rta = RTA_DATA(rta) + RTA_ALIGN(len); \
+                       parse_rtattr_nested(tb, max, rta); \
+         } else \
+                       memset(tb, 0, sizeof(struct rtattr *) * (max + 1)); \
+       })
+
+#define parse_rtattr_nested_compat(tb, max, rta, data, len) \
+       ({data = RTA_PAYLOAD(rta) >= len ? RTA_DATA(rta) : NULL; \
+       __parse_rtattr_nested_compat(tb, max, rta, len); })
+
+#define show_details (0) /* not implemented. Does anyone need it? */
+#define use_iec (0) /* not currently documented in the upstream manpage */
+
+
+struct globals {
+       int filter_ifindex;
+       __u32 filter_qdisc;
+       __u32 filter_parent;
+       __u32 filter_prio;
+       __u32 filter_proto;
+};
+
+#define G (*(struct globals*)&bb_common_bufsiz1)
+#define filter_ifindex (G.filter_ifindex)
+#define filter_qdisc (G.filter_qdisc)
+#define filter_parent (G.filter_parent)
+#define filter_prio (G.filter_prio)
+#define filter_proto (G.filter_proto)
+
+void BUG_tc_globals_too_big(void);
+#define INIT_G() do { \
+       if (sizeof(G) > COMMON_BUFSIZE) \
+               BUG_tc_globals_too_big(); \
+} while (0)
+
+/* Allocates a buffer containing the name of a class id.
+ * The caller must free the returned memory.  */
+static char* print_tc_classid(uint32_t cid)
+{
+#if 0 /* IMPOSSIBLE */
+       if (cid == TC_H_ROOT)
+               return xasprintf("root");
+       else
+#endif
+       if (cid == TC_H_UNSPEC)
+               return xasprintf("none");
+       else if (TC_H_MAJ(cid) == 0)
+               return xasprintf(":%x", TC_H_MIN(cid));
+       else if (TC_H_MIN(cid) == 0)
+               return xasprintf("%x:", TC_H_MAJ(cid)>>16);
+       else
+               return xasprintf("%x:%x", TC_H_MAJ(cid)>>16, TC_H_MIN(cid));
+}
+
+/* Get a qdisc handle.  Return 0 on success, !0 otherwise.  */
+static int get_qdisc_handle(__u32 *h, const char *str) {
+       __u32 maj;
+       char *p;
+
+       maj = TC_H_UNSPEC;
+       if (!strcmp(str, "none"))
+               goto ok;
+       maj = strtoul(str, &p, 16);
+       if (p == str)
+               return 1;
+       maj <<= 16;
+       if (*p != ':' && *p!=0)
+               return 1;
+ ok:
+       *h = maj;
+       return 0;
+}
+
+/* Get class ID.  Return 0 on success, !0 otherwise.  */
+static int get_tc_classid(__u32 *h, const char *str) {
+       __u32 maj, min;
+       char *p;
+
+       maj = TC_H_ROOT;
+       if (!strcmp(str, "root"))
+               goto ok;
+       maj = TC_H_UNSPEC;
+       if (!strcmp(str, "none"))
+               goto ok;
+       maj = strtoul(str, &p, 16);
+       if (p == str) {
+               if (*p != ':')
+                       return 1;
+               maj = 0;
+       }
+       if (*p == ':') {
+               if (maj >= (1<<16))
+                       return 1;
+               maj <<= 16;
+               str = p + 1;
+               min = strtoul(str, &p, 16);
+               if (*p != 0 || min >= (1<<16))
+                       return 1;
+               maj |= min;
+       } else if (*p != 0)
+               return 1;
+ ok:
+       *h = maj;
+       return 0;
+}
+
+static void print_rate(char *buf, int len, uint32_t rate)
+{
+       double tmp = (double)rate*8;
+
+       if (use_iec) {
+               if (tmp >= 1000.0*1024.0*1024.0)
+                       snprintf(buf, len, "%.0fMibit", tmp/1024.0*1024.0);
+               else if (tmp >= 1000.0*1024)
+                       snprintf(buf, len, "%.0fKibit", tmp/1024);
+               else
+                       snprintf(buf, len, "%.0fbit", tmp);
+       } else {
+               if (tmp >= 1000.0*1000000.0)
+                       snprintf(buf, len, "%.0fMbit", tmp/1000000.0);
+               else if (tmp >= 1000.0 * 1000.0)
+                       snprintf(buf, len, "%.0fKbit", tmp/1000.0);
+               else
+                       snprintf(buf, len, "%.0fbit",  tmp);
+       }
+}
+
+/* This is "pfifo_fast".  */
+static int prio_parse_opt(int argc, char **argv, struct nlmsghdr *n)
+{
+       return 0;
+}
+static int prio_print_opt(struct rtattr *opt)
+{
+       int i;
+       struct tc_prio_qopt *qopt;
+       struct rtattr *tb[TCA_PRIO_MAX+1];
+
+       if (opt == NULL)
+               return 0;
+       parse_rtattr_nested_compat(tb, TCA_PRIO_MAX, opt, qopt, sizeof(*qopt));
+       if (tb == NULL)
+               return 0;
+       printf("bands %u priomap ", qopt->bands);
+       for (i=0; i<=TC_PRIO_MAX; i++)
+               printf(" %d", qopt->priomap[i]);
+
+       if (tb[TCA_PRIO_MQ])
+               printf(" multiqueue: o%s ",
+                   *(unsigned char *)RTA_DATA(tb[TCA_PRIO_MQ]) ? "n" : "ff");
+
+       return 0;
+}
+
+/* Class Based Queue */
+static int cbq_parse_opt(int argc, char **argv, struct nlmsghdr *n)
+{
+       return 0;
+}
+static int cbq_print_opt(struct rtattr *opt)
+{
+       struct rtattr *tb[TCA_CBQ_MAX+1];
+       struct tc_ratespec *r = NULL;
+       struct tc_cbq_lssopt *lss = NULL;
+       struct tc_cbq_wrropt *wrr = NULL;
+       struct tc_cbq_fopt *fopt = NULL;
+       struct tc_cbq_ovl *ovl = NULL;
+       const char * const error = "CBQ: too short %s opt";
+       RESERVE_CONFIG_BUFFER(buf, 64);
+
+       if (opt == NULL)
+               goto done;
+       parse_rtattr_nested(tb, TCA_CBQ_MAX, opt);
+
+       if (tb[TCA_CBQ_RATE]) {
+               if (RTA_PAYLOAD(tb[TCA_CBQ_RATE]) < sizeof(*r))
+                       bb_error_msg(error, "rate");
+               else
+                       r = RTA_DATA(tb[TCA_CBQ_RATE]);
+       }
+       if (tb[TCA_CBQ_LSSOPT]) {
+               if (RTA_PAYLOAD(tb[TCA_CBQ_LSSOPT]) < sizeof(*lss))
+                       bb_error_msg(error, "lss");
+               else
+                       lss = RTA_DATA(tb[TCA_CBQ_LSSOPT]);
+       }
+       if (tb[TCA_CBQ_WRROPT]) {
+               if (RTA_PAYLOAD(tb[TCA_CBQ_WRROPT]) < sizeof(*wrr))
+                       bb_error_msg(error, "wrr");
+               else
+                       wrr = RTA_DATA(tb[TCA_CBQ_WRROPT]);
+       }
+       if (tb[TCA_CBQ_FOPT]) {
+               if (RTA_PAYLOAD(tb[TCA_CBQ_FOPT]) < sizeof(*fopt))
+                       bb_error_msg(error, "fopt");
+               else
+                       fopt = RTA_DATA(tb[TCA_CBQ_FOPT]);
+       }
+       if (tb[TCA_CBQ_OVL_STRATEGY]) {
+               if (RTA_PAYLOAD(tb[TCA_CBQ_OVL_STRATEGY]) < sizeof(*ovl))
+                       bb_error_msg("CBQ: too short overlimit strategy %u/%u",
+                               (unsigned) RTA_PAYLOAD(tb[TCA_CBQ_OVL_STRATEGY]),
+                               (unsigned) sizeof(*ovl));
+               else
+                       ovl = RTA_DATA(tb[TCA_CBQ_OVL_STRATEGY]);
+       }
+
+       if (r) {
+               print_rate(buf, sizeof(buf), r->rate);
+               printf("rate %s ", buf);
+               if (show_details) {
+                       printf("cell %ub ", 1<<r->cell_log);
+                       if (r->mpu)
+                               printf("mpu %ub ", r->mpu);
+                       if (r->overhead)
+                               printf("overhead %ub ", r->overhead);
+               }
+       }
+       if (lss && lss->flags) {
+               bool comma = false;
+               bb_putchar('(');
+               if (lss->flags&TCF_CBQ_LSS_BOUNDED) {
+                       printf("bounded");
+                       comma = true;
+               }
+               if (lss->flags&TCF_CBQ_LSS_ISOLATED) {
+                       if (comma)
+                               bb_putchar(',');
+                       printf("isolated");
+               }
+               printf(") ");
+       }
+       if (wrr) {
+               if (wrr->priority != TC_CBQ_MAXPRIO)
+                       printf("prio %u", wrr->priority);
+               else
+                       printf("prio no-transmit");
+               if (show_details) {
+                       printf("/%u ", wrr->cpriority);
+                       if (wrr->weight != 1) {
+                               print_rate(buf, sizeof(buf), wrr->weight);
+                               printf("weight %s ", buf);
+                       }
+                       if (wrr->allot)
+                               printf("allot %ub ", wrr->allot);
+               }
+       }
+ done:
+       RELEASE_CONFIG_BUFFER(buf);
+       return 0;
+}
+
+static int print_qdisc(const struct sockaddr_nl *who UNUSED_PARAM,
+                                               struct nlmsghdr *hdr, void *arg UNUSED_PARAM)
+{
+       struct tcmsg *msg = NLMSG_DATA(hdr);
+       int len = hdr->nlmsg_len;
+       struct rtattr * tb[TCA_MAX+1];
+       char *name;
+
+       if (hdr->nlmsg_type != RTM_NEWQDISC && hdr->nlmsg_type != RTM_DELQDISC) {
+               /* bb_error_msg("Not a qdisc"); */
+               return 0; /* ??? mimic upstream; should perhaps return -1 */
+       }
+       len -= NLMSG_LENGTH(sizeof(*msg));
+       if (len < 0) {
+               /* bb_error_msg("Wrong len %d", len); */
+               return -1;
+       }
+       /* not the desired interface? */
+       if (filter_ifindex && filter_ifindex != msg->tcm_ifindex)
+               return 0;
+       memset (tb, 0, sizeof(tb));
+       parse_rtattr(tb, TCA_MAX, TCA_RTA(msg), len);
+       if (tb[TCA_KIND] == NULL) {
+               /* bb_error_msg("%s: NULL kind", "qdisc"); */
+               return -1;
+       }
+       if (hdr->nlmsg_type == RTM_DELQDISC)
+               printf("deleted ");
+       name = (char*)RTA_DATA(tb[TCA_KIND]);
+       printf("qdisc %s %x: ", name, msg->tcm_handle>>16);
+       if (filter_ifindex == 0)
+               printf("dev %s ", ll_index_to_name(msg->tcm_ifindex));
+       if (msg->tcm_parent == TC_H_ROOT)
+               printf("root ");
+       else if (msg->tcm_parent) {
+               char *classid = print_tc_classid(msg->tcm_parent);
+               printf("parent %s ", classid);
+               if (ENABLE_FEATURE_CLEAN_UP)
+                       free(classid);
+       }
+       if (msg->tcm_info != 1)
+               printf("refcnt %d ", msg->tcm_info);
+       if (tb[TCA_OPTIONS]) {
+               static const char _q_[] ALIGN1 = "pfifo_fast\0""cbq\0";
+               int qqq = index_in_strings(_q_, name);
+               if (qqq == 0) { /* pfifo_fast aka prio */
+                       prio_print_opt(tb[TCA_OPTIONS]);
+               } else if (qqq == 1) { /* class based queueing */
+                       cbq_print_opt(tb[TCA_OPTIONS]);
+               } else
+                       bb_error_msg("unknown %s", name);
+       }
+       bb_putchar('\n');
+       return 0;
+}
+
+static int print_class(const struct sockaddr_nl *who UNUSED_PARAM,
+                                               struct nlmsghdr *hdr, void *arg UNUSED_PARAM)
+{
+       struct tcmsg *msg = NLMSG_DATA(hdr);
+       int len = hdr->nlmsg_len;
+       struct rtattr * tb[TCA_MAX+1];
+       char *name, *classid;
+
+       /*XXX Eventually factor out common code */
+
+       if (hdr->nlmsg_type != RTM_NEWTCLASS && hdr->nlmsg_type != RTM_DELTCLASS) {
+               /* bb_error_msg("Not a class"); */
+               return 0; /* ??? mimic upstream; should perhaps return -1 */
+       }
+       len -= NLMSG_LENGTH(sizeof(*msg));
+       if (len < 0) {
+               /* bb_error_msg("Wrong len %d", len); */
+               return -1;
+       }
+       /* not the desired interface? */
+       if (filter_qdisc && TC_H_MAJ(msg->tcm_handle^filter_qdisc))
+               return 0;
+       memset (tb, 0, sizeof(tb));
+       parse_rtattr(tb, TCA_MAX, TCA_RTA(msg), len);
+       if (tb[TCA_KIND] == NULL) {
+               /* bb_error_msg("%s: NULL kind", "class"); */
+               return -1;
+       }
+       if (hdr->nlmsg_type == RTM_DELTCLASS)
+               printf("deleted ");
+
+       name = (char*)RTA_DATA(tb[TCA_KIND]);
+       classid = !msg->tcm_handle ? NULL : print_tc_classid(
+                               filter_qdisc ? TC_H_MIN(msg->tcm_parent) : msg->tcm_parent);
+       printf ("class %s %s", name, classid);
+       if (ENABLE_FEATURE_CLEAN_UP)
+               free(classid);
+
+       if (filter_ifindex == 0)
+               printf("dev %s ", ll_index_to_name(msg->tcm_ifindex));
+       if (msg->tcm_parent == TC_H_ROOT)
+               printf("root ");
+       else if (msg->tcm_parent) {
+               classid = print_tc_classid(filter_qdisc ?
+                                                                  TC_H_MIN(msg->tcm_parent) : msg->tcm_parent);
+               printf("parent %s ", classid);
+               if (ENABLE_FEATURE_CLEAN_UP)
+                       free(classid);
+       }
+       if (msg->tcm_info)
+               printf("leaf %x ", msg->tcm_info >> 16);
+       /* Do that get_qdisc_kind(RTA_DATA(tb[TCA_KIND])).  */
+       if (tb[TCA_OPTIONS]) {
+               static const char _q_[] ALIGN1 = "pfifo_fast\0""cbq\0";
+               int qqq = index_in_strings(_q_, name);
+               if (qqq == 0) { /* pfifo_fast aka prio */
+                       /* nothing. */ /*prio_print_opt(tb[TCA_OPTIONS]);*/
+               } else if (qqq == 1) { /* class based queueing */
+                       /* cbq_print_copt() is identical to cbq_print_opt(). */
+                       cbq_print_opt(tb[TCA_OPTIONS]);
+               } else
+                       bb_error_msg("unknown %s", name);
+       }
+       bb_putchar('\n');
+
+       return 0;
+}
+
+static int print_filter(const struct sockaddr_nl *who UNUSED_PARAM,
+                                               struct nlmsghdr *hdr, void *arg UNUSED_PARAM)
+{
+       struct tcmsg *msg = NLMSG_DATA(hdr);
+       int len = hdr->nlmsg_len;
+       struct rtattr * tb[TCA_MAX+1];
+       return 0;
+}
+
+int tc_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int tc_main(int argc UNUSED_PARAM, char **argv)
+{
+       static const char objects[] ALIGN1 =
+               "qdisc\0""class\0""filter\0"
+               ;
+       enum { OBJ_qdisc = 0, OBJ_class, OBJ_filter };
+       static const char commands[] ALIGN1 =
+               "add\0""delete\0""change\0"
+               "link\0" /* only qdisc */
+               "replace\0"
+               "show\0""list\0"
+               ;
+       static const char args[] ALIGN1 =
+               "dev\0" /* qdisc, class, filter */
+               "root\0" /* class, filter */
+               "parent\0" /* class, filter */
+               "qdisc\0" /* class */
+               "handle\0" /* change: qdisc, class(classid) list: filter */
+               "classid\0" /* change: for class use "handle" */
+               "preference\0""priority\0""protocol\0" /* filter */
+               ;
+       enum { CMD_add = 0, CMD_del, CMD_change, CMD_link, CMD_replace, CMD_show };
+       enum { ARG_dev = 0, ARG_root, ARG_parent, ARG_qdisc,
+                       ARG_handle, ARG_classid, ARG_pref, ARG_prio, ARG_proto};
+       struct rtnl_handle rth;
+       struct tcmsg msg;
+       int ret, obj, cmd, arg;
+       char *dev = NULL;
+
+       INIT_G();
+
+       if (!*++argv)
+               bb_show_usage();
+       xrtnl_open(&rth);
+       ret = EXIT_SUCCESS;
+
+       obj = index_in_substrings(objects, *argv++);
+
+       if (obj < OBJ_qdisc)
+               bb_show_usage();
+       if (!*argv)
+               cmd = CMD_show; /* list is the default */
+       else {
+               cmd = index_in_substrings(commands, *argv);
+               if (cmd < 0)
+                       bb_error_msg_and_die(bb_msg_invalid_arg, *argv, applet_name);
+               argv++;
+       }
+       memset(&msg, 0, sizeof(msg));
+       msg.tcm_family = AF_UNSPEC;
+       ll_init_map(&rth);
+       while (*argv) {
+               arg = index_in_substrings(args, *argv);
+               if (arg == ARG_dev) {
+                       NEXT_ARG();
+                       if (dev)
+                               duparg2("dev", *argv);
+                       dev = *argv++;
+                       msg.tcm_ifindex = xll_name_to_index(dev);
+                       if (cmd >= CMD_show)
+                               filter_ifindex = msg.tcm_ifindex;
+               } else if ((arg == ARG_qdisc && obj == OBJ_class && cmd >= CMD_show)
+                                  || (arg == ARG_handle && obj == OBJ_qdisc
+                                          && cmd == CMD_change)) {
+                       NEXT_ARG();
+                       /* We don't care about duparg2("qdisc handle",*argv) for now */
+                       if (get_qdisc_handle(&filter_qdisc, *argv))
+                               invarg(*argv, "qdisc");
+               } else if (obj != OBJ_qdisc &&
+                                  (arg == ARG_root
+                                        || arg == ARG_parent
+                                        || (obj == OBJ_filter && arg >= ARG_pref))) {
+               } else {
+                       invarg(*argv, "command");
+               }
+               NEXT_ARG();
+               if (arg == ARG_root) {
+                       if (msg.tcm_parent)
+                               duparg("parent", *argv);
+                       msg.tcm_parent = TC_H_ROOT;
+                       if (obj == OBJ_filter)
+                               filter_parent = TC_H_ROOT;
+               } else if (arg == ARG_parent) {
+                       __u32 handle;
+                       if (msg.tcm_parent)
+                               duparg(*argv, "parent");
+                       if (get_tc_classid(&handle, *argv))
+                               invarg(*argv, "parent");
+                       msg.tcm_parent = handle;
+                       if (obj == OBJ_filter)
+                               filter_parent = handle;
+               } else if (arg == ARG_handle) { /* filter::list */
+                       if (msg.tcm_handle)
+                               duparg(*argv, "handle");
+                       /* reject LONG_MIN || LONG_MAX */
+                       /* TODO: for fw
+                          if ((slash = strchr(handle, '/')) != NULL)
+                                  *slash = '\0';
+                        */
+                       msg.tcm_handle = get_u32(*argv, "handle");
+                       /* if (slash) {if (get_u32(__u32 &mask, slash+1, NULL)) inv mask; addattr32(n, MAX_MSG, TCA_FW_MASK, mask); */
+               } else if (arg == ARG_classid && obj == OBJ_class && cmd == CMD_change){
+               } else if (arg == ARG_pref || arg == ARG_prio) { /* filter::list */
+                       if (filter_prio)
+                               duparg(*argv, "priority");
+                       filter_prio = get_u32(*argv, "priority");
+               } else if (arg == ARG_proto) { /* filter::list */
+                       __u16 tmp;
+                       if (filter_proto)
+                               duparg(*argv, "protocol");
+                       if (ll_proto_a2n(&tmp, *argv))
+                               invarg(*argv, "protocol");
+                       filter_proto = tmp;
+               }
+       }
+       if (cmd >= CMD_show) { /* show or list */
+               if (obj == OBJ_filter)
+                       msg.tcm_info = TC_H_MAKE(filter_prio<<16, filter_proto);
+               if (rtnl_dump_request(&rth, obj == OBJ_qdisc ? RTM_GETQDISC :
+                                               obj == OBJ_class ? RTM_GETTCLASS : RTM_GETTFILTER,
+                                               &msg, sizeof(msg)) < 0)
+                       bb_simple_perror_msg_and_die("cannot send dump request");
+
+               xrtnl_dump_filter(&rth, obj == OBJ_qdisc ? print_qdisc :
+                                               obj == OBJ_class ? print_class : print_filter,
+                                               NULL);
+       }
+       if (ENABLE_FEATURE_CLEAN_UP) {
+               rtnl_close(&rth);
+       }
+       return ret;
+}
diff --git a/networking/tcpudp.c b/networking/tcpudp.c
new file mode 100644 (file)
index 0000000..55a3e08
--- /dev/null
@@ -0,0 +1,607 @@
+/* Based on ipsvd utilities written by Gerrit Pape <pape@smarden.org>
+ * which are released into public domain by the author.
+ * Homepage: http://smarden.sunsite.dk/ipsvd/
+ *
+ * Copyright (C) 2007 Denys Vlasenko.
+ *
+ * Licensed under GPLv2, see file LICENSE in this tarball for details.
+ */
+
+/* Based on ipsvd-0.12.1. This tcpsvd accepts all options
+ * which are supported by one from ipsvd-0.12.1, but not all are
+ * functional. See help text at the end of this file for details.
+ *
+ * Code inside "#ifdef SSLSVD" is for sslsvd and is currently unused.
+ *
+ * Busybox version exports TCPLOCALADDR instead of
+ * TCPLOCALIP + TCPLOCALPORT pair. ADDR more closely matches reality
+ * (which is "struct sockaddr_XXX". Port is not a separate entity,
+ * it's just a part of (AF_INET[6]) sockaddr!).
+ *
+ * TCPORIGDSTADDR is Busybox-specific addition.
+ *
+ * udp server is hacked up by reusing TCP code. It has the following
+ * limitation inherent in Unix DGRAM sockets implementation:
+ * - local IP address is retrieved (using recvmsg voodoo) but
+ *   child's socket is not bound to it (bind cannot be called on
+ *   already bound socket). Thus it still can emit outgoing packets
+ *   with wrong source IP...
+ * - don't know how to retrieve ORIGDST for udp.
+ */
+
+#include "libbb.h"
+/* Wants <limits.h> etc, thus included after libbb.h: */
+#include <linux/types.h> /* for __be32 etc */
+#include <linux/netfilter_ipv4.h>
+
+// TODO: move into this file:
+#include "tcpudp_perhost.h"
+
+#ifdef SSLSVD
+#include "matrixSsl.h"
+#include "ssl_io.h"
+#endif
+
+struct globals {
+       unsigned verbose;
+       unsigned max_per_host;
+       unsigned cur_per_host;
+       unsigned cnum;
+       unsigned cmax;
+       char **env_cur;
+       char *env_var[1]; /* actually bigger */
+};
+#define G (*(struct globals*)&bb_common_bufsiz1)
+#define verbose      (G.verbose     )
+#define max_per_host (G.max_per_host)
+#define cur_per_host (G.cur_per_host)
+#define cnum         (G.cnum        )
+#define cmax         (G.cmax        )
+#define env_cur      (G.env_cur     )
+#define env_var      (G.env_var     )
+#define INIT_G() do { \
+       cmax = 30; \
+       env_cur = &env_var[0]; \
+} while (0)
+
+
+/* We have to be careful about leaking memory in repeated setenv's */
+static void xsetenv_plain(const char *n, const char *v)
+{
+       char *var = xasprintf("%s=%s", n, v);
+       *env_cur++ = var;
+       putenv(var);
+}
+
+static void xsetenv_proto(const char *proto, const char *n, const char *v)
+{
+       char *var = xasprintf("%s%s=%s", proto, n, v);
+       *env_cur++ = var;
+       putenv(var);
+}
+
+static void undo_xsetenv(void)
+{
+       char **pp = env_cur = &env_var[0];
+       while (*pp) {
+               char *var = *pp;
+               bb_unsetenv(var);
+               free(var);
+               *pp++ = NULL;
+       }
+}
+
+static void sig_term_handler(int sig)
+{
+       if (verbose)
+               bb_error_msg("got signal %u, exit", sig);
+       kill_myself_with_sig(sig);
+}
+
+/* Little bloated, but tries to give accurate info how child exited.
+ * Makes easier to spot segfaulting children etc... */
+static void print_waitstat(unsigned pid, int wstat)
+{
+       unsigned e = 0;
+       const char *cause = "?exit";
+
+       if (WIFEXITED(wstat)) {
+               cause++;
+               e = WEXITSTATUS(wstat);
+       } else if (WIFSIGNALED(wstat)) {
+               cause = "signal";
+               e = WTERMSIG(wstat);
+       }
+       bb_error_msg("end %d %s %d", pid, cause, e);
+}
+
+/* Must match getopt32 in main! */
+enum {
+       OPT_c = (1 << 0),
+       OPT_C = (1 << 1),
+       OPT_i = (1 << 2),
+       OPT_x = (1 << 3),
+       OPT_u = (1 << 4),
+       OPT_l = (1 << 5),
+       OPT_E = (1 << 6),
+       OPT_b = (1 << 7),
+       OPT_h = (1 << 8),
+       OPT_p = (1 << 9),
+       OPT_t = (1 << 10),
+       OPT_v = (1 << 11),
+       OPT_V = (1 << 12),
+       OPT_U = (1 << 13), /* from here: sslsvd only */
+       OPT_slash = (1 << 14),
+       OPT_Z = (1 << 15),
+       OPT_K = (1 << 16),
+};
+
+static void connection_status(void)
+{
+       /* "only 1 client max" desn't need this */
+       if (cmax > 1)
+               bb_error_msg("status %u/%u", cnum, cmax);
+}
+
+static void sig_child_handler(int sig UNUSED_PARAM)
+{
+       int wstat;
+       pid_t pid;
+
+       while ((pid = wait_any_nohang(&wstat)) > 0) {
+               if (max_per_host)
+                       ipsvd_perhost_remove(pid);
+               if (cnum)
+                       cnum--;
+               if (verbose)
+                       print_waitstat(pid, wstat);
+       }
+       if (verbose)
+               connection_status();
+}
+
+int tcpudpsvd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int tcpudpsvd_main(int argc UNUSED_PARAM, char **argv)
+{
+       char *str_C, *str_t;
+       char *user;
+       struct hcc *hccp;
+       const char *instructs;
+       char *msg_per_host = NULL;
+       unsigned len_per_host = len_per_host; /* gcc */
+#ifndef SSLSVD
+       struct bb_uidgid_t ugid;
+#endif
+       bool tcp;
+       uint16_t local_port;
+       char *preset_local_hostname = NULL;
+       char *remote_hostname = remote_hostname; /* for compiler */
+       char *remote_addr = remote_addr; /* for compiler */
+       len_and_sockaddr *lsa;
+       len_and_sockaddr local, remote;
+       socklen_t sa_len;
+       int pid;
+       int sock;
+       int conn;
+       unsigned backlog = 20;
+
+       INIT_G();
+
+       tcp = (applet_name[0] == 't');
+
+       /* 3+ args, -i at most once, -p implies -h, -v is counter, -b N, -c N */
+       opt_complementary = "-3:i--i:ph:vv:b+:c+";
+#ifdef SSLSVD
+       getopt32(argv, "+c:C:i:x:u:l:Eb:hpt:vU:/:Z:K:",
+               &cmax, &str_C, &instructs, &instructs, &user, &preset_local_hostname,
+               &backlog, &str_t, &ssluser, &root, &cert, &key, &verbose
+       );
+#else
+       /* "+": stop on first non-option */
+       getopt32(argv, "+c:C:i:x:u:l:Eb:hpt:v",
+               &cmax, &str_C, &instructs, &instructs, &user, &preset_local_hostname,
+               &backlog, &str_t, &verbose
+       );
+#endif
+       if (option_mask32 & OPT_C) { /* -C n[:message] */
+               max_per_host = bb_strtou(str_C, &str_C, 10);
+               if (str_C[0]) {
+                       if (str_C[0] != ':')
+                               bb_show_usage();
+                       msg_per_host = str_C + 1;
+                       len_per_host = strlen(msg_per_host);
+               }
+       }
+       if (max_per_host > cmax)
+               max_per_host = cmax;
+       if (option_mask32 & OPT_u) {
+               xget_uidgid(&ugid, user);
+       }
+#ifdef SSLSVD
+       if (option_mask32 & OPT_U) ssluser = optarg;
+       if (option_mask32 & OPT_slash) root = optarg;
+       if (option_mask32 & OPT_Z) cert = optarg;
+       if (option_mask32 & OPT_K) key = optarg;
+#endif
+       argv += optind;
+       if (!argv[0][0] || LONE_CHAR(argv[0], '0'))
+               argv[0] = (char*)"0.0.0.0";
+
+       /* Per-IP flood protection is not thought-out for UDP */
+       if (!tcp)
+               max_per_host = 0;
+
+       bb_sanitize_stdio(); /* fd# 0,1,2 must be opened */
+
+#ifdef SSLSVD
+       sslser = user;
+       client = 0;
+       if ((getuid() == 0) && !(option_mask32 & OPT_u)) {
+               xfunc_exitcode = 100;
+               bb_error_msg_and_die("-U ssluser must be set when running as root");
+       }
+       if (option_mask32 & OPT_u)
+               if (!uidgid_get(&sslugid, ssluser, 1)) {
+                       if (errno) {
+                               bb_perror_msg_and_die("can't get user/group: %s", ssluser);
+                       }
+                       bb_error_msg_and_die("unknown user/group %s", ssluser);
+               }
+       if (!cert) cert = "./cert.pem";
+       if (!key) key = cert;
+       if (matrixSslOpen() < 0)
+               fatal("cannot initialize ssl");
+       if (matrixSslReadKeys(&keys, cert, key, 0, ca) < 0) {
+               if (client)
+                       fatal("cannot read cert, key, or ca file");
+               fatal("cannot read cert or key file");
+       }
+       if (matrixSslNewSession(&ssl, keys, 0, SSL_FLAGS_SERVER) < 0)
+               fatal("cannot create ssl session");
+#endif
+
+       sig_block(SIGCHLD);
+       signal(SIGCHLD, sig_child_handler);
+       bb_signals(BB_FATAL_SIGS, sig_term_handler);
+       signal(SIGPIPE, SIG_IGN);
+
+       if (max_per_host)
+               ipsvd_perhost_init(cmax);
+
+       local_port = bb_lookup_port(argv[1], tcp ? "tcp" : "udp", 0);
+       lsa = xhost2sockaddr(argv[0], local_port);
+       argv += 2;
+
+       sock = xsocket(lsa->u.sa.sa_family, tcp ? SOCK_STREAM : SOCK_DGRAM, 0);
+       setsockopt_reuseaddr(sock);
+       sa_len = lsa->len; /* I presume sockaddr len stays the same */
+       xbind(sock, &lsa->u.sa, sa_len);
+       if (tcp)
+               xlisten(sock, backlog);
+       else /* udp: needed for recv_from_to to work: */
+               socket_want_pktinfo(sock);
+       /* ndelay_off(sock); - it is the default I think? */
+
+#ifndef SSLSVD
+       if (option_mask32 & OPT_u) {
+               /* drop permissions */
+               xsetgid(ugid.gid);
+               xsetuid(ugid.uid);
+       }
+#endif
+
+       if (verbose) {
+               char *addr = xmalloc_sockaddr2dotted(&lsa->u.sa);
+               bb_error_msg("listening on %s, starting", addr);
+               free(addr);
+#ifndef SSLSVD
+               if (option_mask32 & OPT_u)
+                       printf(", uid %u, gid %u",
+                               (unsigned)ugid.uid, (unsigned)ugid.gid);
+#endif
+       }
+
+       /* Main accept() loop */
+
+ again:
+       hccp = NULL;
+
+       while (cnum >= cmax)
+               wait_for_any_sig(); /* expecting SIGCHLD */
+
+       /* Accept a connection to fd #0 */
+ again1:
+       close(0);
+ again2:
+       sig_unblock(SIGCHLD);
+       local.len = remote.len = sa_len;
+       if (tcp) {
+               conn = accept(sock, &remote.u.sa, &remote.len);
+       } else {
+               /* In case recv_from_to won't be able to recover local addr.
+                * Also sets port - recv_from_to is unable to do it. */
+               local = *lsa;
+               conn = recv_from_to(sock, NULL, 0, MSG_PEEK,
+                               &remote.u.sa, &local.u.sa, sa_len);
+       }
+       sig_block(SIGCHLD);
+       if (conn < 0) {
+               if (errno != EINTR)
+                       bb_perror_msg(tcp ? "accept" : "recv");
+               goto again2;
+       }
+       xmove_fd(tcp ? conn : sock, 0);
+
+       if (max_per_host) {
+               /* Drop connection immediately if cur_per_host > max_per_host
+                * (minimizing load under SYN flood) */
+               remote_addr = xmalloc_sockaddr2dotted_noport(&remote.u.sa);
+               cur_per_host = ipsvd_perhost_add(remote_addr, max_per_host, &hccp);
+               if (cur_per_host > max_per_host) {
+                       /* ipsvd_perhost_add detected that max is exceeded
+                        * (and did not store ip in connection table) */
+                       free(remote_addr);
+                       if (msg_per_host) {
+                               /* don't block or test for errors */
+                               send(0, msg_per_host, len_per_host, MSG_DONTWAIT);
+                       }
+                       goto again1;
+               }
+               /* NB: remote_addr is not leaked, it is stored in conn table */
+       }
+
+       if (!tcp) {
+               /* Voodoo magic: making udp sockets each receive its own
+                * packets is not trivial, and I still not sure
+                * I do it 100% right.
+                * 1) we have to do it before fork()
+                * 2) order is important - is it right now? */
+
+               /* Open new non-connected UDP socket for further clients... */
+               sock = xsocket(lsa->u.sa.sa_family, SOCK_DGRAM, 0);
+               setsockopt_reuseaddr(sock);
+               /* Make plain write/send work for old socket by supplying default
+                * destination address. This also restricts incoming packets
+                * to ones coming from this remote IP. */
+               xconnect(0, &remote.u.sa, sa_len);
+       /* hole? at this point we have no wildcard udp socket...
+        * can this cause clients to get "port unreachable" icmp?
+        * Yup, time window is very small, but it exists (is it?) */
+               /* ..."open new socket", continued */
+               xbind(sock, &lsa->u.sa, sa_len);
+               socket_want_pktinfo(sock);
+
+               /* Doesn't work:
+                * we cannot replace fd #0 - we will lose pending packet
+                * which is already buffered for us! And we cannot use fd #1
+                * instead - it will "intercept" all following packets, but child
+                * does not expect data coming *from fd #1*! */
+#if 0
+               /* Make it so that local addr is fixed to localp->u.sa
+                * and we don't accidentally accept packets to other local IPs. */
+               /* NB: we possibly bind to the _very_ same_ address & port as the one
+                * already bound in parent! This seems to work in Linux.
+                * (otherwise we can move socket to fd #0 only if bind succeeds) */
+               close(0);
+               set_nport(localp, htons(local_port));
+               xmove_fd(xsocket(localp->u.sa.sa_family, SOCK_DGRAM, 0), 0);
+               setsockopt_reuseaddr(0); /* crucial */
+               xbind(0, &localp->u.sa, localp->len);
+#endif
+       }
+
+       pid = vfork();
+       if (pid == -1) {
+               bb_perror_msg("vfork");
+               goto again;
+       }
+
+       if (pid != 0) {
+               /* Parent */
+               cnum++;
+               if (verbose)
+                       connection_status();
+               if (hccp)
+                       hccp->pid = pid;
+               /* clean up changes done by vforked child */
+               undo_xsetenv();
+               goto again;
+       }
+
+       /* Child: prepare env, log, and exec prog */
+
+       /* Closing tcp listening socket */
+       if (tcp)
+               close(sock);
+
+       { /* vfork alert! every xmalloc in this block should be freed! */
+               char *local_hostname = local_hostname; /* for compiler */
+               char *local_addr = NULL;
+               char *free_me0 = NULL;
+               char *free_me1 = NULL;
+               char *free_me2 = NULL;
+
+               if (verbose || !(option_mask32 & OPT_E)) {
+                       if (!max_per_host) /* remote_addr is not yet known */
+                               free_me0 = remote_addr = xmalloc_sockaddr2dotted(&remote.u.sa);
+                       if (option_mask32 & OPT_h) {
+                               free_me1 = remote_hostname = xmalloc_sockaddr2host_noport(&remote.u.sa);
+                               if (!remote_hostname) {
+                                       bb_error_msg("cannot look up hostname for %s", remote_addr);
+                                       remote_hostname = remote_addr;
+                               }
+                       }
+                       /* Find out local IP peer connected to.
+                        * Errors ignored (I'm not paranoid enough to imagine kernel
+                        * which doesn't know local IP). */
+                       if (tcp)
+                               getsockname(0, &local.u.sa, &local.len);
+                       /* else: for UDP it is done earlier by parent */
+                       local_addr = xmalloc_sockaddr2dotted(&local.u.sa);
+                       if (option_mask32 & OPT_h) {
+                               local_hostname = preset_local_hostname;
+                               if (!local_hostname) {
+                                       free_me2 = local_hostname = xmalloc_sockaddr2host_noport(&local.u.sa);
+                                       if (!local_hostname)
+                                               bb_error_msg_and_die("cannot look up hostname for %s", local_addr);
+                               }
+                               /* else: local_hostname is not NULL, but is NOT malloced! */
+                       }
+               }
+               if (verbose) {
+                       pid = getpid();
+                       if (max_per_host) {
+                               bb_error_msg("concurrency %s %u/%u",
+                                       remote_addr,
+                                       cur_per_host, max_per_host);
+                       }
+                       bb_error_msg((option_mask32 & OPT_h)
+                               ? "start %u %s-%s (%s-%s)"
+                               : "start %u %s-%s",
+                               pid,
+                               local_addr, remote_addr,
+                               local_hostname, remote_hostname);
+               }
+
+               if (!(option_mask32 & OPT_E)) {
+                       /* setup ucspi env */
+                       const char *proto = tcp ? "TCP" : "UDP";
+
+                       /* Extract "original" destination addr:port
+                        * from Linux firewall. Useful when you redirect
+                        * an outbond connection to local handler, and it needs
+                        * to know where it originally tried to connect */
+                       if (tcp && getsockopt(0, SOL_IP, SO_ORIGINAL_DST, &local.u.sa, &local.len) == 0) {
+                               char *addr = xmalloc_sockaddr2dotted(&local.u.sa);
+                               xsetenv_plain("TCPORIGDSTADDR", addr);
+                               free(addr);
+                       }
+                       xsetenv_plain("PROTO", proto);
+                       xsetenv_proto(proto, "LOCALADDR", local_addr);
+                       xsetenv_proto(proto, "REMOTEADDR", remote_addr);
+                       if (option_mask32 & OPT_h) {
+                               xsetenv_proto(proto, "LOCALHOST", local_hostname);
+                               xsetenv_proto(proto, "REMOTEHOST", remote_hostname);
+                       }
+                       //compat? xsetenv_proto(proto, "REMOTEINFO", "");
+                       /* additional */
+                       if (cur_per_host > 0) /* can not be true for udp */
+                               xsetenv_plain("TCPCONCURRENCY", utoa(cur_per_host));
+               }
+               free(local_addr);
+               free(free_me0);
+               free(free_me1);
+               free(free_me2);
+       }
+
+       xdup2(0, 1);
+
+       signal(SIGPIPE, SIG_DFL); /* this one was SIG_IGNed */
+       /* Non-ignored signals revert to SIG_DFL on exec anyway */
+       /*signal(SIGCHLD, SIG_DFL);*/
+       sig_unblock(SIGCHLD);
+
+#ifdef SSLSVD
+       strcpy(id, utoa(pid));
+       ssl_io(0, argv);
+#else
+       BB_EXECVP(argv[0], argv);
+#endif
+       bb_perror_msg_and_die("exec '%s'", argv[0]);
+}
+
+/*
+tcpsvd [-hpEvv] [-c n] [-C n:msg] [-b n] [-u user] [-l name]
+       [-i dir|-x cdb] [ -t sec] host port prog
+
+tcpsvd creates a TCP/IP socket, binds it to the address host:port,
+and listens on the socket for incoming connections.
+
+On each incoming connection, tcpsvd conditionally runs a program,
+with standard input reading from the socket, and standard output
+writing to the socket, to handle this connection. tcpsvd keeps
+listening on the socket for new connections, and can handle
+multiple connections simultaneously.
+
+tcpsvd optionally checks for special instructions depending
+on the IP address or hostname of the client that initiated
+the connection, see ipsvd-instruct(5).
+
+host
+    host either is a hostname, or a dotted-decimal IP address,
+    or 0. If host is 0, tcpsvd accepts connections to any local
+    IP address.
+    * busybox accepts IPv6 addresses and host:port pairs too
+      In this case second parameter is ignored
+port
+    tcpsvd accepts connections to host:port. port may be a name
+    from /etc/services or a number.
+prog
+    prog consists of one or more arguments. For each connection,
+    tcpsvd normally runs prog, with file descriptor 0 reading from
+    the network, and file descriptor 1 writing to the network.
+    By default it also sets up TCP-related environment variables,
+    see tcp-environ(5)
+-i dir
+    read instructions for handling new connections from the instructions
+    directory dir. See ipsvd-instruct(5) for details.
+    * ignored by busyboxed version
+-x cdb
+    read instructions for handling new connections from the constant database
+    cdb. The constant database normally is created from an instructions
+    directory by running ipsvd-cdb(8).
+    * ignored by busyboxed version
+-t sec
+    timeout. This option only takes effect if the -i option is given.
+    While checking the instructions directory, check the time of last access
+    of the file that matches the clients address or hostname if any, discard
+    and remove the file if it wasn't accessed within the last sec seconds;
+    tcpsvd does not discard or remove a file if the user's write permission
+    is not set, for those files the timeout is disabled. Default is 0,
+    which means that the timeout is disabled.
+    * ignored by busyboxed version
+-l name
+    local hostname. Do not look up the local hostname in DNS, but use name
+    as hostname. This option must be set if tcpsvd listens on port 53
+    to avoid loops.
+-u user[:group]
+    drop permissions. Switch user ID to user's UID, and group ID to user's
+    primary GID after creating and binding to the socket. If user is followed
+    by a colon and a group name, the group ID is switched to the GID of group
+    instead. All supplementary groups are removed.
+-c n
+    concurrency. Handle up to n connections simultaneously. Default is 30.
+    If there are n connections active, tcpsvd defers acceptance of a new
+    connection until an active connection is closed.
+-C n[:msg]
+    per host concurrency. Allow only up to n connections from the same IP
+    address simultaneously. If there are n active connections from one IP
+    address, new incoming connections from this IP address are closed
+    immediately. If n is followed by :msg, the message msg is written
+    to the client if possible, before closing the connection. By default
+    msg is empty. See ipsvd-instruct(5) for supported escape sequences in msg.
+
+    For each accepted connection, the current per host concurrency is
+    available through the environment variable TCPCONCURRENCY. n and msg
+    can be overwritten by ipsvd(7) instructions, see ipsvd-instruct(5).
+    By default tcpsvd doesn't keep track of connections.
+-h
+    Look up the client's hostname in DNS.
+-p
+    paranoid. After looking up the client's hostname in DNS, look up the IP
+    addresses in DNS for that hostname, and forget about the hostname
+    if none of the addresses match the client's IP address. You should
+    set this option if you use hostname based instructions. The -p option
+    implies the -h option.
+    * ignored by busyboxed version
+-b n
+    backlog. Allow a backlog of approximately n TCP SYNs. On some systems n
+    is silently limited. Default is 20.
+-E
+    no special environment. Do not set up TCP-related environment variables.
+-v
+    verbose. Print verbose messsages to standard output.
+-vv
+    more verbose. Print more verbose messages to standard output.
+    * no difference between -v and -vv in busyboxed version
+*/
diff --git a/networking/tcpudp_perhost.c b/networking/tcpudp_perhost.c
new file mode 100644 (file)
index 0000000..3005f12
--- /dev/null
@@ -0,0 +1,65 @@
+/* Based on ipsvd utilities written by Gerrit Pape <pape@smarden.org>
+ * which are released into public domain by the author.
+ * Homepage: http://smarden.sunsite.dk/ipsvd/
+ *
+ * Copyright (C) 2007 Denys Vlasenko.
+ *
+ * Licensed under GPLv2, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.h"
+#include "tcpudp_perhost.h"
+
+static struct hcc *cc;
+static unsigned cclen;
+
+/* to be optimized */
+
+void ipsvd_perhost_init(unsigned c)
+{
+//     free(cc);
+       cc = xzalloc(c * sizeof(*cc));
+       cclen = c;
+}
+
+unsigned ipsvd_perhost_add(char *ip, unsigned maxconn, struct hcc **hccpp)
+{
+       unsigned i;
+       unsigned conn = 1;
+       int freepos = -1;
+
+       for (i = 0; i < cclen; ++i) {
+               if (!cc[i].ip) {
+                       freepos = i;
+                       continue;
+               }
+               if (strcmp(cc[i].ip, ip) == 0) {
+                       conn++;
+                       continue;
+               }
+       }
+       if (freepos == -1) return 0;
+       if (conn <= maxconn) {
+               cc[freepos].ip = ip;
+               *hccpp = &cc[freepos];
+       }
+       return conn;
+}
+
+void ipsvd_perhost_remove(int pid)
+{
+       unsigned i;
+       for (i = 0; i < cclen; ++i) {
+               if (cc[i].pid == pid) {
+                       free(cc[i].ip);
+                       cc[i].ip = NULL;
+                       cc[i].pid = 0;
+                       return;
+               }
+       }
+}
+
+//void ipsvd_perhost_free(void)
+//{
+//     free(cc);
+//}
diff --git a/networking/tcpudp_perhost.h b/networking/tcpudp_perhost.h
new file mode 100644 (file)
index 0000000..d370036
--- /dev/null
@@ -0,0 +1,33 @@
+/* Based on ipsvd utilities written by Gerrit Pape <pape@smarden.org>
+ * which are released into public domain by the author.
+ * Homepage: http://smarden.sunsite.dk/ipsvd/
+ *
+ * Copyright (C) 2007 Denys Vlasenko.
+ *
+ * Licensed under GPLv2, see file LICENSE in this tarball for details.
+ */
+
+PUSH_AND_SET_FUNCTION_VISIBILITY_TO_HIDDEN
+
+struct hcc {
+       char *ip;
+       int pid;
+};
+
+void ipsvd_perhost_init(unsigned);
+
+/* Returns number of already opened connects to this ips, including this one.
+ * ip should be a malloc'ed ptr.
+ * If return value is <= maxconn, ip is inserted into the table
+ * and pointer to table entry if stored in *hccpp
+ * (useful for storing pid later).
+ * Else ip is NOT inserted (you must take care of it - free() etc) */
+unsigned ipsvd_perhost_add(char *ip, unsigned maxconn, struct hcc **hccpp);
+
+/* Finds and frees element with pid */
+void ipsvd_perhost_remove(int pid);
+
+//unsigned ipsvd_perhost_setpid(int pid);
+//void ipsvd_perhost_free(void);
+
+POP_SAVED_FUNCTION_VISIBILITY
diff --git a/networking/telnet.c b/networking/telnet.c
new file mode 100644 (file)
index 0000000..cc99425
--- /dev/null
@@ -0,0 +1,650 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * telnet implementation for busybox
+ *
+ * Author: Tomi Ollila <too@iki.fi>
+ * 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 <andersen@codepoet.org>
+ * Modified 2001/05/07 to add ability to pass TTYPE to remote host by Jim McQuillan
+ * <jam@ltsp.org>
+ * Modified 2004/02/11 to add ability to pass the USER variable to remote host
+ * by Fernando Silveira <swrh@gmx.net>
+ *
+ */
+
+#include <arpa/telnet.h>
+#include <netinet/in.h>
+#include "libbb.h"
+
+#ifdef DOTRACE
+#define TRACE(x, y) do { if (x) printf y; } while (0)
+#else
+#define TRACE(x, y)
+#endif
+
+enum {
+       DATABUFSIZE = 128,
+       IACBUFSIZE  = 128,
+
+       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,
+};
+
+typedef unsigned char byte;
+
+enum { netfd = 3 };
+
+struct globals {
+       int     iaclen; /* could even use byte, but it's a loss on x86 */
+       byte    telstate; /* telnet negotiation state from network input */
+       byte    telwish;  /* DO, DONT, WILL, WONT */
+       byte    charmode;
+       byte    telflags;
+       byte    do_termios;
+#if ENABLE_FEATURE_TELNET_TTYPE
+       char    *ttype;
+#endif
+#if ENABLE_FEATURE_TELNET_AUTOLOGIN
+       const char *autologin;
+#endif
+#if ENABLE_FEATURE_AUTOWIDTH
+       unsigned win_width, win_height;
+#endif
+       /* same buffer used both for network and console read/write */
+       char    buf[DATABUFSIZE];
+       /* buffer to handle telnet negotiations */
+       char    iacbuf[IACBUFSIZE];
+       struct termios termios_def;
+       struct termios termios_raw;
+};
+#define G (*(struct globals*)&bb_common_bufsiz1)
+void BUG_telnet_globals_too_big(void);
+#define INIT_G() do { \
+       if (sizeof(G) > COMMON_BUFSIZE) \
+               BUG_telnet_globals_too_big(); \
+       /* memset(&G, 0, sizeof G); - already is */ \
+} while (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);
+
+static void iac_flush(void)
+{
+       write(netfd, G.iacbuf, G.iaclen);
+       G.iaclen = 0;
+}
+
+#define write_str(fd, str) write(fd, str, sizeof(str) - 1)
+
+static void doexit(int ev) NORETURN;
+static void doexit(int ev)
+{
+       cookmode();
+       exit(ev);
+}
+
+static void con_escape(void)
+{
+       char b;
+
+       if (bb_got_signal) /* came from line mode... go raw */
+               rawmode();
+
+       write_str(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(STDIN_FILENO, &b, 1) <= 0)
+               doexit(EXIT_FAILURE);
+
+       switch (b) {
+       case 'l':
+               if (!bb_got_signal) {
+                       do_linemode();
+                       goto ret;
+               }
+               break;
+       case 'c':
+               if (bb_got_signal) {
+                       will_charmode();
+                       goto ret;
+               }
+               break;
+       case 'z':
+               cookmode();
+               kill(0, SIGTSTP);
+               rawmode();
+               break;
+       case 'e':
+               doexit(EXIT_SUCCESS);
+       }
+
+       write_str(1, "continuing...\r\n");
+
+       if (bb_got_signal)
+               cookmode();
+ ret:
+       bb_got_signal = 0;
+
+}
+
+static void handle_net_output(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 - why do you have to make 'many write()s'?
+        *      I don't understand.
+        * So I implemented it. It's really useful for me. I hope that
+        * other people will find it interesting too.
+        */
+
+       int i, j;
+       byte *p = (byte*)G.buf;
+       byte outbuf[4*DATABUFSIZE];
+
+       for (i = len, j = 0; i > 0; i--, p++) {
+               if (*p == 0x1d) {
+                       con_escape();
+                       return;
+               }
+               outbuf[j++] = *p;
+               if (*p == 0xff)
+                       outbuf[j++] = 0xff;
+               else if (*p == 0x0d)
+                       outbuf[j++] = 0x00;
+       }
+       if (j > 0)
+               write(netfd, outbuf, j);
+}
+
+static void handle_net_input(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;
+                       }
+                       continue;
+               }
+               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)
+                       iac_flush();
+               if (G.telstate == TS_0)
+                       G.telstate = 0;
+               len = cstart;
+       }
+
+       if (len)
+               write(STDOUT_FILENO, G.buf, len);
+}
+
+static void put_iac(int c)
+{
+       G.iacbuf[G.iaclen++] = c;
+}
+
+static void put_iac2(byte wwdd, byte c)
+{
+       if (G.iaclen + 3 > IACBUFSIZE)
+               iac_flush();
+
+       put_iac(IAC);
+       put_iac(wwdd);
+       put_iac(c);
+}
+
+#if ENABLE_FEATURE_TELNET_TTYPE
+static void put_iac_subopt(byte c, char *str)
+{
+       int len = strlen(str) + 6;   // ( 2 + 1 + 1 + strlen + 2 )
+
+       if (G.iaclen + len > IACBUFSIZE)
+               iac_flush();
+
+       put_iac(IAC);
+       put_iac(SB);
+       put_iac(c);
+       put_iac(0);
+
+       while (*str)
+               put_iac(*str++);
+
+       put_iac(IAC);
+       put_iac(SE);
+}
+#endif
+
+#if ENABLE_FEATURE_TELNET_AUTOLOGIN
+static void put_iac_subopt_autologin(void)
+{
+       int len = strlen(G.autologin) + 6;      // (2 + 1 + 1 + strlen + 2)
+       const char *user = "USER";
+
+       if (G.iaclen + len > IACBUFSIZE)
+               iac_flush();
+
+       put_iac(IAC);
+       put_iac(SB);
+       put_iac(TELOPT_NEW_ENVIRON);
+       put_iac(TELQUAL_IS);
+       put_iac(NEW_ENV_VAR);
+
+       while (*user)
+               put_iac(*user++);
+
+       put_iac(NEW_ENV_VALUE);
+
+       while (*G.autologin)
+               put_iac(*G.autologin++);
+
+       put_iac(IAC);
+       put_iac(SE);
+}
+#endif
+
+#if ENABLE_FEATURE_AUTOWIDTH
+static void put_iac_naws(byte c, int x, int y)
+{
+       if (G.iaclen + 9 > IACBUFSIZE)
+               iac_flush();
+
+       put_iac(IAC);
+       put_iac(SB);
+       put_iac(c);
+
+       put_iac((x >> 8) & 0xff);
+       put_iac(x & 0xff);
+       put_iac((y >> 8) & 0xff);
+       put_iac(y & 0xff);
+
+       put_iac(IAC);
+       put_iac(SE);
+}
+#endif
+
+static char const escapecharis[] ALIGN1 = "\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();
+
+       put_iac2(DO, TELOPT_ECHO);
+       put_iac2(DO, TELOPT_SGA);
+       iac_flush();
+}
+
+static void do_linemode(void)
+{
+       G.charmode = CHM_TRY;
+       G.telflags &= ~(UF_ECHO | UF_SGA);
+       setConMode();
+
+       put_iac2(DONT, TELOPT_ECHO);
+       put_iac2(DONT, TELOPT_SGA);
+       iac_flush();
+}
+
+static void to_notsup(char c)
+{
+       if (G.telwish == WILL)
+               put_iac2(DONT, c);
+       else if (G.telwish == DO)
+               put_iac2(WONT, c);
+}
+
+static void to_echo(void)
+{
+       /* if server requests ECHO, don't agree */
+       if (G.telwish == DO) {
+               put_iac2(WONT, TELOPT_ECHO);
+               return;
+       }
+       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)
+               put_iac2(DO, TELOPT_ECHO);
+       else
+               put_iac2(DONT, TELOPT_ECHO);
+
+       setConMode();
+       write_str(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;
+
+       G.telflags ^= UF_SGA; /* toggle */
+       if (G.telflags & UF_SGA)
+               put_iac2(DO, TELOPT_SGA);
+       else
+               put_iac2(DONT, TELOPT_SGA);
+}
+
+#if ENABLE_FEATURE_TELNET_TTYPE
+static void to_ttype(void)
+{
+       /* Tell server we will (or won't) do TTYPE */
+
+       if (G.ttype)
+               put_iac2(WILL, TELOPT_TTYPE);
+       else
+               put_iac2(WONT, TELOPT_TTYPE);
+}
+#endif
+
+#if ENABLE_FEATURE_TELNET_AUTOLOGIN
+static void to_new_environ(void)
+{
+       /* Tell server we will (or will not) do AUTOLOGIN */
+
+       if (G.autologin)
+               put_iac2(WILL, TELOPT_NEW_ENVIRON);
+       else
+               put_iac2(WONT, TELOPT_NEW_ENVIRON);
+}
+#endif
+
+#if ENABLE_FEATURE_AUTOWIDTH
+static void to_naws(void)
+{
+       /* Tell server we will do NAWS */
+       put_iac2(WILL, TELOPT_NAWS);
+}
+#endif
+
+static void telopt(byte c)
+{
+       switch (c) {
+       case TELOPT_ECHO:
+               to_echo(); break;
+       case TELOPT_SGA:
+               to_sga(); break;
+#if ENABLE_FEATURE_TELNET_TTYPE
+       case TELOPT_TTYPE:
+               to_ttype(); break;
+#endif
+#if ENABLE_FEATURE_TELNET_AUTOLOGIN
+       case TELOPT_NEW_ENVIRON:
+               to_new_environ(); break;
+#endif
+#if ENABLE_FEATURE_AUTOWIDTH
+       case TELOPT_NAWS:
+               to_naws();
+               put_iac_naws(c, G.win_width, G.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;
+#if ENABLE_FEATURE_TELNET_TTYPE
+               else
+               if (c == TELOPT_TTYPE)
+                       put_iac_subopt(TELOPT_TTYPE, G.ttype);
+#endif
+#if ENABLE_FEATURE_TELNET_AUTOLOGIN
+               else
+               if (c == TELOPT_NEW_ENVIRON)
+                       put_iac_subopt_autologin();
+#endif
+               break;
+       case TS_SUB2:
+               if (c == SE)
+                       return TRUE;
+               G.telstate = TS_SUB1;
+               /* break; */
+       }
+       return FALSE;
+}
+
+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);
+}
+
+/* poll gives smaller (-70 bytes) code */
+#define USE_POLL 1
+
+int telnet_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int telnet_main(int argc UNUSED_PARAM, char **argv)
+{
+       char *host;
+       int port;
+       int len;
+#ifdef USE_POLL
+       struct pollfd ufds[2];
+#else
+       fd_set readfds;
+       int maxfd;
+#endif
+
+       INIT_G();
+
+#if ENABLE_FEATURE_AUTOWIDTH
+       get_terminal_width_height(0, &G.win_width, &G.win_height);
+#endif
+
+#if ENABLE_FEATURE_TELNET_TTYPE
+       G.ttype = getenv("TERM");
+#endif
+
+       if (tcgetattr(0, &G.termios_def) >= 0) {
+               G.do_termios = 1;
+               G.termios_raw = G.termios_def;
+               cfmakeraw(&G.termios_raw);
+       }
+
+#if ENABLE_FEATURE_TELNET_AUTOLOGIN
+       if (1 & getopt32(argv, "al:", &G.autologin))
+               G.autologin = getenv("USER");
+       argv += optind;
+#else
+       argv++;
+#endif
+       if (!*argv)
+               bb_show_usage();
+       host = *argv++;
+       port = bb_lookup_port(*argv ? *argv++ : "telnet", "tcp", 23);
+       if (*argv) /* extra params?? */
+               bb_show_usage();
+
+       xmove_fd(create_and_connect_stream_or_die(host, port), netfd);
+
+       setsockopt(netfd, SOL_SOCKET, SO_KEEPALIVE, &const_int_1, sizeof(const_int_1));
+
+       signal(SIGINT, record_signo);
+
+#ifdef USE_POLL
+       ufds[0].fd = 0; ufds[1].fd = netfd;
+       ufds[0].events = ufds[1].events = POLLIN;
+#else
+       FD_ZERO(&readfds);
+       FD_SET(STDIN_FILENO, &readfds);
+       FD_SET(netfd, &readfds);
+       maxfd = 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 (bb_got_signal)
+                               con_escape();
+                       else
+                               sleep(1);
+                       break;
+               default:
+
+#ifdef USE_POLL
+                       if (ufds[0].revents) /* well, should check POLLIN, but ... */
+#else
+                       if (FD_ISSET(STDIN_FILENO, &rfds))
+#endif
+                       {
+                               len = read(STDIN_FILENO, G.buf, DATABUFSIZE);
+                               if (len <= 0)
+                                       doexit(EXIT_SUCCESS);
+                               TRACE(0, ("Read con: %d\n", len));
+                               handle_net_output(len);
+                       }
+
+#ifdef USE_POLL
+                       if (ufds[1].revents) /* well, should check POLLIN, but ... */
+#else
+                       if (FD_ISSET(netfd, &rfds))
+#endif
+                       {
+                               len = read(netfd, G.buf, DATABUFSIZE);
+                               if (len <= 0) {
+                                       write_str(1, "Connection closed by foreign host\r\n");
+                                       doexit(EXIT_FAILURE);
+                               }
+                               TRACE(0, ("Read netfd (%d): %d\n", netfd, len));
+                               handle_net_input(len);
+                       }
+               }
+       } /* while (1) */
+}
diff --git a/networking/telnetd.c b/networking/telnetd.c
new file mode 100644 (file)
index 0000000..4c5ea3a
--- /dev/null
@@ -0,0 +1,680 @@
+/* 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 <dzo@simtreas.ru> 2001
+ *     Set process group corrections, initial busybox port
+ */
+
+#define DEBUG 0
+
+#include "libbb.h"
+#include <syslog.h>
+
+#if DEBUG
+#define TELCMDS
+#define TELOPTS
+#endif
+#include <arpa/telnet.h>
+
+/* Structure that describes a session */
+struct tsession {
+       struct tsession *next;
+       pid_t shell_pid;
+       int sockfd_read, sockfd_write, ptyfd;
+
+       /* two circular buffers */
+       /*char *buf1, *buf2;*/
+/*#define TS_BUF1 ts->buf1*/
+/*#define TS_BUF2 TS_BUF2*/
+#define TS_BUF1 ((unsigned char*)(ts + 1))
+#define TS_BUF2 (((unsigned char*)(ts + 1)) + BUFSIZE)
+       int rdidx1, wridx1, size1;
+       int rdidx2, wridx2, size2;
+};
+
+/* Two buffers are directly after tsession in malloced memory.
+ * Make whole thing fit in 4k */
+enum { BUFSIZE = (4 * 1024 - sizeof(struct tsession)) / 2 };
+
+
+/* Globals */
+static int maxfd;
+static struct tsession *sessions;
+static const char *loginpath = "/bin/login";
+static const char *issuefile = "/etc/issue.net";
+
+
+/*
+   Remove all IAC's from buf1 (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 buf.
+
+   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.
+
+   CR-LF ->'s CR mapping is also done here, for convenience.
+
+   NB: may fail to remove iacs which wrap around buffer!
+ */
+static unsigned char *
+remove_iacs(struct tsession *ts, int *pnum_totty)
+{
+       unsigned char *ptr0 = TS_BUF1 + ts->wridx1;
+       unsigned char *ptr = ptr0;
+       unsigned char *totty = ptr;
+       unsigned char *end = ptr + MIN(BUFSIZE - ts->wridx1, ts->size1);
+       int num_totty;
+
+       while (ptr < end) {
+               if (*ptr != IAC) {
+                       char c = *ptr;
+
+                       *totty++ = c;
+                       ptr++;
+                       /* We map \r\n ==> \r for pragmatic reasons.
+                        * Many client implementations send \r\n when
+                        * the user hits the CarriageReturn key.
+                        */
+                       if (c == '\r' && ptr < end && (*ptr == '\n' || *ptr == '\0'))
+                               ptr++;
+                       continue;
+               }
+
+               if ((ptr+1) >= end)
+                       break;
+               if (ptr[1] == NOP) { /* Ignore? (putty keepalive, etc.) */
+                       ptr += 2;
+                       continue;
+               }
+               if (ptr[1] == IAC) { /* Literal IAC? (emacs M-DEL) */
+                       *totty++ = ptr[1];
+                       ptr += 2;
+                       continue;
+               }
+
+               /*
+                * 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
+                */
+               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;
+                       continue;
+               }
+               /* 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;
+       }
+
+       num_totty = totty - ptr0;
+       *pnum_totty = num_totty;
+       /* the difference between ptr and totty is number of iacs
+          we removed from the stream. Adjust buf1 accordingly. */
+       if ((ptr - totty) == 0) /* 99.999% of cases */
+               return ptr0;
+       ts->wridx1 += ptr - totty;
+       ts->size1 -= ptr - totty;
+       /* move chars meant for the terminal towards the end of the buffer */
+       return memmove(ptr - num_totty, ptr0, num_totty);
+}
+
+/*
+ * Converting single IAC into double on output
+ */
+static size_t iac_safe_write(int fd, const char *buf, size_t count)
+{
+       const char *IACptr;
+       size_t wr, rc, total;
+
+       total = 0;
+       while (1) {
+               if (count == 0)
+                       return total;
+               if (*buf == (char)IAC) {
+                       static const char IACIAC[] ALIGN1 = { IAC, IAC };
+                       rc = safe_write(fd, IACIAC, 2);
+                       if (rc != 2)
+                               break;
+                       buf++;
+                       total++;
+                       count--;
+                       continue;
+               }
+               /* count != 0, *buf != IAC */
+               IACptr = memchr(buf, IAC, count);
+               wr = count;
+               if (IACptr)
+                       wr = IACptr - buf;
+               rc = safe_write(fd, buf, wr);
+               if (rc != wr)
+                       break;
+               buf += rc;
+               total += rc;
+               count -= rc;
+       }
+       /* here: rc - result of last short write */
+       if ((ssize_t)rc < 0) { /* error? */
+               if (total == 0)
+                       return rc;
+               rc = 0;
+       }
+       return total + rc;
+}
+
+/* Must match getopt32 string */
+enum {
+       OPT_WATCHCHILD = (1 << 2), /* -K */
+       OPT_INETD      = (1 << 3) * ENABLE_FEATURE_TELNETD_STANDALONE, /* -i */
+       OPT_PORT       = (1 << 4) * ENABLE_FEATURE_TELNETD_STANDALONE, /* -p */
+       OPT_FOREGROUND = (1 << 6) * ENABLE_FEATURE_TELNETD_STANDALONE, /* -F */
+};
+
+static struct tsession *
+make_new_session(
+               USE_FEATURE_TELNETD_STANDALONE(int master_fd, int sock)
+               SKIP_FEATURE_TELNETD_STANDALONE(void)
+) {
+       const char *login_argv[2];
+       struct termios termbuf;
+       int fd, pid;
+       char tty_name[GETPTY_BUFSIZE];
+       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 = xgetpty(tty_name);
+       if (fd > maxfd)
+               maxfd = fd;
+       ts->ptyfd = fd;
+       ndelay_on(fd);
+#if ENABLE_FEATURE_TELNETD_STANDALONE
+       ts->sockfd_read = sock;
+       /* SO_KEEPALIVE by popular demand */
+       setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, &const_int_1, sizeof(const_int_1));
+       ndelay_on(sock);
+       if (!sock) { /* We are called with fd 0 - we are in inetd mode */
+               sock++; /* so use fd 1 for output */
+               ndelay_on(sock);
+       }
+       ts->sockfd_write = sock;
+       if (sock > maxfd)
+               maxfd = sock;
+#else
+       /* SO_KEEPALIVE by popular demand */
+       setsockopt(0, SOL_SOCKET, SO_KEEPALIVE, &const_int_1, sizeof(const_int_1));
+       /* ts->sockfd_read = 0; - done by xzalloc */
+       ts->sockfd_write = 1;
+       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. */
+       {
+               static const char iacs_to_send[] ALIGN1 = {
+                       IAC, DO, TELOPT_ECHO,
+                       IAC, DO, TELOPT_NAWS,
+               /* This requires telnetd.ctrlSQ.patch (incomplete) */
+               /*      IAC, DO, TELOPT_LFLOW, */
+                       IAC, WILL, TELOPT_ECHO,
+                       IAC, WILL, TELOPT_SGA
+               };
+               /* This confuses iac_safe_write(), it will try to duplicate
+                * each IAC... */
+               //memcpy(TS_BUF2, iacs_to_send, sizeof(iacs_to_send));
+               //ts->rdidx2 = sizeof(iacs_to_send);
+               //ts->size2 = sizeof(iacs_to_send);
+               /* So just stuff it into TCP stream! (no error check...) */
+#if ENABLE_FEATURE_TELNETD_STANDALONE
+               safe_write(sock, iacs_to_send, sizeof(iacs_to_send));
+#else
+               safe_write(1, iacs_to_send, sizeof(iacs_to_send));
+#endif
+               /*ts->rdidx2 = 0; - xzalloc did it */
+               /*ts->size2 = 0;*/
+       }
+
+       fflush(NULL); /* flush all streams */
+       pid = vfork(); /* NOMMU-friendly */
+       if (pid < 0) {
+               free(ts);
+               close(fd);
+               /* sock will be closed by caller */
+               bb_perror_msg("vfork");
+               return NULL;
+       }
+       if (pid > 0) {
+               /* Parent */
+               ts->shell_pid = pid;
+               return ts;
+       }
+
+       /* Child */
+       /* Careful - we are after vfork! */
+
+       /* Restore default signal handling ASAP */
+       bb_signals((1 << SIGCHLD) + (1 << SIGPIPE), SIG_DFL);
+
+#if ENABLE_FEATURE_TELNETD_STANDALONE
+       if (!(option_mask32 & OPT_INETD)) {
+               struct tsession *tp = sessions;
+               while (tp) {
+                       close(tp->ptyfd);
+                       close(tp->sockfd_read);
+                       /* sockfd_write == sockfd_read for standalone telnetd */
+                       /*close(tp->sockfd_write);*/
+                       tp = tp->next;
+               }
+       }
+#endif
+
+       /* Make new session and process group */
+       setsid();
+
+       close(fd);
+#if ENABLE_FEATURE_TELNETD_STANDALONE
+       close(sock);
+       if (master_fd >= 0)
+               close(master_fd);
+#endif
+
+       /* Open the child's side of the tty. */
+       /* NB: setsid() disconnects from any previous ctty's. Therefore
+        * we must open child's side of the tty AFTER setsid! */
+       close(0);
+       xopen(tty_name, O_RDWR); /* becomes our ctty */
+       xdup2(0, 1);
+       xdup2(0, 2);
+       tcsetpgrp(0, getpid()); /* switch this tty's process group to us */
+
+       /* 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_stdin_TCSANOW(&termbuf);
+
+       /* Uses FILE-based I/O to stdout, but does fflush(stdout),
+        * so should be safe with vfork.
+        * I fear, though, that some users will have ridiculously big
+        * issue files, and they may block writing to fd 1,
+        * (parent is supposed to read it, but parent waits
+        * for vforked child to exec!) */
+       print_login_issue(issuefile, tty_name);
+
+       /* Exec shell / login / whatever */
+       login_argv[0] = loginpath;
+       login_argv[1] = NULL;
+       /* exec busybox applet (if PREFER_APPLETS=y), if that fails,
+        * exec external program */
+       BB_EXECVP(loginpath, (char **)login_argv);
+       /* _exit is safer with vfork, and we shouldn't send message
+        * to remote clients anyway */
+       _exit(EXIT_FAILURE); /*bb_perror_msg_and_die("execv %s", loginpath);*/
+}
+
+#if ENABLE_FEATURE_TELNETD_STANDALONE
+
+static void
+free_session(struct tsession *ts)
+{
+       struct tsession *t = sessions;
+
+       if (option_mask32 & OPT_INETD)
+               exit(EXIT_SUCCESS);
+
+       /* 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;
+       }
+
+#if 0
+       /* It was said that "normal" telnetd just closes ptyfd,
+        * doesn't send SIGKILL. When we close ptyfd,
+        * kernel sends SIGHUP to processes having slave side opened. */
+       kill(ts->shell_pid, SIGKILL);
+       waitpid(ts->shell_pid, NULL, 0);
+#endif
+       close(ts->ptyfd);
+       close(ts->sockfd_read);
+       /* We do not need to close(ts->sockfd_write), it's the same
+        * as sockfd_read unless we are in inetd mode. But in inetd mode
+        * we do not reach this */
+       free(ts);
+
+       /* Scan all sessions and find new maxfd */
+       maxfd = 0;
+       ts = sessions;
+       while (ts) {
+               if (maxfd < ts->ptyfd)
+                       maxfd = ts->ptyfd;
+               if (maxfd < ts->sockfd_read)
+                       maxfd = ts->sockfd_read;
+#if 0
+               /* Again, sockfd_write == sockfd_read here */
+               if (maxfd < ts->sockfd_write)
+                       maxfd = ts->sockfd_write;
+#endif
+               ts = ts->next;
+       }
+}
+
+#else /* !FEATURE_TELNETD_STANDALONE */
+
+/* Used in main() only, thus "return 0" actually is exit(EXIT_SUCCESS). */
+#define free_session(ts) return 0
+
+#endif
+
+static void handle_sigchld(int sig UNUSED_PARAM)
+{
+       pid_t pid;
+       struct tsession *ts;
+
+       /* Looping: more than one child may have exited */
+       while (1) {
+               pid = wait_any_nohang(NULL);
+               if (pid <= 0)
+                       break;
+               ts = sessions;
+               while (ts) {
+                       if (ts->shell_pid == pid) {
+                               ts->shell_pid = -1;
+                               break;
+                       }
+                       ts = ts->next;
+               }
+       }
+}
+
+int telnetd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int telnetd_main(int argc UNUSED_PARAM, char **argv)
+{
+       fd_set rdfdset, wrfdset;
+       unsigned opt;
+       int count;
+       struct tsession *ts;
+#if ENABLE_FEATURE_TELNETD_STANDALONE
+#define IS_INETD (opt & OPT_INETD)
+       int master_fd = master_fd; /* be happy, gcc */
+       unsigned portnbr = 23;
+       char *opt_bindaddr = NULL;
+       char *opt_portnbr;
+#else
+       enum {
+               IS_INETD = 1,
+               master_fd = -1,
+               portnbr = 23,
+       };
+#endif
+       /* Even if !STANDALONE, we accept (and ignore) -i, thus people
+        * don't need to guess whether it's ok to pass -i to us */
+       opt = getopt32(argv, "f:l:Ki" USE_FEATURE_TELNETD_STANDALONE("p:b:F"),
+                       &issuefile, &loginpath
+                       USE_FEATURE_TELNETD_STANDALONE(, &opt_portnbr, &opt_bindaddr));
+       if (!IS_INETD /*&& !re_execed*/) {
+               /* inform that we start in standalone mode?
+                * May be useful when people forget to give -i */
+               /*bb_error_msg("listening for connections");*/
+               if (!(opt & OPT_FOREGROUND)) {
+                       /* DAEMON_CHDIR_ROOT was giving inconsistent
+                        * behavior with/without -F, -i */
+                       bb_daemonize_or_rexec(0 /*was DAEMON_CHDIR_ROOT*/, argv);
+               }
+       }
+       /* Redirect log to syslog early, if needed */
+       if (IS_INETD || !(opt & OPT_FOREGROUND)) {
+               openlog(applet_name, LOG_PID, LOG_DAEMON);
+               logmode = LOGMODE_SYSLOG;
+       }
+       USE_FEATURE_TELNETD_STANDALONE(
+               if (opt & OPT_PORT)
+                       portnbr = xatou16(opt_portnbr);
+       );
+
+       /* Used to check access(loginpath, X_OK) here. Pointless.
+        * exec will do this for us for free later. */
+
+#if ENABLE_FEATURE_TELNETD_STANDALONE
+       if (IS_INETD) {
+               sessions = make_new_session(-1, 0);
+               if (!sessions) /* pty opening or vfork problem, exit */
+                       return 1; /* make_new_session prints error message */
+       } else {
+               master_fd = create_and_bind_stream_or_die(opt_bindaddr, portnbr);
+               xlisten(master_fd, 1);
+       }
+#else
+       sessions = make_new_session();
+       if (!sessions) /* pty opening or vfork problem, exit */
+               return 1; /* make_new_session prints error message */
+#endif
+
+       /* We don't want to die if just one session is broken */
+       signal(SIGPIPE, SIG_IGN);
+
+       if (opt & OPT_WATCHCHILD)
+               signal(SIGCHLD, handle_sigchld);
+       else /* prevent dead children from becoming zombies */
+               signal(SIGCHLD, SIG_IGN);
+
+/*
+   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--      +----------+
+
+   size1: "how many bytes are buffered for pty between rdidx1 and wridx1?"
+   size2: "how many bytes are buffered for socket between rdidx2 and wridx2?"
+
+   Each session has got two buffers. Buffers are circular. If sizeN == 0,
+   buffer is empty. If sizeN == BUFSIZE, buffer is full. In both these cases
+   rdidxN == wridxN.
+*/
+ again:
+       FD_ZERO(&rdfdset);
+       FD_ZERO(&wrfdset);
+
+       /* Select on the master socket, all telnet sockets and their
+        * ptys if there is room in their session buffers.
+        * NB: scalability problem: we recalculate entire bitmap
+        * before each select. Can be a problem with 500+ connections. */
+       ts = sessions;
+       while (ts) {
+               struct tsession *next = ts->next; /* in case we free ts. */
+               if (ts->shell_pid == -1) {
+                       /* Child died and we detected that */
+                       free_session(ts);
+               } else {
+                       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 = next;
+       }
+       if (!IS_INETD) {
+               FD_SET(master_fd, &rdfdset);
+               /* This is needed because free_session() does not
+                * take master_fd into account when it finds new
+                * maxfd among remaining fd's */
+               if (master_fd > maxfd)
+                       maxfd = master_fd;
+       }
+
+       count = select(maxfd + 1, &rdfdset, &wrfdset, NULL, NULL);
+       if (count < 0)
+               goto again; /* EINTR or ENOMEM */
+
+#if ENABLE_FEATURE_TELNETD_STANDALONE
+       /* First check for and accept new sessions. */
+       if (!IS_INETD && FD_ISSET(master_fd, &rdfdset)) {
+               int fd;
+               struct tsession *new_ts;
+
+               fd = accept(master_fd, NULL, NULL);
+               if (fd < 0)
+                       goto again;
+               /* Create a new session and link it into our active list */
+               new_ts = make_new_session(master_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;
+                       unsigned char *ptr;
+                       /* Write to pty from buffer 1. */
+                       ptr = remove_iacs(ts, &num_totty);
+                       count = safe_write(ts->ptyfd, ptr, num_totty);
+                       if (count < 0) {
+                               if (errno == EAGAIN)
+                                       goto skip1;
+                               goto kill_session;
+                       }
+                       ts->size1 -= count;
+                       ts->wridx1 += count;
+                       if (ts->wridx1 >= BUFSIZE) /* actually == BUFSIZE */
+                               ts->wridx1 = 0;
+               }
+ skip1:
+               if (/*ts->size2 &&*/ FD_ISSET(ts->sockfd_write, &wrfdset)) {
+                       /* Write to socket from buffer 2. */
+                       count = MIN(BUFSIZE - ts->wridx2, ts->size2);
+                       count = iac_safe_write(ts->sockfd_write, (void*)(TS_BUF2 + ts->wridx2), count);
+                       if (count < 0) {
+                               if (errno == EAGAIN)
+                                       goto skip2;
+                               goto kill_session;
+                       }
+                       ts->size2 -= count;
+                       ts->wridx2 += count;
+                       if (ts->wridx2 >= BUFSIZE) /* actually == BUFSIZE */
+                               ts->wridx2 = 0;
+               }
+ skip2:
+               /* Should not be needed, but... remove_iacs is actually buggy
+                * (it cannot process iacs which wrap around buffer's end)!
+                * Since properly fixing it requires writing bigger code,
+                * we rely instead on this code making it virtually impossible
+                * to have wrapped iac (people don't type at 2k/second).
+                * It also allows for bigger reads in common case. */
+               if (ts->size1 == 0) {
+                       ts->rdidx1 = 0;
+                       ts->wridx1 = 0;
+               }
+               if (ts->size2 == 0) {
+                       ts->rdidx2 = 0;
+                       ts->wridx2 = 0;
+               }
+
+               if (/*ts->size1 < BUFSIZE &&*/ FD_ISSET(ts->sockfd_read, &rdfdset)) {
+                       /* Read from socket to buffer 1. */
+                       count = MIN(BUFSIZE - ts->rdidx1, BUFSIZE - ts->size1);
+                       count = safe_read(ts->sockfd_read, TS_BUF1 + ts->rdidx1, count);
+                       if (count <= 0) {
+                               if (count < 0 && errno == EAGAIN)
+                                       goto skip3;
+                               goto kill_session;
+                       }
+                       /* Ignore trailing NUL if it is there */
+                       if (!TS_BUF1[ts->rdidx1 + count - 1]) {
+                               --count;
+                       }
+                       ts->size1 += count;
+                       ts->rdidx1 += count;
+                       if (ts->rdidx1 >= BUFSIZE) /* actually == BUFSIZE */
+                               ts->rdidx1 = 0;
+               }
+ skip3:
+               if (/*ts->size2 < BUFSIZE &&*/ FD_ISSET(ts->ptyfd, &rdfdset)) {
+                       /* Read from pty to buffer 2. */
+                       count = MIN(BUFSIZE - ts->rdidx2, BUFSIZE - ts->size2);
+                       count = safe_read(ts->ptyfd, TS_BUF2 + ts->rdidx2, count);
+                       if (count <= 0) {
+                               if (count < 0 && errno == EAGAIN)
+                                       goto skip4;
+                               goto kill_session;
+                       }
+                       ts->size2 += count;
+                       ts->rdidx2 += count;
+                       if (ts->rdidx2 >= BUFSIZE) /* actually == BUFSIZE */
+                               ts->rdidx2 = 0;
+               }
+ skip4:
+               ts = next;
+               continue;
+ kill_session:
+               free_session(ts);
+               ts = next;
+       }
+
+       goto again;
+}
diff --git a/networking/telnetd.ctrlSQ.patch b/networking/telnetd.ctrlSQ.patch
new file mode 100644 (file)
index 0000000..885e105
--- /dev/null
@@ -0,0 +1,175 @@
+From: "Doug Graham" <dgraham@nortel.com>
+Date: 2009-01-22 07:20
+
+Hello,
+
+Busybox's telnetd does not disable local (client-side) flow control
+properly.  It does not put the pty into packet mode and then notify the
+client whenever flow control is disabled by an application running under
+its control.  The result is that ^S/^Q are not passed through to the
+application, which is painful when the application is an emacs variant.
+
+I suppose that support for this might be considered bloat, but the
+included patch only adds about 200 bytes of text to x86 busybox and 300
+bytes to mipsel busybox.  Please consider applying.
+
+=============================
+
+NB: the patch doesn't work as-is because we now have iac_safe_write()
+which quotes IACs on output.
+
+=============================
+Docs:
+
+The following ioctl(2) calls apply only to pseudo terminals:
+
+TIOCSTOP Stops output to a terminal (e.g. like typing ^S). Takes no parameter.
+
+TIOCSTART Restarts output (stopped by TIOCSTOP or by typing ^S). Takes no parameter.
+
+TIOCPKT         Enable/disable packet mode. When applied to the master side of a pseudo terminal, each
+subsequent read(2) from the terminal will return data written on the slave part of the pseudo terminal preceded by a
+zero byte (symbolically defined as TIOCPKT_DATA), or a single byte reflecting control status information.
+In the latter case, the byte is an inclusive-or of zero or more of the bits:
+
+TIOCPKT_FLUSHREAD     whenever the read queue for the terminal is flushed.
+TIOCPKT_FLUSHWRITE    whenever the write queue for the terminal is flushed.
+TIOCPKT_STOP    whenever output to the terminal is stopped a la ^S.
+TIOCPKT_START   whenever output to the terminal is restarted.
+TIOCPKT_DOSTOP  whenever t_stopc is ^S and t_startc is ^Q.
+TIOCPKT_NOSTOP  whenever the start and stop characters are not ^S/^Q.
+
+While this mode is in use, the presence of control status information to be read from the master side may be detected
+by a select(2) for exceptional conditions.
+
+This mode is used by rlogin(1) and rlogind(8) to implement a remote-echoed, locally ^S/^Q flow-controlled remote login
+with proper back-flushing of output; it can be used by other similar programs.
+
+TIOCUCNTL       Enable/disable a mode that allows a small number of simple user ioctl(2) commands to be passed through
+the pseudo-terminal, using a protocol similar to that of TIOCPKT. The TIOCUCNTL and TIOCPKT modes are mutually
+exclusive. This mode is enabled from the master side of a pseudo terminal. Each subsequent read(2) from the master side
+will return data written on the slave part of the pseudo terminal preceded by a zero byte, or a single byte reflecting a
+user control operation on the slave side. A user control command consists of a special ioctl(2) operation with no data;
+the command is given as UIOCCMD (n), where n is a number in the range 1-255. The operation value n will be received as
+a single byte on the next read(2) from the master side. The ioctl(2) UIOCCMD (0) is a no-op that may be used to probe
+for the existence of this facility. As with TIOCPKT mode, command operations may be detected with a select(2) for
+exceptional conditions.
+
+--- busybox-1.13.2/networking/telnetd.c        2009/01/21 20:02:39     1.1
++++ busybox-1.13.2/networking/telnetd.c        2009/01/22 00:35:28
+@@ -38,6 +38,9 @@
+       int sockfd_read, sockfd_write, ptyfd;
+       int shell_pid;
++#ifdef TIOCPKT
++      int flowstate;
++#endif
+       /* two circular buffers */
+       /*char *buf1, *buf2;*/
+ /*#define TS_BUF1 ts->buf1*/
+@@ -170,6 +173,9 @@
+       int fd, pid;
+       char tty_name[GETPTY_BUFSIZE];
+       struct tsession *ts = xzalloc(sizeof(struct tsession) + BUFSIZE * 2);
++#ifdef TIOCPKT
++      int on = 1;
++#endif
+       /*ts->buf1 = (char *)(ts + 1);*/
+       /*ts->buf2 = ts->buf1 + BUFSIZE;*/
+@@ -180,6 +186,10 @@
+               maxfd = fd;
+       ts->ptyfd = fd;
+       ndelay_on(fd);
++#ifdef TIOCPKT
++      ioctl(fd, TIOCPKT, &on);
++      ts->flowstate = TIOCPKT_DOSTOP;
++#endif
+ #if ENABLE_FEATURE_TELNETD_STANDALONE
+       ts->sockfd_read = sock;
+       /* SO_KEEPALIVE by popular demand */
+@@ -385,6 +395,16 @@
+               portnbr = 23,
+       };
+ #endif
++#ifdef TIOCPKT
++      int control;
++      static const char lflow_on[] =
++          {IAC, SB, TELOPT_LFLOW, LFLOW_ON, IAC, SE};
++      static const char lflow_off[] =
++          {IAC, SB, TELOPT_LFLOW, LFLOW_OFF, IAC, SE};
++# define RESERVED sizeof(lflow_on)
++#else
++# define RESERVED 0
++#endif
+       /* Even if !STANDALONE, we accept (and ignore) -i, thus people
+        * don't need to guess whether it's ok to pass -i to us */
+       opt = getopt32(argv, "f:l:Ki" USE_FEATURE_TELNETD_STANDALONE("p:b:F"),
+@@ -475,7 +495,7 @@
+                               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 */
++                      if (ts->size2 < (BUFSIZE - RESERVED)) /* can read from pty */
+                               FD_SET(ts->ptyfd, &rdfdset);
+               }
+               ts = next;
+@@ -593,6 +613,52 @@
+                                       goto skip4;
+                               goto kill_session;
+                       }
++#ifdef TIOCPKT
++                      control = TS_BUF2[ts->rdidx2];
++                      if (--count > 0 && control == TIOCPKT_DATA) {
++                              /*
++                               * If we are in packet mode, and we have
++                               * just read a chunk of actual data from
++                               * the pty, then there is the TIOCPKT_DATA
++                               * byte (zero) that we have got to remove
++                               * somehow.  If there were no chars in
++                               * TS_BUF2 before we did this read, then
++                               * we can optimize by just advancing wridx2.
++                               * Otherwise we have to copy the new data down
++                               * to close the gap (Could use readv() instead).
++                               */
++                              if (ts->size2 == 0)
++                                      ts->wridx2++;
++                              else {
++                                      memmove(TS_BUF2 + ts->rdidx2,
++                                              TS_BUF2 + ts->rdidx2 + 1, count);
++                              }
++                      }
++
++                      /*
++                       * If the flow control state changed, notify
++                       * the client.  If "control" is not TIOCPKT_DATA,
++                       * then there are no data bytes to worry about.
++                       */
++                      if ((control & (TIOCPKT_DOSTOP|TIOCPKT_NOSTOP)) != 0
++                       && ts->flowstate != (control & TIOCPKT_DOSTOP)) {
++                              const char *p = ts->flowstate ? lflow_off : lflow_on;
++
++                              /*
++                               * We know we have enough free slots available
++                               * (see RESERVED) but they are not necessarily
++                               * contiguous; we may have to wrap.
++                               */
++                              for (count = sizeof(lflow_on); count > 0; count--) {
++                                      TS_BUF2[ts->rdidx2++] = *p++;
++                                      if (ts->rdidx2 >= BUFSIZE)
++                                              ts->rdidx2 = 0;
++                                      ts->size2++;
++                              }
++
++                              ts->flowstate = control & TIOCPKT_DOSTOP;
++                      }
++#endif /* TIOCPKT */
+                       ts->size2 += count;
+                       ts->rdidx2 += count;
+                       if (ts->rdidx2 >= BUFSIZE) /* actually == BUFSIZE */
+
+--Doug
+_______________________________________________
+busybox mailing list
+busybox@busybox.net
+http://lists.busybox.net/mailman/listinfo/busybox
diff --git a/networking/tftp.c b/networking/tftp.c
new file mode 100644 (file)
index 0000000..9c78b6e
--- /dev/null
@@ -0,0 +1,740 @@
+/* vi: set sw=4 ts=4: */
+/* -------------------------------------------------------------------------
+ * tftp.c
+ *
+ * A simple tftp client/server for busybox.
+ * Tries to follow RFC1350.
+ * Only "octet" mode supported.
+ * Optional blocksize negotiation (RFC2347 + RFC2348)
+ *
+ * Copyright (C) 2001 Magnus Damm <damm@opensource.se>
+ *
+ * Parts of the code based on:
+ *
+ * atftp:  Copyright (C) 2000 Jean-Pierre Lefebvre <helix@step.polymtl.ca>
+ *                        and Remi Lefebvre <remi@debian.org>
+ *
+ * utftp:  Copyright (C) 1999 Uwe Ohse <uwe@ohse.de>
+ *
+ * tftpd added by Denys Vlasenko & Vladimir Dronnikov
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ * ------------------------------------------------------------------------- */
+
+#include "libbb.h"
+
+#if ENABLE_FEATURE_TFTP_GET || ENABLE_FEATURE_TFTP_PUT
+
+#define TFTP_BLKSIZE_DEFAULT       512  /* according to RFC 1350, don't change */
+#define TFTP_BLKSIZE_DEFAULT_STR "512"
+#define TFTP_TIMEOUT_MS             50
+#define TFTP_MAXTIMEOUT_MS        2000
+#define TFTP_NUM_RETRIES            12  /* number of backed-off retries */
+
+/* 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
+
+/* error codes sent over network (we use only 0, 1, 3 and 8) */
+/* generic (error message is included in the packet) */
+#define ERR_UNSPEC   0
+#define ERR_NOFILE   1
+#define ERR_ACCESS   2
+/* disk full or allocation exceeded */
+#define ERR_WRITE    3
+#define ERR_OP       4
+#define ERR_BAD_ID   5
+#define ERR_EXIST    6
+#define ERR_BAD_USER 7
+#define ERR_BAD_OPT  8
+
+/* masks coming from getopt32 */
+enum {
+       TFTP_OPT_GET = (1 << 0),
+       TFTP_OPT_PUT = (1 << 1),
+       /* pseudo option: if set, it's tftpd */
+       TFTPD_OPT = (1 << 7) * ENABLE_TFTPD,
+       TFTPD_OPT_r = (1 << 8) * ENABLE_TFTPD,
+       TFTPD_OPT_c = (1 << 9) * ENABLE_TFTPD,
+       TFTPD_OPT_u = (1 << 10) * ENABLE_TFTPD,
+};
+
+#if ENABLE_FEATURE_TFTP_GET && !ENABLE_FEATURE_TFTP_PUT
+#define USE_GETPUT(...)
+#define CMD_GET(cmd) 1
+#define CMD_PUT(cmd) 0
+#elif !ENABLE_FEATURE_TFTP_GET && ENABLE_FEATURE_TFTP_PUT
+#define USE_GETPUT(...)
+#define CMD_GET(cmd) 0
+#define CMD_PUT(cmd) 1
+#else
+#define USE_GETPUT(...) __VA_ARGS__
+#define CMD_GET(cmd) ((cmd) & TFTP_OPT_GET)
+#define CMD_PUT(cmd) ((cmd) & TFTP_OPT_PUT)
+#endif
+/* NB: in the code below
+ * CMD_GET(cmd) and CMD_PUT(cmd) are mutually exclusive
+ */
+
+
+struct globals {
+       /* u16 TFTP_ERROR; u16 reason; both network-endian, then error text: */
+       uint8_t error_pkt[4 + 32];
+       char *user_opt;
+       /* used in tftpd_main(), a bit big for stack: */
+       char block_buf[TFTP_BLKSIZE_DEFAULT];
+};
+#define G (*(struct globals*)&bb_common_bufsiz1)
+#define block_buf        (G.block_buf   )
+#define user_opt         (G.user_opt    )
+#define error_pkt        (G.error_pkt   )
+#define INIT_G() do { } while (0)
+
+#define error_pkt_reason (error_pkt[3])
+#define error_pkt_str    (error_pkt + 4)
+
+
+#if ENABLE_FEATURE_TFTP_BLOCKSIZE
+
+static int tftp_blksize_check(const char *blksize_str, int maxsize)
+{
+       /* Check if the blksize is valid:
+        * RFC2348 says between 8 and 65464,
+        * but our implementation makes it impossible
+        * to use blksizes smaller than 22 octets. */
+       unsigned blksize = bb_strtou(blksize_str, NULL, 10);
+       if (errno
+        || (blksize < 24) || (blksize > maxsize)
+       ) {
+               bb_error_msg("bad blocksize '%s'", blksize_str);
+               return -1;
+       }
+#if ENABLE_TFTP_DEBUG
+       bb_error_msg("using blksize %u", blksize);
+#endif
+       return blksize;
+}
+
+static char *tftp_get_option(const char *option, char *buf, int len)
+{
+       int opt_val = 0;
+       int opt_found = 0;
+       int k;
+
+       /* buf points to:
+        * "opt_name<NUL>opt_val<NUL>opt_name2<NUL>opt_val2<NUL>..." */
+
+       while (len > 0) {
+               /* Make sure options are terminated correctly */
+               for (k = 0; k < len; k++) {
+                       if (buf[k] == '\0') {
+                               goto nul_found;
+                       }
+               }
+               return NULL;
+ nul_found:
+               if (opt_val == 0) { /* it's "name" part */
+                       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_protocol(
+               len_and_sockaddr *our_lsa,
+               len_and_sockaddr *peer_lsa,
+               const char *local_file
+               USE_TFTP(, const char *remote_file)
+               USE_FEATURE_TFTP_BLOCKSIZE(USE_TFTPD(, void *tsize))
+               USE_FEATURE_TFTP_BLOCKSIZE(, int blksize))
+{
+#if !ENABLE_TFTP
+#define remote_file NULL
+#endif
+#if !(ENABLE_FEATURE_TFTP_BLOCKSIZE && ENABLE_TFTPD)
+#define tsize NULL
+#endif
+#if !ENABLE_FEATURE_TFTP_BLOCKSIZE
+       enum { blksize = TFTP_BLKSIZE_DEFAULT };
+#endif
+
+       struct pollfd pfd[1];
+#define socket_fd (pfd[0].fd)
+       int len;
+       int send_len;
+       USE_FEATURE_TFTP_BLOCKSIZE(smallint want_option_ack = 0;)
+       smallint finished = 0;
+       uint16_t opcode;
+       uint16_t block_nr;
+       uint16_t recv_blk;
+       int open_mode, local_fd;
+       int retries, waittime_ms;
+       int io_bufsize = blksize + 4;
+       char *cp;
+       /* Can't use RESERVE_CONFIG_BUFFER here since the allocation
+        * size varies meaning BUFFERS_GO_ON_STACK would fail */
+       /* We must keep the transmit and receive buffers seperate */
+       /* In case we rcv a garbage pkt and we need to rexmit the last pkt */
+       char *xbuf = xmalloc(io_bufsize);
+       char *rbuf = xmalloc(io_bufsize);
+
+       socket_fd = xsocket(peer_lsa->u.sa.sa_family, SOCK_DGRAM, 0);
+       setsockopt_reuseaddr(socket_fd);
+
+       block_nr = 1;
+       cp = xbuf + 2;
+
+       if (!ENABLE_TFTP || our_lsa) {
+               /* tftpd */
+
+               /* Create a socket which is:
+                * 1. bound to IP:port peer sent 1st datagram to,
+                * 2. connected to peer's IP:port
+                * This way we will answer from the IP:port peer
+                * expects, will not get any other packets on
+                * the socket, and also plain read/write will work. */
+               xbind(socket_fd, &our_lsa->u.sa, our_lsa->len);
+               xconnect(socket_fd, &peer_lsa->u.sa, peer_lsa->len);
+
+               /* Is there an error already? Send pkt and bail out */
+               if (error_pkt_reason || error_pkt_str[0])
+                       goto send_err_pkt;
+
+               if (CMD_GET(option_mask32)) {
+                       /* it's upload - we must ACK 1st packet (with filename)
+                        * as if it's "block 0" */
+                       block_nr = 0;
+               }
+
+               if (user_opt) {
+                       struct passwd *pw = xgetpwnam(user_opt);
+                       change_identity(pw); /* initgroups, setgid, setuid */
+               }
+       }
+
+       /* Open local file (must be after changing user) */
+       if (CMD_PUT(option_mask32)) {
+               open_mode = O_RDONLY;
+       } else {
+               open_mode = O_WRONLY | O_TRUNC | O_CREAT;
+#if ENABLE_TFTPD
+               if ((option_mask32 & (TFTPD_OPT+TFTPD_OPT_c)) == TFTPD_OPT) {
+                       /* tftpd without -c */
+                       open_mode = O_WRONLY | O_TRUNC;
+               }
+#endif
+       }
+       if (!(option_mask32 & TFTPD_OPT)) {
+               local_fd = CMD_GET(option_mask32) ? STDOUT_FILENO : STDIN_FILENO;
+               if (NOT_LONE_DASH(local_file))
+                       local_fd = xopen(local_file, open_mode);
+       } else {
+               local_fd = open(local_file, open_mode);
+               if (local_fd < 0) {
+                       error_pkt_reason = ERR_NOFILE;
+                       strcpy((char*)error_pkt_str, "can't open file");
+                       goto send_err_pkt;
+               }
+       }
+
+       if (!ENABLE_TFTP || our_lsa) {
+/* gcc 4.3.1 would NOT optimize it out as it should! */
+#if ENABLE_FEATURE_TFTP_BLOCKSIZE
+               if (blksize != TFTP_BLKSIZE_DEFAULT || tsize) {
+                       /* Create and send OACK packet. */
+                       /* For the download case, block_nr is still 1 -
+                        * we expect 1st ACK from peer to be for (block_nr-1),
+                        * that is, for "block 0" which is our OACK pkt */
+                       opcode = TFTP_OACK;
+                       goto add_blksize_opt;
+               }
+#endif
+       } else {
+/* Removing it, or using if() statement instead of #if may lead to
+ * "warning: null argument where non-null required": */
+#if ENABLE_TFTP
+               /* tftp */
+
+               /* We can't (and don't really need to) bind the socket:
+                * we don't know from which local IP datagrams will be sent,
+                * but kernel will pick the same IP every time (unless routing
+                * table is changed), thus peer will see dgrams consistently
+                * coming from the same IP.
+                * We would like to connect the socket, but since peer's
+                * UDP code can be less perfect than ours, _peer's_ IP:port
+                * in replies may differ from IP:port we used to send
+                * our first packet. We can connect() only when we get
+                * first reply. */
+
+               /* build opcode */
+               opcode = TFTP_WRQ;
+               if (CMD_GET(option_mask32)) {
+                       opcode = TFTP_RRQ;
+               }
+               /* add filename and mode */
+               /* fill in packet if the filename fits into xbuf */
+               len = strlen(remote_file) + 1;
+               if (2 + len + sizeof("octet") >= io_bufsize) {
+                       bb_error_msg("remote filename is too long");
+                       goto ret;
+               }
+               strcpy(cp, remote_file);
+               cp += len;
+               /* add "mode" part of the package */
+               strcpy(cp, "octet");
+               cp += sizeof("octet");
+
+#if ENABLE_FEATURE_TFTP_BLOCKSIZE
+               if (blksize == TFTP_BLKSIZE_DEFAULT)
+                       goto send_pkt;
+
+               /* Non-standard blocksize: add option to pkt */
+               if ((&xbuf[io_bufsize - 1] - cp) < sizeof("blksize NNNNN")) {
+                       bb_error_msg("remote filename is too long");
+                       goto ret;
+               }
+               want_option_ack = 1;
+#endif
+#endif /* ENABLE_TFTP */
+
+#if ENABLE_FEATURE_TFTP_BLOCKSIZE
+ add_blksize_opt:
+#if ENABLE_TFTPD
+               if (tsize) {
+                       struct stat st;
+                       /* add "tsize", <nul>, size, <nul> */
+                       strcpy(cp, "tsize");
+                       cp += sizeof("tsize");
+                       fstat(local_fd, &st);
+                       cp += snprintf(cp, 10, "%u", (int) st.st_size) + 1;
+               }
+#endif
+               if (blksize != TFTP_BLKSIZE_DEFAULT) {
+                       /* add "blksize", <nul>, blksize, <nul> */
+                       strcpy(cp, "blksize");
+                       cp += sizeof("blksize");
+                       cp += snprintf(cp, 6, "%d", blksize) + 1;
+               }
+#endif
+               /* First packet is built, so skip packet generation */
+               goto send_pkt;
+       }
+
+       /* Using mostly goto's - continue/break will be less clear
+        * in where we actually jump to */
+       while (1) {
+               /* Build ACK or DATA */
+               cp = xbuf + 2;
+               *((uint16_t*)cp) = htons(block_nr);
+               cp += 2;
+               block_nr++;
+               opcode = TFTP_ACK;
+               if (CMD_PUT(option_mask32)) {
+                       opcode = TFTP_DATA;
+                       len = full_read(local_fd, cp, blksize);
+                       if (len < 0) {
+                               goto send_read_err_pkt;
+                       }
+                       if (len != blksize) {
+                               finished = 1;
+                       }
+                       cp += len;
+               }
+ send_pkt:
+               /* Send packet */
+               *((uint16_t*)xbuf) = htons(opcode); /* fill in opcode part */
+               send_len = cp - xbuf;
+               /* NB: send_len value is preserved in code below
+                * for potential resend */
+
+               retries = TFTP_NUM_RETRIES;     /* re-initialize */
+               waittime_ms = TFTP_TIMEOUT_MS;
+
+ send_again:
+#if ENABLE_TFTP_DEBUG
+               fprintf(stderr, "sending %u bytes\n", send_len);
+               for (cp = xbuf; cp < &xbuf[send_len]; cp++)
+                       fprintf(stderr, "%02x ", (unsigned char) *cp);
+               fprintf(stderr, "\n");
+#endif
+               xsendto(socket_fd, xbuf, send_len, &peer_lsa->u.sa, peer_lsa->len);
+               /* Was it final ACK? then exit */
+               if (finished && (opcode == TFTP_ACK))
+                       goto ret;
+
+ recv_again:
+               /* Receive packet */
+               /*pfd[0].fd = socket_fd;*/
+               pfd[0].events = POLLIN;
+               switch (safe_poll(pfd, 1, waittime_ms)) {
+               default:
+                       /*bb_perror_msg("poll"); - done in safe_poll */
+                       goto ret;
+               case 0:
+                       retries--;
+                       if (retries == 0) {
+                               bb_error_msg("timeout");
+                               goto ret; /* no err packet sent */
+                       }
+
+                       /* exponential backoff with limit */
+                       waittime_ms += waittime_ms/2;
+                       if (waittime_ms > TFTP_MAXTIMEOUT_MS) {
+                               waittime_ms = TFTP_MAXTIMEOUT_MS;
+                       }
+
+                       goto send_again; /* resend last sent pkt */
+               case 1:
+                       if (!our_lsa) {
+                               /* tftp (not tftpd!) receiving 1st packet */
+                               our_lsa = ((void*)(ptrdiff_t)-1); /* not NULL */
+                               len = recvfrom(socket_fd, rbuf, io_bufsize, 0,
+                                               &peer_lsa->u.sa, &peer_lsa->len);
+                               /* Our first dgram went to port 69
+                                * but reply may come from different one.
+                                * Remember and use this new port (and IP) */
+                               if (len >= 0)
+                                       xconnect(socket_fd, &peer_lsa->u.sa, peer_lsa->len);
+                       } else {
+                               /* tftpd, or not the very first packet:
+                                * socket is connect()ed, can just read from it. */
+                               /* Don't full_read()!
+                                * This is not TCP, one read == one pkt! */
+                               len = safe_read(socket_fd, rbuf, io_bufsize);
+                       }
+                       if (len < 0) {
+                               goto send_read_err_pkt;
+                       }
+                       if (len < 4) { /* too small? */
+                               goto recv_again;
+                       }
+               }
+
+               /* Process recv'ed packet */
+               opcode = ntohs( ((uint16_t*)rbuf)[0] );
+               recv_blk = ntohs( ((uint16_t*)rbuf)[1] );
+#if ENABLE_TFTP_DEBUG
+               fprintf(stderr, "received %d bytes: %04x %04x\n", len, opcode, recv_blk);
+#endif
+               if (opcode == TFTP_ERROR) {
+                       static const char errcode_str[] ALIGN1 =
+                               "\0"
+                               "file not found\0"
+                               "access violation\0"
+                               "disk full\0"
+                               "bad operation\0"
+                               "unknown transfer id\0"
+                               "file already exists\0"
+                               "no such user\0"
+                               "bad option";
+
+                       const char *msg = "";
+
+                       if (len > 4 && rbuf[4] != '\0') {
+                               msg = &rbuf[4];
+                               rbuf[io_bufsize - 1] = '\0'; /* paranoia */
+                       } else if (recv_blk <= 8) {
+                               msg = nth_string(errcode_str, recv_blk);
+                       }
+                       bb_error_msg("server error: (%u) %s", recv_blk, msg);
+                       goto ret;
+               }
+
+#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_get_option("blksize", &rbuf[2], len - 2);
+                               if (res) {
+                                       blksize = tftp_blksize_check(res, blksize);
+                                       if (blksize < 0) {
+                                               error_pkt_reason = ERR_BAD_OPT;
+                                               goto send_err_pkt;
+                                       }
+                                       io_bufsize = blksize + 4;
+                                       /* Send ACK for OACK ("block" no: 0) */
+                                       block_nr = 0;
+                                       continue;
+                               }
+                               /* rfc2347:
+                                * "An option not acknowledged by the server
+                                *  must be ignored by the client and server
+                                *  as if it were never requested." */
+                       }
+                       bb_error_msg("server only supports blocksize of 512");
+                       blksize = TFTP_BLKSIZE_DEFAULT;
+                       io_bufsize = TFTP_BLKSIZE_DEFAULT + 4;
+               }
+#endif
+               /* block_nr is already advanced to next block# we expect
+                * to get / block# we are about to send next time */
+
+               if (CMD_GET(option_mask32) && (opcode == TFTP_DATA)) {
+                       if (recv_blk == block_nr) {
+                               int sz = full_write(local_fd, &rbuf[4], len - 4);
+                               if (sz != len - 4) {
+                                       strcpy((char*)error_pkt_str, bb_msg_write_error);
+                                       error_pkt_reason = ERR_WRITE;
+                                       goto send_err_pkt;
+                               }
+                               if (sz != blksize) {
+                                       finished = 1;
+                               }
+                               continue; /* send ACK */
+                       }
+                       if (recv_blk == (block_nr - 1)) {
+                               /* Server lost our TFTP_ACK.  Resend it */
+                               block_nr = recv_blk;
+                               continue;
+                       }
+               }
+
+               if (CMD_PUT(option_mask32) && (opcode == TFTP_ACK)) {
+                       /* did peer ACK our last DATA pkt? */
+                       if (recv_blk == (uint16_t) (block_nr - 1)) {
+                               if (finished)
+                                       goto ret;
+                               continue; /* send next block */
+                       }
+               }
+               /* Awww... recv'd packet is not recognized! */
+               goto recv_again;
+               /* why recv_again? - rfc1123 says:
+                * "The sender (i.e., the side originating the DATA packets)
+                *  must never resend the current DATA packet on receipt
+                *  of a duplicate ACK".
+                * DATA pkts are resent ONLY on timeout.
+                * Thus "goto send_again" will ba a bad mistake above.
+                * See:
+                * http://en.wikipedia.org/wiki/Sorcerer's_Apprentice_Syndrome
+                */
+       } /* end of "while (1)" */
+ ret:
+       if (ENABLE_FEATURE_CLEAN_UP) {
+               close(local_fd);
+               close(socket_fd);
+               free(xbuf);
+               free(rbuf);
+       }
+       return finished == 0; /* returns 1 on failure */
+
+ send_read_err_pkt:
+       strcpy((char*)error_pkt_str, bb_msg_read_error);
+ send_err_pkt:
+       if (error_pkt_str[0])
+               bb_error_msg((char*)error_pkt_str);
+       error_pkt[1] = TFTP_ERROR;
+       xsendto(socket_fd, error_pkt, 4 + 1 + strlen((char*)error_pkt_str),
+                       &peer_lsa->u.sa, peer_lsa->len);
+       return EXIT_FAILURE;
+#undef remote_file
+#undef tsize
+}
+
+#if ENABLE_TFTP
+
+int tftp_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int tftp_main(int argc UNUSED_PARAM, char **argv)
+{
+       len_and_sockaddr *peer_lsa;
+       const char *local_file = NULL;
+       const char *remote_file = NULL;
+#if ENABLE_FEATURE_TFTP_BLOCKSIZE
+       const char *blksize_str = TFTP_BLKSIZE_DEFAULT_STR;
+       int blksize;
+#endif
+       int result;
+       int port;
+       USE_GETPUT(int opt;)
+
+       INIT_G();
+
+       /* -p or -g is mandatory, and they are mutually exclusive */
+       opt_complementary = "" USE_FEATURE_TFTP_GET("g:") USE_FEATURE_TFTP_PUT("p:")
+                       USE_GETPUT("g--p:p--g:");
+
+       USE_GETPUT(opt =) getopt32(argv,
+                       USE_FEATURE_TFTP_GET("g") USE_FEATURE_TFTP_PUT("p")
+                               "l:r:" USE_FEATURE_TFTP_BLOCKSIZE("b:"),
+                       &local_file, &remote_file
+                       USE_FEATURE_TFTP_BLOCKSIZE(, &blksize_str));
+       argv += optind;
+
+#if ENABLE_FEATURE_TFTP_BLOCKSIZE
+       /* Check if the blksize is valid:
+        * RFC2348 says between 8 and 65464 */
+       blksize = tftp_blksize_check(blksize_str, 65564);
+       if (blksize < 0) {
+               //bb_error_msg("bad block size");
+               return EXIT_FAILURE;
+       }
+#endif
+
+       if (remote_file) {
+               if (!local_file) {
+                       const char *slash = strrchr(remote_file, '/');
+                       local_file = slash ? slash + 1 : remote_file;
+               }
+       } else {
+               remote_file = local_file;
+       }
+
+       /* Error if filename or host is not known */
+       if (!remote_file || !argv[0])
+               bb_show_usage();
+
+       port = bb_lookup_port(argv[1], "udp", 69);
+       peer_lsa = xhost2sockaddr(argv[0], port);
+
+#if ENABLE_TFTP_DEBUG
+       fprintf(stderr, "using server '%s', remote_file '%s', local_file '%s'\n",
+                       xmalloc_sockaddr2dotted(&peer_lsa->u.sa),
+                       remote_file, local_file);
+#endif
+
+       result = tftp_protocol(
+               NULL /*our_lsa*/, peer_lsa,
+               local_file, remote_file
+               USE_FEATURE_TFTP_BLOCKSIZE(USE_TFTPD(, NULL /*tsize*/))
+               USE_FEATURE_TFTP_BLOCKSIZE(, blksize)
+       );
+
+       if (result != EXIT_SUCCESS && NOT_LONE_DASH(local_file) && CMD_GET(opt)) {
+               unlink(local_file);
+       }
+       return result;
+}
+
+#endif /* ENABLE_TFTP */
+
+#if ENABLE_TFTPD
+int tftpd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int tftpd_main(int argc UNUSED_PARAM, char **argv)
+{
+       len_and_sockaddr *our_lsa;
+       len_and_sockaddr *peer_lsa;
+       char *local_file, *mode;
+       const char *error_msg;
+       int opt, result, opcode;
+       USE_FEATURE_TFTP_BLOCKSIZE(int blksize = TFTP_BLKSIZE_DEFAULT;)
+       USE_FEATURE_TFTP_BLOCKSIZE(char *tsize = NULL;)
+
+       INIT_G();
+
+       our_lsa = get_sock_lsa(STDIN_FILENO);
+       if (!our_lsa) {
+               /* This is confusing:
+                *bb_error_msg_and_die("stdin is not a socket");
+                * Better: */
+               bb_show_usage();
+               /* Help text says that tftpd must be used as inetd service,
+                * which is by far the most usual cause of get_sock_lsa
+                * failure */
+       }
+       peer_lsa = xzalloc(LSA_LEN_SIZE + our_lsa->len);
+       peer_lsa->len = our_lsa->len;
+
+       /* Shifting to not collide with TFTP_OPTs */
+       opt = option_mask32 = TFTPD_OPT | (getopt32(argv, "rcu:", &user_opt) << 8);
+       argv += optind;
+       if (argv[0])
+               xchdir(argv[0]);
+
+       result = recv_from_to(STDIN_FILENO, block_buf, sizeof(block_buf),
+                       0 /* flags */,
+                       &peer_lsa->u.sa, &our_lsa->u.sa, our_lsa->len);
+
+       error_msg = "malformed packet";
+       opcode = ntohs(*(uint16_t*)block_buf);
+       if (result < 4 || result >= sizeof(block_buf)
+        || block_buf[result-1] != '\0'
+        || (USE_FEATURE_TFTP_PUT(opcode != TFTP_RRQ) /* not download */
+            USE_GETPUT(&&)
+            USE_FEATURE_TFTP_GET(opcode != TFTP_WRQ) /* not upload */
+           )
+       ) {
+               goto err;
+       }
+       local_file = block_buf + 2;
+       if (local_file[0] == '.' || strstr(local_file, "/.")) {
+               error_msg = "dot in file name";
+               goto err;
+       }
+       mode = local_file + strlen(local_file) + 1;
+       if (mode >= block_buf + result || strcmp(mode, "octet") != 0) {
+               goto err;
+       }
+#if ENABLE_FEATURE_TFTP_BLOCKSIZE
+       {
+               char *res;
+               char *opt_str = mode + sizeof("octet");
+               int opt_len = block_buf + result - opt_str;
+               if (opt_len > 0) {
+                       res = tftp_get_option("blksize", opt_str, opt_len);
+                       if (res) {
+                               blksize = tftp_blksize_check(res, 65564);
+                               if (blksize < 0) {
+                                       error_pkt_reason = ERR_BAD_OPT;
+                                       /* will just send error pkt */
+                                       goto do_proto;
+                               }
+                       }
+                       /* did client ask us about file size? */
+                       tsize = tftp_get_option("tsize", opt_str, opt_len);
+               }
+       }
+#endif
+
+       if (!ENABLE_FEATURE_TFTP_PUT || opcode == TFTP_WRQ) {
+               if (opt & TFTPD_OPT_r) {
+                       /* This would mean "disk full" - not true */
+                       /*error_pkt_reason = ERR_WRITE;*/
+                       error_msg = bb_msg_write_error;
+                       goto err;
+               }
+               USE_GETPUT(option_mask32 |= TFTP_OPT_GET;) /* will receive file's data */
+       } else {
+               USE_GETPUT(option_mask32 |= TFTP_OPT_PUT;) /* will send file's data */
+       }
+
+       /* NB: if error_pkt_str or error_pkt_reason is set up,
+        * tftp_protocol() just sends one error pkt and returns */
+
+ do_proto:
+       close(STDIN_FILENO); /* close old, possibly wildcard socket */
+       /* tftp_protocol() will create new one, bound to particular local IP */
+       result = tftp_protocol(
+               our_lsa, peer_lsa,
+               local_file USE_TFTP(, NULL /*remote_file*/)
+               USE_FEATURE_TFTP_BLOCKSIZE(, tsize)
+               USE_FEATURE_TFTP_BLOCKSIZE(, blksize)
+       );
+
+       return result;
+ err:
+       strcpy((char*)error_pkt_str, error_msg);
+       goto do_proto;
+}
+
+#endif /* ENABLE_TFTPD */
+
+#endif /* ENABLE_FEATURE_TFTP_GET || ENABLE_FEATURE_TFTP_PUT */
diff --git a/networking/traceroute.c b/networking/traceroute.c
new file mode 100644 (file)
index 0000000..7284f00
--- /dev/null
@@ -0,0 +1,926 @@
+/* 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 <dzo@simtreas.ru>
+ *
+ * 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.
+ */
+
+/*
+ * 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 <net/if.h>
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <netinet/udp.h>
+#include <netinet/ip.h>
+#include <netinet/ip_icmp.h>
+
+#include "libbb.h"
+#include "inet_common.h"
+
+#ifndef IPPROTO_ICMP
+# define IPPROTO_ICMP 1
+#endif
+#ifndef IPPROTO_IP
+# define IPPROTO_IP 0
+#endif
+
+/* Keep in sync with getopt32 call! */
+enum {
+       OPT_DONT_FRAGMNT = (1 << 0),    /* F */
+       OPT_USE_ICMP     = (1 << 1) * ENABLE_FEATURE_TRACEROUTE_USE_ICMP, /* I */
+       OPT_TTL_FLAG     = (1 << 2),    /* l */
+       OPT_ADDR_NUM     = (1 << 3),    /* n */
+       OPT_BYPASS_ROUTE = (1 << 4),    /* r */
+       OPT_DEBUG        = (1 << 5),    /* d */
+       OPT_VERBOSE      = (1 << 6) * ENABLE_FEATURE_TRACEROUTE_VERBOSE, /* v */
+       OPT_IP_CHKSUM    = (1 << 7),    /* x */
+       OPT_TOS          = (1 << 8),    /* t */
+       OPT_DEVICE       = (1 << 9),    /* i */
+       OPT_MAX_TTL      = (1 << 10),   /* m */
+       OPT_PORT         = (1 << 11),   /* p */
+       OPT_NPROBES      = (1 << 12),   /* q */
+       OPT_SOURCE       = (1 << 13),   /* s */
+       OPT_WAITTIME     = (1 << 14),   /* w */
+       OPT_PAUSE_MS     = (1 << 15),   /* z */
+       OPT_FIRST_TTL    = (1 << 16),   /* f */
+};
+#define verbose (option_mask32 & OPT_VERBOSE)
+
+enum {
+       SIZEOF_ICMP_HDR = 8,
+       rcvsock = 3, /* receive (icmp) socket file descriptor */
+       sndsock = 4, /* send (udp/icmp) socket file descriptor */
+};
+
+/* Data section of the probe packet */
+struct outdata_t {
+       unsigned char seq;             /* sequence number of this packet */
+       unsigned char ttl;             /* ttl packet left with */
+// UNUSED. Retaining to have the same packet size.
+       struct timeval tv_UNUSED PACKED; /* time packet left */
+};
+
+struct globals {
+       struct ip *outip;
+       struct outdata_t *outdata;
+       len_and_sockaddr *dest_lsa;
+       int packlen;                    /* total length of packet */
+       int pmtu;                       /* Path MTU Discovery (RFC1191) */
+       uint16_t ident;
+       uint16_t port; // 32768 + 666;  /* start udp dest port # for probe packets */
+       int waittime; // 5;             /* time to wait for response (in seconds) */
+#if ENABLE_FEATURE_TRACEROUTE_SOURCE_ROUTE
+       int optlen;                     /* length of ip options */
+#else
+#define optlen 0
+#endif
+       unsigned char recv_pkt[512];    /* last inbound (icmp) packet */
+#if ENABLE_FEATURE_TRACEROUTE_SOURCE_ROUTE
+       /* Maximum number of gateways (include room for one noop) */
+#define NGATEWAYS ((int)((MAX_IPOPTLEN - IPOPT_MINOFF - 1) / sizeof(uint32_t)))
+       /* loose source route gateway list (including room for final destination) */
+       uint32_t gwlist[NGATEWAYS + 1];
+#endif
+};
+
+#define G (*ptr_to_globals)
+#define outip     (G.outip    )
+#define outdata   (G.outdata  )
+#define dest_lsa  (G.dest_lsa )
+#define packlen   (G.packlen  )
+#define pmtu      (G.pmtu     )
+#define ident     (G.ident    )
+#define port      (G.port     )
+#define waittime  (G.waittime )
+#if ENABLE_FEATURE_TRACEROUTE_SOURCE_ROUTE
+# define optlen   (G.optlen   )
+#endif
+#define recv_pkt  (G.recv_pkt )
+#define gwlist    (G.gwlist   )
+#define INIT_G() do { \
+       SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
+       port = 32768 + 666; \
+       waittime = 5; \
+} while (0)
+
+#define outicmp ((struct icmp *)(outip + 1))
+#define outudp  ((struct udphdr *)(outip + 1))
+
+
+static int
+wait_for_reply(struct sockaddr_in *fromp)
+{
+       struct pollfd pfd[1];
+       int cc = 0;
+       socklen_t fromlen = sizeof(*fromp);
+
+       pfd[0].fd = rcvsock;
+       pfd[0].events = POLLIN;
+       if (safe_poll(pfd, 1, waittime * 1000) > 0)
+               cc = recvfrom(rcvsock, recv_pkt, sizeof(recv_pkt), 0,
+                           (struct sockaddr *)fromp, &fromlen);
+       return cc;
+}
+
+/*
+ * Checksum routine for Internet Protocol family headers (C Version)
+ */
+static uint16_t
+in_cksum(uint16_t *addr, int len)
+{
+       int nleft = len;
+       uint16_t *w = addr;
+       uint16_t 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)
+{
+       int len, res;
+       void *out;
+
+       /* Payload */
+       outdata->seq = seq;
+       outdata->ttl = ttl;
+// UNUSED: was storing gettimeofday's result there, but never ever checked it
+       /*memcpy(&outdata->tv, tp, sizeof(outdata->tv));*/
+
+       if (option_mask32 & OPT_USE_ICMP) {
+               outicmp->icmp_seq = htons(seq);
+
+               /* Always calculate checksum for icmp packets */
+               outicmp->icmp_cksum = 0;
+               outicmp->icmp_cksum = in_cksum((uint16_t *)outicmp,
+                                       packlen - (sizeof(*outip) + optlen));
+               if (outicmp->icmp_cksum == 0)
+                       outicmp->icmp_cksum = 0xffff;
+       }
+
+//BUG! verbose is (x & OPT_VERBOSE), not a counter!
+#if 0 //ENABLE_FEATURE_TRACEROUTE_VERBOSE
+       /* XXX undocumented debugging hack */
+       if (verbose > 1) {
+               const uint16_t *sp;
+               int nshorts, i;
+
+               sp = (uint16_t *)outip;
+               nshorts = (unsigned)packlen / sizeof(uint16_t);
+               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_TTL)
+       if (setsockopt(sndsock, IPPROTO_IP, IP_TTL,
+                               (char *)&ttl, sizeof(ttl)) < 0) {
+               bb_perror_msg_and_die("setsockopt ttl %d", ttl);
+       }
+#endif
+
+       len = packlen - sizeof(*outip);
+       if (option_mask32 & OPT_USE_ICMP)
+               out = outicmp;
+       else {
+               out = outdata;
+               len -= sizeof(*outudp);
+               set_nport(dest_lsa, htons(port + seq));
+       }
+       res = xsendto(sndsock, out, len,
+                       (struct sockaddr *)&dest_lsa->u.sa, dest_lsa->len);
+       if (res != len) {
+               bb_info_msg("sent %d octets, ret=%d", len, res);
+       }
+}
+
+#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 >= ARRAY_SIZE(ttab))
+               return "OUT-OF-RANGE";
+
+       return ttab[t];
+}
+#endif
+
+#if !ENABLE_FEATURE_TRACEROUTE_VERBOSE
+#define packet_ok(cc, from, seq) \
+       packet_ok(cc, seq)
+#endif
+static int
+packet_ok(int cc, const struct sockaddr_in *from, int seq)
+{
+       const struct icmp *icp;
+       unsigned char type, code;
+       int hlen;
+       const struct ip *ip;
+
+       ip = (struct ip *) recv_pkt;
+       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 *)(recv_pkt + hlen);
+       type = icp->icmp_type;
+       code = icp->icmp_code;
+       /* Path MTU Discovery (RFC1191) */
+       pmtu = 0;
+       if (code == ICMP_UNREACH_NEEDFRAG)
+               pmtu = ntohs(icp->icmp_nextmtu);
+
+       if ((type == ICMP_TIMXCEED && code == ICMP_TIMXCEED_INTRANS)
+        || type == ICMP_UNREACH
+        || type == ICMP_ECHOREPLY
+       ) {
+               const struct ip *hip;
+               const struct udphdr *up;
+
+               hip = &icp->icmp_ip;
+               hlen = hip->ip_hl << 2;
+               if (option_mask32 & OPT_USE_ICMP) {
+                       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);
+                       if (hlen + SIZEOF_ICMP_HDR <= cc
+                        && hip->ip_p == IPPROTO_ICMP
+                        && hicmp->icmp_id == htons(ident)
+                        && hicmp->icmp_seq == htons(seq)
+                       ) {
+                               return (type == ICMP_TIMXCEED ? -1 : code + 1);
+                       }
+               } else {
+                       up = (struct udphdr *)((char *)hip + hlen);
+                       if (hlen + 12 <= cc
+                        && hip->ip_p == IPPROTO_UDP
+// Off: since we do not form the entire IP packet,
+// but defer it to kernel, we can't set source port,
+// and thus can't check it here in the reply
+                       /* && 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;
+               uint32_t *lp = (uint32_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 -n flag has been supplied, give
+ * numeric value, otherwise try for symbolic name.
+ */
+static void
+print_inetname(const struct sockaddr_in *from)
+{
+       const char *ina;
+
+       ina = inet_ntoa(from->sin_addr);
+       if (option_mask32 & OPT_ADDR_NUM)
+               printf("  %s", ina);
+       else {
+               char *n = NULL;
+               if (from->sin_addr.s_addr != INADDR_ANY)
+                       n = xmalloc_sockaddr2host_noport((struct sockaddr*)from);
+               printf("  %s (%s)", (n ? n : ina), ina);
+               free(n);
+       }
+}
+
+static void
+print(int cc, const struct sockaddr_in *from)
+{
+       print_inetname(from);
+       if (verbose) {
+               const struct ip *ip;
+               int hlen;
+
+               ip = (struct ip *) recv_pkt;
+               hlen = ip->ip_hl << 2;
+               cc -= hlen;
+               printf(" %d bytes to %s", cc, inet_ntoa(ip->ip_dst));
+       }
+}
+
+static void
+print_delta_ms(unsigned t1p, unsigned t2p)
+{
+       unsigned tt = t2p - t1p;
+       printf("  %u.%03u ms", tt / 1000, tt % 1000);
+}
+
+/*
+Usage: [-dFIlnrvx] [-g gateway] [-i iface] [-f first_ttl]
+[-m max_ttl] [ -p port] [-q nqueries] [-s src_addr] [-t tos]
+[-w waittime] [-z pausemsecs] host [packetlen]"
+*/
+
+int traceroute_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int traceroute_main(int argc, char **argv)
+{
+       int minpacket;
+       int ttl, i;
+       int seq = 0;
+       int tos = 0;
+       int max_ttl = 30;
+       int nprobes = 3;
+       int first_ttl = 1;
+       unsigned pausemsecs = 0;
+       unsigned op;
+       char *source;
+       char *device;
+       char *tos_str;
+       char *max_ttl_str;
+       char *port_str;
+       char *nprobes_str;
+       char *waittime_str;
+       char *pausemsecs_str;
+       char *first_ttl_str;
+#if ENABLE_FEATURE_TRACEROUTE_SOURCE_ROUTE
+       llist_t *source_route_list = NULL;
+       int lsrr = 0;
+#endif
+
+       INIT_G();
+
+#if ENABLE_FEATURE_TRACEROUTE_SOURCE_ROUTE
+       opt_complementary = "x-x:g::";
+#else
+       opt_complementary = "x-x";
+#endif
+
+       op = getopt32(argv, "FIlnrdvxt:i:m:p:q:s:w:z:f:"
+#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
+               , &source_route_list
+#endif
+       );
+
+#if 0 /* IGNORED */
+       if (op & OPT_IP_CHKSUM)
+               bb_error_msg("warning: ip checksums disabled");
+#endif
+       if (op & OPT_TOS)
+               tos = xatou_range(tos_str, 0, 255);
+       if (op & OPT_MAX_TTL)
+               max_ttl = xatou_range(max_ttl_str, 1, 255);
+       if (op & OPT_PORT)
+               port = xatou16(port_str);
+       if (op & OPT_NPROBES)
+               nprobes = xatou_range(nprobes_str, 1, INT_MAX);
+       if (op & OPT_SOURCE) {
+               /*
+                * set the ip source address of the outbound
+                * probe (e.g., on a multi-homed host).
+                */
+               if (getuid() != 0)
+                       bb_error_msg_and_die("you must be root to use -s");
+       }
+       if (op & OPT_WAITTIME)
+               waittime = xatou_range(waittime_str, 1, 24 * 60 * 60);
+       if (op & OPT_PAUSE_MS)
+               pausemsecs = xatou_range(pausemsecs_str, 0, 60 * 60 * 1000);
+       if (op & OPT_FIRST_TTL)
+               first_ttl = xatou_range(first_ttl_str, 1, max_ttl);
+
+#if ENABLE_FEATURE_TRACEROUTE_SOURCE_ROUTE
+       if (source_route_list) {
+               while (source_route_list) {
+                       len_and_sockaddr *lsa;
+
+                       if (lsrr >= NGATEWAYS)
+                               bb_error_msg_and_die("no more than %d gateways", NGATEWAYS);
+                       lsa = xhost_and_af2sockaddr(llist_pop(&source_route_list), 0, AF_INET);
+                       gwlist[lsrr] = lsa->u.sin.sin_addr.s_addr;
+                       free(lsa);
+                       ++lsrr;
+               }
+               optlen = (lsrr + 1) * sizeof(gwlist[0]);
+       }
+#endif
+
+       minpacket = sizeof(*outip) + SIZEOF_ICMP_HDR + sizeof(*outdata) + optlen;
+       if (!(op & OPT_USE_ICMP))
+               minpacket += sizeof(*outudp) - SIZEOF_ICMP_HDR;
+       packlen = minpacket;
+
+       /* Process destination and optional packet size */
+       argv += optind;
+       argc -= optind;
+       switch (argc) {
+       case 2:
+               packlen = xatoul_range(argv[1], minpacket, 32 * 1024);
+               /* Fall through */
+       case 1:
+               dest_lsa = xhost2sockaddr(argv[0], port);
+               break;
+       default:
+               bb_show_usage();
+       }
+
+       /* Ensure the socket fds won't be 0, 1 or 2 */
+       bb_sanitize_stdio();
+
+       xmove_fd(xsocket(AF_INET, SOCK_RAW, IPPROTO_ICMP), rcvsock);
+#if TRACEROUTE_SO_DEBUG
+       if (op & OPT_DEBUG)
+               setsockopt(rcvsock, SOL_SOCKET, SO_DEBUG,
+                               &const_int_1, sizeof(const_int_1));
+#endif
+       if (op & OPT_BYPASS_ROUTE)
+               setsockopt(rcvsock, SOL_SOCKET, SO_DONTROUTE,
+                               &const_int_1, sizeof(const_int_1));
+
+       if (op & OPT_USE_ICMP)
+               xmove_fd(xsocket(AF_INET, SOCK_RAW, IPPROTO_ICMP), sndsock);
+       else
+               xmove_fd(xsocket(AF_INET, SOCK_DGRAM, 0), sndsock);
+#if ENABLE_FEATURE_TRACEROUTE_SOURCE_ROUTE
+#if defined(IP_OPTIONS)
+       if (lsrr > 0) {
+               unsigned char optlist[MAX_IPOPTLEN];
+
+               /* final hop */
+               gwlist[lsrr] = dest_lsa->u.sin.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, &packlen, sizeof(packlen)) < 0) {
+               bb_perror_msg_and_die("SO_SNDBUF");
+       }
+#endif
+#ifdef IP_TOS
+       if ((op & OPT_TOS) && setsockopt(sndsock, IPPROTO_IP, IP_TOS, &tos, sizeof(tos)) < 0) {
+               bb_perror_msg_and_die("setsockopt tos %d", tos);
+       }
+#endif
+#ifdef IP_DONTFRAG
+       if (op & OPT_DONT_FRAGMNT)
+               setsockopt(sndsock, IPPROTO_IP, IP_DONTFRAG,
+                               &const_int_1, sizeof(const_int_1));
+#endif
+#if TRACEROUTE_SO_DEBUG
+       if (op & OPT_DEBUG)
+               setsockopt(sndsock, SOL_SOCKET, SO_DEBUG,
+                               &const_int_1, sizeof(const_int_1));
+#endif
+       if (op & OPT_BYPASS_ROUTE)
+               setsockopt(sndsock, SOL_SOCKET, SO_DONTROUTE,
+                               &const_int_1, sizeof(const_int_1));
+
+       outip = xzalloc(packlen);
+
+       if (op & OPT_USE_ICMP) {
+               ident = getpid() | 0x8000;
+               outicmp->icmp_type = ICMP_ECHO;
+               outicmp->icmp_id = htons(ident);
+               outdata = (struct outdata_t *)((char *)outicmp + SIZEOF_ICMP_HDR);
+       } else {
+               outdata = (struct outdata_t *)(outudp + 1);
+       }
+
+       if (op & OPT_DEVICE) /* hmm, do we need error check? */
+               setsockopt_bindtodevice(sndsock, device);
+
+       if (op & OPT_SOURCE) {
+               len_and_sockaddr *source_lsa = xdotted2sockaddr(source, 0);
+               /* Ping does this (why?) */
+               if (setsockopt(sndsock, IPPROTO_IP, IP_MULTICAST_IF,
+                               &source_lsa->u.sa, source_lsa->len))
+                       bb_error_msg_and_die("can't set multicast source interface");
+//TODO: we can query source port we bound to,
+// and check it in replies... if we care enough
+               xbind(sndsock, &source_lsa->u.sa, source_lsa->len);
+               free(source_lsa);
+       }
+
+       /* Revert to non-privileged user after opening sockets */
+       xsetgid(getgid());
+       xsetuid(getuid());
+
+       printf("traceroute to %s (%s)", argv[0],
+                       xmalloc_sockaddr2dotted_noport(&dest_lsa->u.sa));
+       if (op & OPT_SOURCE)
+               printf(" from %s", source);
+       printf(", %d hops max, %d byte packets\n", max_ttl, packlen);
+
+       for (ttl = first_ttl; ttl <= max_ttl; ++ttl) {
+//TODO: make it protocol agnostic (get rid of sockaddr_in)
+               struct sockaddr_in from;
+               uint32_t lastaddr = 0;
+               int probe;
+               int unreachable = 0; /* counter */
+               int gotlastaddr = 0; /* flags */
+               int got_there = 0;
+               int first = 1;
+
+               printf("%2d", ttl);
+               for (probe = 0; probe < nprobes; ++probe) {
+                       int cc;
+                       unsigned t1;
+                       unsigned t2;
+                       struct ip *ip;
+
+                       if (!first && pausemsecs > 0)
+                               usleep(pausemsecs * 1000);
+                       fflush(stdout);
+
+                       t1 = monotonic_us();
+                       send_probe(++seq, ttl);
+                       first = 0;
+
+                       while ((cc = wait_for_reply(&from)) != 0) {
+                               t2 = monotonic_us();
+                               i = packet_ok(cc, &from, seq);
+                               /* Skip short packet */
+                               if (i == 0)
+                                       continue;
+                               if (!gotlastaddr
+                                || from.sin_addr.s_addr != lastaddr
+                               ) {
+                                       print(cc, &from);
+                                       lastaddr = from.sin_addr.s_addr;
+                                       gotlastaddr = 1;
+                               }
+                               print_delta_ms(t1, t2);
+                               ip = (struct ip *)recv_pkt;
+                               if (op & OPT_TTL_FLAG)
+                                       printf(" (%d)", ip->ip_ttl);
+                               if (i == -2) {
+                                       if (ip->ip_ttl <= 1)
+                                               printf(" !");
+                                       got_there = 1;
+                                       break;
+                               }
+                               /* time exceeded in transit */
+                               if (i == -1)
+                                       break;
+                               i--;
+                               switch (i) {
+                               case ICMP_UNREACH_PORT:
+                                       if (ip->ip_ttl <= 1)
+                                               printf(" !");
+                                       got_there = 1;
+                                       break;
+                               case ICMP_UNREACH_NET:
+                                       printf(" !N");
+                                       ++unreachable;
+                                       break;
+                               case ICMP_UNREACH_HOST:
+                                       printf(" !H");
+                                       ++unreachable;
+                                       break;
+                               case ICMP_UNREACH_PROTOCOL:
+                                       printf(" !P");
+                                       got_there = 1;
+                                       break;
+                               case ICMP_UNREACH_NEEDFRAG:
+                                       printf(" !F-%d", pmtu);
+                                       ++unreachable;
+                                       break;
+                               case ICMP_UNREACH_SRCFAIL:
+                                       printf(" !S");
+                                       ++unreachable;
+                                       break;
+                               case ICMP_UNREACH_FILTER_PROHIB:
+                               case ICMP_UNREACH_NET_PROHIB:   /* misuse */
+                                       printf(" !A");
+                                       ++unreachable;
+                                       break;
+                               case ICMP_UNREACH_HOST_PROHIB:
+                                       printf(" !C");
+                                       ++unreachable;
+                                       break;
+                               case ICMP_UNREACH_HOST_PRECEDENCE:
+                                       printf(" !V");
+                                       ++unreachable;
+                                       break;
+                               case ICMP_UNREACH_PRECEDENCE_CUTOFF:
+                                       printf(" !C");
+                                       ++unreachable;
+                                       break;
+                               case ICMP_UNREACH_NET_UNKNOWN:
+                               case ICMP_UNREACH_HOST_UNKNOWN:
+                                       printf(" !U");
+                                       ++unreachable;
+                                       break;
+                               case ICMP_UNREACH_ISOLATED:
+                                       printf(" !I");
+                                       ++unreachable;
+                                       break;
+                               case ICMP_UNREACH_TOSNET:
+                               case ICMP_UNREACH_TOSHOST:
+                                       printf(" !T");
+                                       ++unreachable;
+                                       break;
+                               default:
+                                       printf(" !<%d>", i);
+                                       ++unreachable;
+                                       break;
+                               }
+                               break;
+                       }
+                       if (cc == 0)
+                               printf("  *");
+               }
+               bb_putchar('\n');
+               if (got_there
+                || (unreachable > 0 && unreachable >= nprobes - 1)
+               ) {
+                       break;
+               }
+       }
+       return 0;
+}
diff --git a/networking/tunctl.c b/networking/tunctl.c
new file mode 100644 (file)
index 0000000..a8e5270
--- /dev/null
@@ -0,0 +1,139 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * tun devices controller
+ *
+ * Copyright (C) 2008 by Vladimir Dronnikov <dronnikov@gmail.com>
+ *
+ * Original code:
+ *      Jeff Dike
+ *
+ * Licensed under GPLv2, see file LICENSE in this tarball for details.
+ */
+#include <netinet/in.h>
+#include <net/if.h>
+#include <linux/if_tun.h>
+#include "libbb.h"
+
+/* TUNSETGROUP appeared in 2.6.23 */
+#ifndef TUNSETGROUP
+#define TUNSETGROUP _IOW('T', 206, int)
+#endif
+
+#define IOCTL(a, b, c) ioctl_or_perror_and_die(a, b, c, NULL)
+
+#if 1
+
+int tunctl_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int tunctl_main(int argc UNUSED_PARAM, char **argv)
+{
+       struct ifreq ifr;
+       int fd;
+       const char *opt_name = "tap%d";
+       const char *opt_device = "/dev/net/tun";
+#if ENABLE_FEATURE_TUNCTL_UG
+       const char *opt_user, *opt_group;
+       long user = -1, group = -1;
+#endif
+       unsigned opts;
+
+       enum {
+               OPT_f = 1 << 0, // control device name (/dev/net/tun)
+               OPT_t = 1 << 1, // create named interface
+               OPT_d = 1 << 2, // delete named interface
+#if ENABLE_FEATURE_TUNCTL_UG
+               OPT_u = 1 << 3, // set new interface owner
+               OPT_g = 1 << 4, // set new interface group
+               OPT_b = 1 << 5, // brief output
+#endif
+       };
+
+       opt_complementary = "=0:t--d:d--t"; // no arguments; t ^ d
+       opts = getopt32(argv, "f:t:d:" USE_FEATURE_TUNCTL_UG("u:g:b"),
+                       &opt_device, &opt_name, &opt_name
+                       USE_FEATURE_TUNCTL_UG(, &opt_user, &opt_group));
+
+       // select device
+       memset(&ifr, 0, sizeof(ifr));
+       ifr.ifr_flags = IFF_TAP | IFF_NO_PI;
+       strncpy_IFNAMSIZ(ifr.ifr_name, opt_name);
+
+       // open device
+       fd = xopen(opt_device, O_RDWR);
+       IOCTL(fd, TUNSETIFF, (void *)&ifr);
+
+       // delete?
+       if (opts & OPT_d) {
+               IOCTL(fd, TUNSETPERSIST, (void *)(uintptr_t)0);
+               bb_info_msg("Set '%s' %spersistent", ifr.ifr_name, "non");
+               return EXIT_SUCCESS;
+       }
+
+       // create
+#if ENABLE_FEATURE_TUNCTL_UG
+       if (opts & OPT_g) {
+               group = xgroup2gid(opt_group);
+               IOCTL(fd, TUNSETGROUP, (void *)(uintptr_t)group);
+       } else
+               user = geteuid();
+       if (opts & OPT_u)
+               user = xuname2uid(opt_user);
+       IOCTL(fd, TUNSETOWNER, (void *)(uintptr_t)user);
+#endif
+       IOCTL(fd, TUNSETPERSIST, (void *)(uintptr_t)1);
+
+       // show info
+#if ENABLE_FEATURE_TUNCTL_UG
+       if (opts & OPT_b) {
+               puts(ifr.ifr_name);
+       } else {
+               printf("Set '%s' %spersistent", ifr.ifr_name, "");
+               printf(" and owned by uid %ld", user);
+               if (group != -1)
+                       printf(" gid %ld", group);
+               bb_putchar('\n');
+       }
+#else
+       puts(ifr.ifr_name);
+#endif
+       return EXIT_SUCCESS;
+}
+
+#else
+
+/* -210 bytes: */
+
+int tunctl_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int tunctl_main(int argc UNUSED_PARAM, char **argv)
+{
+       struct ifreq ifr;
+       int fd;
+       const char *opt_name = "tap%d";
+       const char *opt_device = "/dev/net/tun";
+       unsigned opts;
+
+       enum {
+               OPT_f = 1 << 0, // control device name (/dev/net/tun)
+               OPT_t = 1 << 1, // create named interface
+               OPT_d = 1 << 2, // delete named interface
+       };
+
+       opt_complementary = "=0:t--d:d--t"; // no arguments; t ^ d
+       opts = getopt32(argv, "f:t:d:u:g:b", // u, g, b accepted and ignored
+                       &opt_device, &opt_name, &opt_name, NULL, NULL);
+
+       // set interface name
+       memset(&ifr, 0, sizeof(ifr));
+       ifr.ifr_flags = IFF_TAP | IFF_NO_PI;
+       strncpy_IFNAMSIZ(ifr.ifr_name, opt_name);
+
+       // open device
+       fd = xopen(opt_device, O_RDWR);
+       IOCTL(fd, TUNSETIFF, (void *)&ifr);
+
+       // create or delete interface
+       IOCTL(fd, TUNSETPERSIST, (void *)(uintptr_t)(0 == (opts & OPT_d)));
+
+       return EXIT_SUCCESS;
+}
+
+#endif
diff --git a/networking/udhcp/Config.in b/networking/udhcp/Config.in
new file mode 100644 (file)
index 0000000..d4b76e1
--- /dev/null
@@ -0,0 +1,122 @@
+#
+# 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.
+
+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.
+
+config FEATURE_UDHCPD_WRITE_LEASES_EARLY
+       bool "Rewrite the lease file at every new acknowledge"
+       default n
+       depends on APP_UDHCPD
+       help
+         If selected, udhcpd will write a new file with leases every
+         time a new lease has been accepted, thus eliminating the need
+         to send SIGUSR1 for the initial writing or updating. Any timed
+         rewriting remains undisturbed
+
+config DHCPD_LEASES_FILE
+       string "Absolute path to lease file"
+       default "/var/lib/misc/udhcpd.leases"
+       depends on APP_UDHCPD
+       help
+         udhcpd stores addresses in a lease file. This is the absolute path
+         of the file. Normally it is safe to leave it untouched.
+
+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
+         runs a script when a lease is obtained or lost.
+
+config FEATURE_UDHCPC_ARPING
+       bool "Verify that the offered address is free, using ARP ping"
+       default y
+       depends on APP_UDHCPC
+       help
+         If selected, udhcpc will send ARP probes and make sure
+         the offered address is really not in use by anyone. The client
+         will DHCPDECLINE the offer if the address is in use,
+         and restart the discover process.
+
+config FEATURE_UDHCP_PORT
+       bool "Enable '-P port' option for udhcpd and udhcpc"
+       default n
+       depends on APP_UDHCPD || APP_UDHCPC
+       help
+         At the cost of ~300 bytes, enables -P port option.
+         This feature is typically not needed.
+
+config 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.
+
+config FEATURE_UDHCP_RFC3397
+       bool "Support for RFC3397 domain search (experimental)"
+       default n
+       depends on APP_UDHCPD || APP_UDHCPC
+       help
+         If selected, both client and server will support passing of domain
+         search lists via option 119, specified in RFC3397.
+
+config UDHCPC_DEFAULT_SCRIPT
+       string "Absolute path to config script"
+       default "/usr/share/udhcpc/default.script"
+       depends on APP_UDHCPC
+       help
+         This script is called after udhcpc receives an answer. See
+         examples/udhcp for a working example. Normally it is safe
+         to leave this untouched.
+
+config UDHCPC_SLACK_FOR_BUGGY_SERVERS
+       int "DHCP options slack buffer size"
+       default 80
+       range 0 924
+       depends on APP_UDHCPD || APP_UDHCPC
+       help
+         Some buggy DHCP servers send DHCP offer packets with option
+         field larger than we expect (which might also be considered a
+         buffer overflow attempt). These packets are normally discarded.
+         If circumstances beyond your control force you to support such
+         servers, this may help. The upper limit (924) makes dhcpc accept
+         even 1500 byte packets (maximum-sized ethernet packets).
+
+         This option does not make dhcp[cd] emit non-standard
+         sized packets.
+
+         Known buggy DHCP servers:
+         3Com OfficeConnect Remote 812 ADSL Router:
+           seems to confuse maximum allowed UDP packet size with
+           maximum size of entire IP packet, and sends packets which are
+           28 bytes too large.
+         Seednet (ISP) VDSL: sends packets 2 bytes too large.
diff --git a/networking/udhcp/Kbuild b/networking/udhcp/Kbuild
new file mode 100644 (file)
index 0000000..e938076
--- /dev/null
@@ -0,0 +1,25 @@
+# Makefile for busybox
+#
+# Copyright (C) 1999-2004 by Erik Andersen <andersen@codepoet.org>
+#
+# 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 \
+                                   signalpipe.o socket.o
+lib-$(CONFIG_APP_UDHCPD)        += common.o options.o packet.o \
+                                   signalpipe.o socket.o
+
+lib-$(CONFIG_APP_UDHCPC)        += dhcpc.o clientpacket.o clientsocket.o \
+                                   script.o
+
+UDHCPC_NEEDS_ARPING-$(CONFIG_FEATURE_UDHCPC_ARPING) = y
+lib-$(UDHCPC_NEEDS_ARPING-y)    += arpping.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
+lib-$(CONFIG_FEATURE_UDHCP_RFC3397) += domain_codec.o
diff --git a/networking/udhcp/arpping.c b/networking/udhcp/arpping.c
new file mode 100644 (file)
index 0000000..b10bff6
--- /dev/null
@@ -0,0 +1,118 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * arpping.c
+ *
+ * Mostly stolen from: dhcpcd - DHCP client daemon
+ * by Yoichi Hariguchi <yoichi@fore.com>
+ *
+ * Licensed under GPLv2, see file LICENSE in this tarball for details.
+ */
+
+#include <netinet/if_ether.h>
+#include <net/if_arp.h>
+
+#include "common.h"
+#include "dhcpd.h"
+
+
+struct arpMsg {
+       /* Ethernet header */
+       uint8_t  h_dest[6];     /* 00 destination ether addr */
+       uint8_t  h_source[6];   /* 06 source ether addr */
+       uint16_t h_proto;       /* 0c packet type ID field */
+
+       /* ARP packet */
+       uint16_t htype;         /* 0e hardware type (must be ARPHRD_ETHER) */
+       uint16_t ptype;         /* 10 protocol type (must be ETH_P_IP) */
+       uint8_t  hlen;          /* 12 hardware address length (must be 6) */
+       uint8_t  plen;          /* 13 protocol address length (must be 4) */
+       uint16_t operation;     /* 14 ARP opcode */
+       uint8_t  sHaddr[6];     /* 16 sender's hardware address */
+       uint8_t  sInaddr[4];    /* 1c sender's IP address */
+       uint8_t  tHaddr[6];     /* 20 target's hardware address */
+       uint8_t  tInaddr[4];    /* 26 target's IP address */
+       uint8_t  pad[18];       /* 2a pad for min. ethernet payload (60 bytes) */
+} PACKED;
+
+enum {
+       ARP_MSG_SIZE = 0x2a
+};
+
+
+/* Returns 1 if no reply received */
+
+int FAST_FUNC arpping(uint32_t test_ip, uint32_t from_ip, uint8_t *from_mac, const char *interface)
+{
+       int timeout_ms;
+       struct pollfd pfd[1];
+#define s (pfd[0].fd)           /* socket */
+       int rv = 1;             /* "no reply received" yet */
+       struct sockaddr addr;   /* for interface name */
+       struct arpMsg arp;
+
+       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 enable bcast on raw socket");
+               goto ret;
+       }
+
+       /* send arp request */
+       memset(&arp, 0, sizeof(arp));
+       memset(arp.h_dest, 0xff, 6);                    /* MAC DA */
+       memcpy(arp.h_source, from_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.sHaddr, from_mac, 6);                /* source hardware address */
+       memcpy(arp.sInaddr, &from_ip, sizeof(from_ip)); /* source IP address */
+       /* tHaddr is zero-fiiled */                     /* target hardware address */
+       memcpy(arp.tInaddr, &test_ip, sizeof(test_ip)); /* target IP address */
+
+       memset(&addr, 0, sizeof(addr));
+       safe_strncpy(addr.sa_data, interface, sizeof(addr.sa_data));
+       if (sendto(s, &arp, sizeof(arp), 0, &addr, sizeof(addr)) < 0) {
+               // TODO: error message? caller didn't expect us to fail,
+               // just returning 1 "no reply received" misleads it.
+               goto ret;
+       }
+
+       /* wait for arp reply, and check it */
+       timeout_ms = 2000;
+       do {
+               int r;
+               unsigned prevTime = monotonic_us();
+
+               pfd[0].events = POLLIN;
+               r = safe_poll(pfd, 1, timeout_ms);
+               if (r < 0)
+                       break;
+               if (r) {
+                       r = read(s, &arp, sizeof(arp));
+                       if (r < 0)
+                               break;
+                       if (r >= ARP_MSG_SIZE
+                        && arp.operation == htons(ARPOP_REPLY)
+                        /* don't check it: Linux doesn't return proper tHaddr (fixed in 2.6.24?) */
+                        /* && memcmp(arp.tHaddr, from_mac, 6) == 0 */
+                        && *((uint32_t *) arp.sInaddr) == test_ip
+                       ) {
+                               rv = 0;
+                               break;
+                       }
+               }
+               timeout_ms -= ((unsigned)monotonic_us() - prevTime) / 1000;
+       } while (timeout_ms > 0);
+
+ ret:
+       close(s);
+       DEBUG("%srp reply received for this address", rv ? "No a" : "A");
+       return rv;
+}
diff --git a/networking/udhcp/clientpacket.c b/networking/udhcp/clientpacket.c
new file mode 100644 (file)
index 0000000..3f9522f
--- /dev/null
@@ -0,0 +1,271 @@
+/* vi: set sw=4 ts=4: */
+/* clientpacket.c
+ *
+ * Packet generation and dispatching functions for the DHCP client.
+ *
+ * Russ Dill <Russ.Dill@asu.edu> July 2001
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+#include <features.h>
+#if (defined(__GLIBC__) && __GLIBC__ >= 2 && __GLIBC_MINOR__ >= 1) || defined _NEWLIB_VERSION
+#include <netpacket/packet.h>
+#include <net/ethernet.h>
+#else
+#include <asm/types.h>
+#include <linux/if_packet.h>
+#include <linux/if_ether.h>
+#endif
+
+#include "common.h"
+#include "dhcpd.h"
+#include "dhcpc.h"
+#include "options.h"
+
+
+/* Create a random xid */
+uint32_t FAST_FUNC random_xid(void)
+{
+       static smallint initialized;
+
+       if (!initialized) {
+               srand(monotonic_us());
+               initialized = 1;
+       }
+       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);
+       if ((type != DHCPDECLINE) && (type != DHCPRELEASE))
+               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_param_req_option(struct dhcpMessage *packet)
+{
+       uint8_t c;
+       int end = end_option(packet->options);
+       int i, len = 0;
+
+       for (i = 0; (c = dhcp_options[i].code) != 0; i++) {
+               if (((dhcp_options[i].flags & OPTION_REQ)
+                    && !client_config.no_default_options)
+                || (client_config.opt_mask[c >> 3] & (1 << (c & 7)))
+               ) {
+                       packet->options[end + OPT_DATA + len] = c;
+                       len++;
+               }
+       }
+       if (len) {
+               packet->options[end + OPT_CODE] = DHCP_PARAM_REQ;
+               packet->options[end + OPT_LEN] = len;
+               packet->options[end + OPT_DATA + len] = DHCP_END;
+       }
+}
+
+/* RFC 2131
+ * 4.4.4 Use of broadcast and unicast
+ *
+ * The DHCP client broadcasts DHCPDISCOVER, DHCPREQUEST and DHCPINFORM
+ * messages, unless the client knows the address of a DHCP server.
+ * The client unicasts DHCPRELEASE messages to the server. Because
+ * the client is declining the use of the IP address supplied by the server,
+ * the client broadcasts DHCPDECLINE messages.
+ *
+ * When the DHCP client knows the address of a DHCP server, in either
+ * INIT or REBOOTING state, the client may use that address
+ * in the DHCPDISCOVER or DHCPREQUEST rather than the IP broadcast address.
+ * The client may also use unicast to send DHCPINFORM messages
+ * to a known DHCP server. If the client receives no response to DHCP
+ * messages sent to the IP address of a known DHCP server, the DHCP
+ * client reverts to using the IP broadcast address.
+ */
+
+static int raw_bcast_from_client_config_ifindex(struct dhcpMessage *packet)
+{
+       return udhcp_send_raw_packet(packet,
+               /*src*/ INADDR_ANY, CLIENT_PORT,
+               /*dst*/ INADDR_BROADCAST, SERVER_PORT, MAC_BCAST_ADDR,
+               client_config.ifindex);
+}
+
+
+#if ENABLE_FEATURE_UDHCPC_ARPING
+/* Broadcast a DHCP decline message */
+int FAST_FUNC send_decline(uint32_t xid, uint32_t server, uint32_t requested)
+{
+       struct dhcpMessage packet;
+
+       init_packet(&packet, DHCPDECLINE);
+       packet.xid = xid;
+       add_simple_option(packet.options, DHCP_REQUESTED_IP, requested);
+       add_simple_option(packet.options, DHCP_SERVER_ID, server);
+
+       bb_info_msg("Sending decline...");
+
+       return raw_bcast_from_client_config_ifindex(&packet);
+}
+#endif
+
+/* Broadcast a DHCP discover packet to the network, with an optionally requested IP */
+int FAST_FUNC send_discover(uint32_t xid, uint32_t requested)
+{
+       struct dhcpMessage packet;
+
+       init_packet(&packet, DHCPDISCOVER);
+       packet.xid = xid;
+       if (requested)
+               add_simple_option(packet.options, DHCP_REQUESTED_IP, requested);
+
+       /* Explicitly saying that we want RFC-compliant packets helps
+        * some buggy DHCP servers to NOT send bigger packets */
+       add_simple_option(packet.options, DHCP_MAX_SIZE, htons(576));
+
+       add_param_req_option(&packet);
+
+       bb_info_msg("Sending discover...");
+       return raw_bcast_from_client_config_ifindex(&packet);
+}
+
+
+/* Broadcasts a DHCP request message */
+/* RFC 2131 3.1 paragraph 3:
+ * "The client _broadcasts_ a DHCPREQUEST message..."
+ */
+int FAST_FUNC send_select(uint32_t xid, uint32_t server, uint32_t 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_param_req_option(&packet);
+
+       addr.s_addr = requested;
+       bb_info_msg("Sending select for %s...", inet_ntoa(addr));
+       return raw_bcast_from_client_config_ifindex(&packet);
+}
+
+
+/* Unicasts or broadcasts a DHCP renew message */
+int FAST_FUNC send_renew(uint32_t xid, uint32_t server, uint32_t ciaddr)
+{
+       struct dhcpMessage packet;
+
+       init_packet(&packet, DHCPREQUEST);
+       packet.xid = xid;
+       packet.ciaddr = ciaddr;
+
+       add_param_req_option(&packet);
+       bb_info_msg("Sending renew...");
+       if (server)
+               return udhcp_send_kernel_packet(&packet,
+                       ciaddr, CLIENT_PORT,
+                       server, SERVER_PORT);
+
+       return raw_bcast_from_client_config_ifindex(&packet);
+}
+
+
+/* Unicasts a DHCP release message */
+int FAST_FUNC send_release(uint32_t server, uint32_t ciaddr)
+{
+       struct dhcpMessage packet;
+
+       init_packet(&packet, DHCPRELEASE);
+       packet.xid = random_xid();
+       packet.ciaddr = ciaddr;
+
+       add_simple_option(packet.options, DHCP_SERVER_ID, server);
+
+       bb_info_msg("Sending release...");
+       return udhcp_send_kernel_packet(&packet, ciaddr, CLIENT_PORT, server, SERVER_PORT);
+}
+
+
+/* Returns -1 on errors that are fatal for the socket, -2 for those that aren't */
+int FAST_FUNC udhcp_recv_raw_packet(struct dhcpMessage *payload, int fd)
+{
+       int bytes;
+       struct udp_dhcp_packet packet;
+       uint16_t check;
+
+       memset(&packet, 0, sizeof(packet));
+       bytes = safe_read(fd, &packet, sizeof(packet));
+       if (bytes < 0) {
+               DEBUG("Cannot read on raw listening socket - ignoring");
+               /* NB: possible down interface, etc. Caller should pause. */
+               return bytes; /* returns -1 */
+       }
+
+       if (bytes < (int) (sizeof(packet.ip) + sizeof(packet.udp))) {
+               DEBUG("Packet is too short, ignoring");
+               return -2;
+       }
+
+       if (bytes < ntohs(packet.ip.tot_len)) {
+               /* packet is bigger than sizeof(packet), we did partial read */
+               DEBUG("Oversized packet, ignoring");
+               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(packet) - can't happen */
+        || ntohs(packet.udp.len) != (uint16_t)(bytes - sizeof(packet.ip))
+       ) {
+               DEBUG("Unrelated/bogus packet");
+               return -2;
+       }
+
+       /* verify 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 -2;
+       }
+
+       /* verify UDP checksum. IP header has to be modified for this */
+       memset(&packet.ip, 0, offsetof(struct iphdr, protocol));
+       /* ip.xx fields which are not memset: protocol, check, saddr, daddr */
+       packet.ip.tot_len = packet.udp.len; /* yes, this is needed */
+       check = packet.udp.check;
+       packet.udp.check = 0;
+       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 (payload->cookie != htonl(DHCP_MAGIC)) {
+               bb_error_msg("received bogus message (bad magic), ignoring");
+               return -2;
+       }
+       DEBUG("Got valid DHCP packet");
+       return bytes - (sizeof(packet.ip) + sizeof(packet.udp));
+}
diff --git a/networking/udhcp/clientsocket.c b/networking/udhcp/clientsocket.c
new file mode 100644 (file)
index 0000000..1dcc105
--- /dev/null
@@ -0,0 +1,108 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * clientsocket.c -- DHCP client socket creation
+ *
+ * udhcp client
+ *
+ * Russ Dill <Russ.Dill@asu.edu> 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 <features.h>
+#include <asm/types.h>
+#if (defined(__GLIBC__) && __GLIBC__ >= 2 && __GLIBC_MINOR__ >= 1) || defined(_NEWLIB_VERSION)
+#include <netpacket/packet.h>
+#include <net/ethernet.h>
+#else
+#include <linux/if_packet.h>
+#include <linux/if_ether.h>
+#endif
+#include <linux/filter.h>
+
+#include "common.h"
+#include "dhcpd.h"
+#include "dhcpc.h"
+
+int FAST_FUNC udhcp_raw_socket(int ifindex)
+{
+       int fd;
+       struct sockaddr_ll sock;
+
+       /*
+        * Comment:
+        *
+        *      I've selected not to see LL header, so BPF doesn't see it, too.
+        *      The filter may also pass non-IP and non-ARP packets, but we do
+        *      a more complete check when receiving the message in userspace.
+        *
+        * and filter shamelessly stolen from:
+        *
+        *      http://www.flamewarmaster.de/software/dhcpclient/
+        *
+        * There are a few other interesting ideas on that page (look under
+        * "Motivation").  Use of netlink events is most interesting.  Think
+        * of various network servers listening for events and reconfiguring.
+        * That would obsolete sending HUP signals and/or make use of restarts.
+        *
+        * Copyright: 2006, 2007 Stefan Rompf <sux@loplof.de>.
+        * License: GPL v2.
+        *
+        * TODO: make conditional?
+        */
+#define SERVER_AND_CLIENT_PORTS  ((67 << 16) + 68)
+       static const struct sock_filter filter_instr[] = {
+               /* check for udp */
+               BPF_STMT(BPF_LD|BPF_B|BPF_ABS, 9),
+               BPF_JUMP(BPF_JMP|BPF_JEQ|BPF_K, IPPROTO_UDP, 2, 0),     /* L5, L1, is UDP? */
+               /* ugly check for arp on ethernet-like and IPv4 */
+               BPF_STMT(BPF_LD|BPF_W|BPF_ABS, 2),                      /* L1: */
+               BPF_JUMP(BPF_JMP|BPF_JEQ|BPF_K, 0x08000604, 3, 4),      /* L3, L4 */
+               /* skip IP header */
+               BPF_STMT(BPF_LDX|BPF_B|BPF_MSH, 0),                     /* L5: */
+               /* check udp source and destination ports */
+               BPF_STMT(BPF_LD|BPF_W|BPF_IND, 0),
+               BPF_JUMP(BPF_JMP|BPF_JEQ|BPF_K, SERVER_AND_CLIENT_PORTS, 0, 1), /* L3, L4 */
+               /* returns */
+               BPF_STMT(BPF_RET|BPF_K, 0x0fffffff ),                   /* L3: pass */
+               BPF_STMT(BPF_RET|BPF_K, 0),                             /* L4: reject */
+       };
+       static const struct sock_fprog filter_prog = {
+               .len = sizeof(filter_instr) / sizeof(filter_instr[0]),
+               /* casting const away: */
+               .filter = (struct sock_filter *) filter_instr,
+       };
+
+       DEBUG("opening raw socket on ifindex %d", ifindex);
+
+       fd = xsocket(PF_PACKET, SOCK_DGRAM, htons(ETH_P_IP));
+       DEBUG("got raw socket fd %d", fd);
+
+       if (SERVER_PORT == 67 && CLIENT_PORT == 68) {
+               /* Use only if standard ports are in use */
+               /* Ignoring error (kernel may lack support for this) */
+               if (setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &filter_prog,
+                               sizeof(filter_prog)) >= 0)
+                       DEBUG("attached filter to raw socket fd %d", fd);
+       }
+
+       sock.sll_family = AF_PACKET;
+       sock.sll_protocol = htons(ETH_P_IP);
+       sock.sll_ifindex = ifindex;
+       xbind(fd, (struct sockaddr *) &sock, sizeof(sock));
+       DEBUG("bound to raw socket fd %d", fd);
+
+       return fd;
+}
diff --git a/networking/udhcp/common.c b/networking/udhcp/common.c
new file mode 100644 (file)
index 0000000..a47bbaf
--- /dev/null
@@ -0,0 +1,11 @@
+/* vi: set sw=4 ts=4: */
+/* common.c
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+#include "common.h"
+
+const uint8_t MAC_BCAST_ADDR[6] ALIGN2 = {
+       0xff, 0xff, 0xff, 0xff, 0xff, 0xff
+};
diff --git a/networking/udhcp/common.h b/networking/udhcp/common.h
new file mode 100644 (file)
index 0000000..5a258c0
--- /dev/null
@@ -0,0 +1,105 @@
+/* vi: set sw=4 ts=4: */
+/* common.h
+ *
+ * Russ Dill <Russ.Dill@asu.edu> September 2001
+ * Rewritten by Vladimir Oleynik <dzo@simtreas.ru> (C) 2003
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+#ifndef UDHCP_COMMON_H
+#define UDHCP_COMMON_H 1
+
+#include "libbb.h"
+#include <netinet/udp.h>
+#include <netinet/ip.h>
+
+PUSH_AND_SET_FUNCTION_VISIBILITY_TO_HIDDEN
+
+#define DEFAULT_SCRIPT   CONFIG_UDHCPC_DEFAULT_SCRIPT
+
+extern const uint8_t MAC_BCAST_ADDR[6]; /* six all-ones */
+
+/*** packet.h ***/
+
+#define DHCP_OPTIONS_BUFSIZE  308
+
+struct dhcpMessage {
+       uint8_t op;      /* 1 = BOOTREQUEST, 2 = BOOTREPLY */
+       uint8_t htype;   /* hardware address type. 1 = 10mb ethernet */
+       uint8_t hlen;    /* hardware address length */
+       uint8_t hops;    /* used by relay agents only */
+       uint32_t xid;    /* unique id */
+       uint16_t secs;   /* elapsed since client began acquisition/renewal */
+       uint16_t flags;  /* only one flag so far: */
+#define BROADCAST_FLAG 0x8000 /* "I need broadcast replies" */
+       uint32_t ciaddr; /* client IP (if client is in BOUND, RENEW or REBINDING state) */
+       uint32_t yiaddr; /* 'your' (client) IP address */
+       uint32_t siaddr; /* IP address of next server to use in bootstrap,
+                         * returned in DHCPOFFER, DHCPACK by server */
+       uint32_t giaddr; /* relay agent IP address */
+       uint8_t chaddr[16];/* link-layer client hardware address (MAC) */
+       uint8_t sname[64]; /* server host name (ASCIZ) */
+       uint8_t file[128]; /* boot file name (ASCIZ) */
+       uint32_t cookie;   /* fixed first four option bytes (99,130,83,99 dec) */
+       uint8_t options[DHCP_OPTIONS_BUFSIZE + CONFIG_UDHCPC_SLACK_FOR_BUGGY_SERVERS];
+} PACKED;
+
+struct udp_dhcp_packet {
+       struct iphdr ip;
+       struct udphdr udp;
+       struct dhcpMessage data;
+} PACKED;
+
+/* Let's see whether compiler understood us right */
+struct BUG_bad_sizeof_struct_udp_dhcp_packet {
+       char BUG_bad_sizeof_struct_udp_dhcp_packet
+               [(sizeof(struct udp_dhcp_packet) != 576 + CONFIG_UDHCPC_SLACK_FOR_BUGGY_SERVERS) ? -1 : 1];
+};
+
+uint16_t udhcp_checksum(void *addr, int count) FAST_FUNC;
+
+void udhcp_init_header(struct dhcpMessage *packet, char type) FAST_FUNC;
+
+/*int udhcp_recv_raw_packet(struct dhcpMessage *payload, int fd); - in dhcpc.h */
+int udhcp_recv_kernel_packet(struct dhcpMessage *packet, int fd) FAST_FUNC;
+
+int udhcp_send_raw_packet(struct dhcpMessage *payload,
+               uint32_t source_ip, int source_port,
+               uint32_t dest_ip, int dest_port, const uint8_t *dest_arp,
+               int ifindex) FAST_FUNC;
+
+int udhcp_send_kernel_packet(struct dhcpMessage *payload,
+               uint32_t source_ip, int source_port,
+               uint32_t dest_ip, int dest_port) FAST_FUNC;
+
+
+/**/
+
+void udhcp_run_script(struct dhcpMessage *packet, const char *name) FAST_FUNC;
+
+// Still need to clean these up...
+
+/* 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
+
+void udhcp_sp_setup(void) FAST_FUNC;
+int udhcp_sp_fd_set(fd_set *rfds, int extra_fd) FAST_FUNC;
+int udhcp_sp_read(const fd_set *rfds) FAST_FUNC;
+int udhcp_read_interface(const char *interface, int *ifindex, uint32_t *addr, uint8_t *arp) FAST_FUNC;
+int udhcp_raw_socket(int ifindex) FAST_FUNC;
+int udhcp_listen_socket(/*uint32_t ip,*/ int port, const char *inf) FAST_FUNC;
+/* Returns 1 if no reply received */
+int arpping(uint32_t test_ip, uint32_t from_ip, uint8_t *from_mac, const char *interface) FAST_FUNC;
+
+#if ENABLE_UDHCP_DEBUG
+# define DEBUG(str, args...) bb_info_msg("### " str, ## args)
+#else
+# define DEBUG(str, args...) do {;} while (0)
+#endif
+
+POP_SAVED_FUNCTION_VISIBILITY
+
+#endif
diff --git a/networking/udhcp/dhcpc.c b/networking/udhcp/dhcpc.c
new file mode 100644 (file)
index 0000000..e9f99e3
--- /dev/null
@@ -0,0 +1,656 @@
+/* vi: set sw=4 ts=4: */
+/* dhcpc.c
+ *
+ * udhcp DHCP client
+ *
+ * Russ Dill <Russ.Dill@asu.edu> July 2001
+ *
+ * Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
+ */
+
+#include <syslog.h>
+
+/* Override ENABLE_FEATURE_PIDFILE - ifupdown needs our pidfile to always exist */
+#define WANT_PIDFILE 1
+#include "common.h"
+#include "dhcpd.h"
+#include "dhcpc.h"
+#include "options.h"
+
+
+static int sockfd = -1;
+
+#define LISTEN_NONE 0
+#define LISTEN_KERNEL 1
+#define LISTEN_RAW 2
+static smallint listen_mode;
+
+#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
+static smallint state;
+
+/* struct client_config_t client_config is in bb_common_bufsiz1 */
+
+
+/* just a little helper */
+static void change_listen_mode(int new_mode)
+{
+       DEBUG("entering %s listen mode",
+               new_mode ? (new_mode == 1 ? "kernel" : "raw") : "none");
+
+       listen_mode = new_mode;
+       if (sockfd >= 0) {
+               close(sockfd);
+               sockfd = -1;
+       }
+       if (new_mode == LISTEN_KERNEL)
+               sockfd = udhcp_listen_socket(/*INADDR_ANY,*/ CLIENT_PORT, client_config.interface);
+       else if (new_mode != LISTEN_NONE)
+               sockfd = udhcp_raw_socket(client_config.ifindex);
+       /* else LISTEN_NONE: sockfd stay closed */
+}
+
+
+/* perform a renew */
+static void perform_renew(void)
+{
+       bb_info_msg("Performing a DHCP renew");
+       switch (state) {
+       case BOUND:
+               change_listen_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_listen_mode(LISTEN_RAW);
+               state = INIT_SELECTING;
+               break;
+       case INIT_SELECTING:
+               break;
+       }
+}
+
+
+/* perform a release */
+static void perform_release(uint32_t requested_ip, uint32_t server_addr)
+{
+       char buffer[sizeof("255.255.255.255")];
+       struct in_addr temp_addr;
+
+       /* send release packet */
+       if (state == BOUND || state == RENEWING || state == REBINDING) {
+               temp_addr.s_addr = server_addr;
+               strcpy(buffer, inet_ntoa(temp_addr));
+               temp_addr.s_addr = requested_ip;
+               bb_info_msg("Unicasting a release of %s to %s",
+                               inet_ntoa(temp_addr), buffer);
+               send_release(server_addr, requested_ip); /* unicast */
+               udhcp_run_script(NULL, "deconfig");
+       }
+       bb_info_msg("Entering released state");
+
+       change_listen_mode(LISTEN_NONE);
+       state = RELEASED;
+}
+
+
+#if BB_MMU
+static void client_background(void)
+{
+       bb_daemonize(0);
+       logmode &= ~LOGMODE_STDIO;
+       /* rewrite pidfile, as our pid is different now */
+       write_pidfile(client_config.pidfile);
+}
+#endif
+
+
+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) MAIN_EXTERNALLY_VISIBLE;
+int udhcpc_main(int argc UNUSED_PARAM, char **argv)
+{
+       uint8_t *temp, *message;
+       char *str_c, *str_V, *str_h, *str_F, *str_r;
+       USE_FEATURE_UDHCP_PORT(char *str_P;)
+       llist_t *list_O = NULL;
+       int tryagain_timeout = 20;
+       int discover_timeout = 3;
+       int discover_retries = 3;
+       uint32_t server_addr = server_addr; /* for compiler */
+       uint32_t requested_ip = 0;
+       uint32_t xid = 0;
+       uint32_t lease_seconds = 0; /* can be given as 32-bit quantity */
+       int packet_num;
+       int timeout; /* must be signed */
+       unsigned already_waited_sec;
+       unsigned opt;
+       int max_fd;
+       int retval;
+       struct timeval tv;
+       struct dhcpMessage packet;
+       fd_set rfds;
+
+#if ENABLE_GETOPT_LONG
+       static const char udhcpc_longopts[] ALIGN1 =
+               "clientid\0"       Required_argument "c"
+               "clientid-none\0"  No_argument       "C"
+               "vendorclass\0"    Required_argument "V"
+               "hostname\0"       Required_argument "H"
+               "fqdn\0"           Required_argument "F"
+               "interface\0"      Required_argument "i"
+               "now\0"            No_argument       "n"
+               "pidfile\0"        Required_argument "p"
+               "quit\0"           No_argument       "q"
+               "release\0"        No_argument       "R"
+               "request\0"        Required_argument "r"
+               "script\0"         Required_argument "s"
+               "timeout\0"        Required_argument "T"
+               "version\0"        No_argument       "v"
+               "retries\0"        Required_argument "t"
+               "tryagain\0"       Required_argument "A"
+               "syslog\0"         No_argument       "S"
+               "request-option\0" Required_argument "O"
+               "no-default-options\0" No_argument   "o"
+               "foreground\0"     No_argument       "f"
+               "background\0"     No_argument       "b"
+               USE_FEATURE_UDHCPC_ARPING("arping\0"    No_argument       "a")
+               USE_FEATURE_UDHCP_PORT("client-port\0"  Required_argument "P")
+               ;
+#endif
+       enum {
+               OPT_c = 1 << 0,
+               OPT_C = 1 << 1,
+               OPT_V = 1 << 2,
+               OPT_H = 1 << 3,
+               OPT_h = 1 << 4,
+               OPT_F = 1 << 5,
+               OPT_i = 1 << 6,
+               OPT_n = 1 << 7,
+               OPT_p = 1 << 8,
+               OPT_q = 1 << 9,
+               OPT_R = 1 << 10,
+               OPT_r = 1 << 11,
+               OPT_s = 1 << 12,
+               OPT_T = 1 << 13,
+               OPT_t = 1 << 14,
+               OPT_v = 1 << 15,
+               OPT_S = 1 << 16,
+               OPT_A = 1 << 17,
+               OPT_O = 1 << 18,
+               OPT_o = 1 << 19,
+               OPT_f = 1 << 20,
+/* The rest has variable bit positions, need to be clever */
+               OPTBIT_f = 20,
+               USE_FOR_MMU(              OPTBIT_b,)
+               USE_FEATURE_UDHCPC_ARPING(OPTBIT_a,)
+               USE_FEATURE_UDHCP_PORT(   OPTBIT_P,)
+               USE_FOR_MMU(              OPT_b = 1 << OPTBIT_b,)
+               USE_FEATURE_UDHCPC_ARPING(OPT_a = 1 << OPTBIT_a,)
+               USE_FEATURE_UDHCP_PORT(   OPT_P = 1 << OPTBIT_P,)
+       };
+
+       /* Default options. */
+       USE_FEATURE_UDHCP_PORT(SERVER_PORT = 67;)
+       USE_FEATURE_UDHCP_PORT(CLIENT_PORT = 68;)
+       client_config.interface = "eth0";
+       client_config.script = DEFAULT_SCRIPT;
+
+       /* Parse command line */
+       /* Cc: mutually exclusive; O: list; -T,-t,-A take numeric param */
+       opt_complementary = "c--C:C--c:O::T+:t+:A+";
+       USE_GETOPT_LONG(applet_long_options = udhcpc_longopts;)
+       opt = getopt32(argv, "c:CV:H:h:F:i:np:qRr:s:T:t:vSA:O:of"
+               USE_FOR_MMU("b")
+               USE_FEATURE_UDHCPC_ARPING("a")
+               USE_FEATURE_UDHCP_PORT("P:")
+               , &str_c, &str_V, &str_h, &str_h, &str_F
+               , &client_config.interface, &client_config.pidfile, &str_r /* i,p */
+               , &client_config.script /* s */
+               , &discover_timeout, &discover_retries, &tryagain_timeout /* T,t,A */
+               , &list_O
+               USE_FEATURE_UDHCP_PORT(, &str_P)
+               );
+       if (opt & OPT_c)
+               client_config.clientid = alloc_dhcp_option(DHCP_CLIENT_ID, str_c, 0);
+       if (opt & OPT_V)
+               client_config.vendorclass = alloc_dhcp_option(DHCP_VENDOR, str_V, 0);
+       if (opt & (OPT_h|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<3>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_r)
+               requested_ip = inet_addr(str_r);
+       if (opt & OPT_v) {
+               puts("version "BB_VER);
+               return 0;
+       }
+#if ENABLE_FEATURE_UDHCP_PORT
+       if (opt & OPT_P) {
+               CLIENT_PORT = xatou16(str_P);
+               SERVER_PORT = CLIENT_PORT - 1;
+       }
+#endif
+       if (opt & OPT_o)
+               client_config.no_default_options = 1;
+       while (list_O) {
+               char *optstr = llist_pop(&list_O);
+               int n = index_in_strings(dhcp_option_strings, optstr);
+               if (n < 0)
+                       bb_error_msg_and_die("unknown option '%s'", optstr);
+               n = dhcp_options[n].code;
+               client_config.opt_mask[n >> 3] |= 1 << (n & 7);
+       }
+
+       if (udhcp_read_interface(client_config.interface, &client_config.ifindex,
+                          NULL, client_config.arp))
+               return 1;
+#if !BB_MMU
+       /* on NOMMU reexec (i.e., background) early */
+       if (!(opt & OPT_f)) {
+               bb_daemonize_or_rexec(0 /* flags */, argv);
+               logmode = LOGMODE_NONE;
+       }
+#endif
+       if (opt & OPT_S) {
+               openlog(applet_name, LOG_PID, LOG_DAEMON);
+               logmode |= LOGMODE_SYSLOG;
+       }
+
+       /* Make sure fd 0,1,2 are open */
+       bb_sanitize_stdio();
+       /* Equivalent of doing a fflush after every \n */
+       setlinebuf(stdout);
+
+       /* Create pidfile */
+       write_pidfile(client_config.pidfile);
+
+       /* Goes to stdout (unless NOMMU) and possibly syslog */
+       bb_info_msg("%s (v"BB_VER") started", applet_name);
+
+       /* if not set, and not suppressed, setup the default client ID */
+       if (!client_config.clientid && !(opt & OPT_C)) {
+               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_listen_mode(LISTEN_RAW);
+       packet_num = 0;
+       timeout = 0;
+       already_waited_sec = 0;
+
+       /* Main event loop. select() waits on signal pipe and possibly
+        * on sockfd.
+        * "continue" statements in code below jump to the top of the loop.
+        */
+       for (;;) {
+               /* silence "uninitialized!" warning */
+               unsigned timestamp_before_wait = timestamp_before_wait;
+
+               //bb_error_msg("sockfd:%d, listen_mode:%d", sockfd, listen_mode);
+
+               /* Was opening raw or udp socket here
+                * if (listen_mode != LISTEN_NONE && sockfd < 0),
+                * but on fast network renew responses return faster
+                * than we open sockets. Thus this code is moved
+                * to change_listen_mode(). Thus we open listen socket
+                * BEFORE we send renew request (see "case BOUND:"). */
+
+               max_fd = udhcp_sp_fd_set(&rfds, sockfd);
+
+               tv.tv_sec = timeout - already_waited_sec;
+               tv.tv_usec = 0;
+               retval = 0; /* If we already timed out, fall through, else... */
+               if (tv.tv_sec > 0) {
+                       timestamp_before_wait = (unsigned)monotonic_sec();
+                       DEBUG("Waiting on select...");
+                       retval = select(max_fd + 1, &rfds, NULL, NULL, &tv);
+                       if (retval < 0) {
+                               /* EINTR? A signal was caught, don't panic */
+                               if (errno == EINTR)
+                                       continue;
+                               /* Else: an error occured, panic! */
+                               bb_perror_msg_and_die("select");
+                       }
+               }
+
+               /* If timeout dropped to zero, time to become active:
+                * resend discover/renew/whatever
+                */
+               if (retval == 0) {
+                       /* We will restart the wait in any case */
+                       already_waited_sec = 0;
+
+                       switch (state) {
+                       case INIT_SELECTING:
+                               if (packet_num < discover_retries) {
+                                       if (packet_num == 0)
+                                               xid = random_xid();
+
+                                       send_discover(xid, requested_ip); /* broadcast */
+
+                                       timeout = discover_timeout;
+                                       packet_num++;
+                                       continue;
+                               }
+ leasefail:
+                               udhcp_run_script(NULL, "leasefail");
+#if BB_MMU /* -b is not supported on NOMMU */
+                               if (opt & OPT_b) { /* background if no lease */
+                                       bb_info_msg("No lease, forking to background");
+                                       client_background();
+                                       /* do not background again! */
+                                       opt = ((opt & ~OPT_b) | OPT_f);
+                               } else
+#endif
+                               if (opt & OPT_n) { /* abort if no lease */
+                                       bb_info_msg("No lease, failing");
+                                       retval = 1;
+                                       goto ret;
+                               }
+                               /* wait before trying again */
+                               timeout = tryagain_timeout;
+                               packet_num = 0;
+                               continue;
+                       case RENEW_REQUESTED:
+                       case REQUESTING:
+                               if (packet_num < discover_retries) {
+                                       /* send request packet */
+                                       if (state == RENEW_REQUESTED) /* unicast */
+                                               send_renew(xid, server_addr, requested_ip);
+                                       else /* broadcast */
+                                               send_select(xid, server_addr, requested_ip);
+
+                                       timeout = discover_timeout;
+                                       packet_num++;
+                                       continue;
+                               }
+                               /* timed out, go back to init state */
+                               if (state == RENEW_REQUESTED)
+                                       udhcp_run_script(NULL, "deconfig");
+                               change_listen_mode(LISTEN_RAW);
+                               /* "discover...select...discover..." loops
+                                * were seen in the wild. Treat them similarly
+                                * to "no response to discover" case */
+                               if (state == REQUESTING) {
+                                       state = INIT_SELECTING;
+                                       goto leasefail;
+                               }
+                               state = INIT_SELECTING;
+                               timeout = 0;
+                               packet_num = 0;
+                               continue;
+                       case BOUND:
+                               /* Half of the lease passed, time to enter renewing state */
+                               change_listen_mode(LISTEN_KERNEL);
+                               DEBUG("Entering renew state");
+                               state = RENEWING;
+                               /* fall right through */
+                       case RENEWING:
+                               if (timeout > 60) {
+                                       /* send a request packet */
+                                       send_renew(xid, server_addr, requested_ip); /* unicast */
+                                       timeout >>= 1;
+                                       continue;
+                               }
+                               /* Timed out, enter rebinding state */
+                               DEBUG("Entering rebinding state");
+                               state = REBINDING;
+                               /* fall right through */
+                       case REBINDING:
+                               /* Lease is *really* about to run out,
+                                * try to find DHCP server using broadcast */
+                               if (timeout > 0) {
+                                       /* send a request packet */
+                                       send_renew(xid, 0 /*INADDR_ANY*/, requested_ip); /* broadcast */
+                                       timeout >>= 1;
+                                       continue;
+                               }
+                               /* Timed out, enter init state */
+                               bb_info_msg("Lease lost, entering init state");
+                               udhcp_run_script(NULL, "deconfig");
+                               change_listen_mode(LISTEN_RAW);
+                               state = INIT_SELECTING;
+                               /*timeout = 0; - already is */
+                               packet_num = 0;
+                               continue;
+                       /* case RELEASED: */
+                       }
+                       /* yah, I know, *you* say it would never happen */
+                       timeout = INT_MAX;
+                       continue; /* back to main loop */
+               }
+
+               /* select() didn't timeout, something did happen. */
+               /* Is it a packet? */
+               if (listen_mode != LISTEN_NONE && FD_ISSET(sockfd, &rfds)) {
+                       int len;
+                       /* A packet is ready, read it */
+
+                       if (listen_mode == LISTEN_KERNEL)
+                               len = udhcp_recv_kernel_packet(&packet, sockfd);
+                       else
+                               len = udhcp_recv_raw_packet(&packet, sockfd);
+                       if (len == -1) { /* error is severe, reopen socket */
+                               DEBUG("error on read, %s, reopening socket", strerror(errno));
+                               sleep(discover_timeout); /* 3 seconds by default */
+                               change_listen_mode(listen_mode); /* just close and reopen */
+                       }
+                       /* If this packet will turn out to be unrelated/bogus,
+                        * we will go back and wait for next one.
+                        * Be sure timeout is properly decreased. */
+                       already_waited_sec += (unsigned)monotonic_sec() - timestamp_before_wait;
+                       if (len < 0)
+                               continue;
+
+                       if (packet.xid != xid) {
+                               DEBUG("Ignoring xid %x (our xid is %x)",
+                                       (unsigned)packet.xid, (unsigned)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;
+                       }
+
+                       message = get_option(&packet, DHCP_MESSAGE_TYPE);
+                       if (message == NULL) {
+                               bb_error_msg("cannot get message type from packet - ignoring");
+                               continue;
+                       }
+
+                       switch (state) {
+                       case INIT_SELECTING:
+                               /* Must be a DHCPOFFER to one of our xid's */
+                               if (*message == DHCPOFFER) {
+                       /* TODO: why we don't just fetch server's IP from IP header? */
+                                       temp = get_option(&packet, DHCP_SERVER_ID);
+                                       if (!temp) {
+                                               bb_error_msg("no server ID in message");
+                                               continue;
+                                               /* still selecting - this server looks bad */
+                                       }
+                                       /* it IS unaligned sometimes, don't "optimize" */
+                                       move_from_unaligned32(server_addr, temp);
+                                       xid = packet.xid;
+                                       requested_ip = packet.yiaddr;
+
+                                       /* enter requesting state */
+                                       state = REQUESTING;
+                                       timeout = 0;
+                                       packet_num = 0;
+                                       already_waited_sec = 0;
+                               }
+                               continue;
+                       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_seconds = 60 * 60;
+                                       } else {
+                                               /* it IS unaligned sometimes, don't "optimize" */
+                                               move_from_unaligned32(lease_seconds, temp);
+                                               lease_seconds = ntohl(lease_seconds);
+                                               lease_seconds &= 0x0fffffff; /* paranoia: must not be prone to overflows */
+                                               if (lease_seconds < 10) /* and not too small */
+                                                       lease_seconds = 10;
+                                       }
+#if ENABLE_FEATURE_UDHCPC_ARPING
+                                       if (opt & OPT_a) {
+/* RFC 2131 3.1 paragraph 5:
+ * "The client receives the DHCPACK message with configuration
+ * parameters. The client SHOULD perform a final check on the
+ * parameters (e.g., ARP for allocated network address), and notes
+ * the duration of the lease specified in the DHCPACK message. At this
+ * point, the client is configured. If the client detects that the
+ * address is already in use (e.g., through the use of ARP),
+ * the client MUST send a DHCPDECLINE message to the server and restarts
+ * the configuration process..." */
+                                               if (!arpping(packet.yiaddr,
+                                                           (uint32_t) 0,
+                                                           client_config.arp,
+                                                           client_config.interface)
+                                               ) {
+                                                       bb_info_msg("offered address is in use "
+                                                               "(got ARP reply), declining");
+                                                       send_decline(xid, server_addr, packet.yiaddr);
+
+                                                       if (state != REQUESTING)
+                                                               udhcp_run_script(NULL, "deconfig");
+                                                       change_listen_mode(LISTEN_RAW);
+                                                       state = INIT_SELECTING;
+                                                       requested_ip = 0;
+                                                       timeout = tryagain_timeout;
+                                                       packet_num = 0;
+                                                       already_waited_sec = 0;
+                                                       continue; /* back to main loop */
+                                               }
+                                       }
+#endif
+                                       /* enter bound state */
+                                       timeout = lease_seconds / 2;
+                                       {
+                                               struct in_addr temp_addr;
+                                               temp_addr.s_addr = packet.yiaddr;
+                                               bb_info_msg("Lease of %s obtained, lease time %u",
+                                                       inet_ntoa(temp_addr), (unsigned)lease_seconds);
+                                       }
+                                       requested_ip = packet.yiaddr;
+                                       udhcp_run_script(&packet,
+                                                       ((state == RENEWING || state == REBINDING) ? "renew" : "bound"));
+
+                                       state = BOUND;
+                                       change_listen_mode(LISTEN_NONE);
+                                       if (opt & OPT_q) { /* quit after lease */
+                                               if (opt & OPT_R) /* release on quit */
+                                                       perform_release(requested_ip, server_addr);
+                                               goto ret0;
+                                       }
+#if BB_MMU /* NOMMU case backgrounded earlier */
+                                       if (!(opt & OPT_f)) {
+                                               client_background();
+                                               /* do not background again! */
+                                               opt = ((opt & ~OPT_b) | OPT_f);
+                                       }
+#endif
+                                       already_waited_sec = 0;
+                                       continue; /* back to main loop */
+                               }
+                               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");
+                                       change_listen_mode(LISTEN_RAW);
+                                       sleep(3); /* avoid excessive network traffic */
+                                       state = INIT_SELECTING;
+                                       requested_ip = 0;
+                                       timeout = 0;
+                                       packet_num = 0;
+                                       already_waited_sec = 0;
+                               }
+                               continue;
+                       /* case BOUND, RELEASED: - ignore all packets */
+                       }
+                       continue; /* back to main loop */
+               }
+
+               /* select() didn't timeout, something did happen.
+                * But it wasn't a packet. It's a signal pipe then. */
+               {
+                       int signo = udhcp_sp_read(&rfds);
+                       switch (signo) {
+                       case SIGUSR1:
+                               perform_renew();
+                               /* start things over */
+                               packet_num = 0;
+                               /* Kill any timeouts because the user wants this to hurry along */
+                               timeout = 0;
+                               break;
+                       case SIGUSR2:
+                               perform_release(requested_ip, server_addr);
+                               timeout = INT_MAX;
+                               break;
+                       case SIGTERM:
+                               bb_info_msg("Received SIGTERM");
+                               if (opt & OPT_R) /* release on quit */
+                                       perform_release(requested_ip, server_addr);
+                               goto ret0;
+                       }
+               }
+       } /* for (;;) - main loop ends */
+
+ ret0:
+       retval = 0;
+ ret:
+       /*if (client_config.pidfile) - remove_pidfile has its own check */
+               remove_pidfile(client_config.pidfile);
+       return retval;
+}
diff --git a/networking/udhcp/dhcpc.h b/networking/udhcp/dhcpc.h
new file mode 100644 (file)
index 0000000..7b77942
--- /dev/null
@@ -0,0 +1,51 @@
+/* vi: set sw=4 ts=4: */
+/* dhcpc.h */
+#ifndef UDHCP_DHCPC_H
+#define UDHCP_DHCPC_H 1
+
+PUSH_AND_SET_FUNCTION_VISIBILITY_TO_HIDDEN
+
+struct client_config_t {
+       uint8_t arp[6];                 /* Our arp address */
+       /* TODO: combine flag fields into single "unsigned opt" */
+       /* (can be set directly to the result of getopt32) */
+       char no_default_options;        /* Do not include default optins in request */
+       USE_FEATURE_UDHCP_PORT(uint16_t port;)
+       int ifindex;                    /* Index number of the interface to use */
+       uint8_t opt_mask[256 / 8];      /* Bitmask of options to send (-O option) */
+       const char *interface;          /* The name of the interface to use */
+       char *pidfile;                  /* Optionally store the process ID */
+       const char *script;             /* User script to run at dhcp events */
+       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 */
+};
+
+/* server_config sits in 1st half of bb_common_bufsiz1 */
+#define client_config (*(struct client_config_t*)(&bb_common_bufsiz1[COMMON_BUFSIZE / 2]))
+
+#if ENABLE_FEATURE_UDHCP_PORT
+#define CLIENT_PORT (client_config.port)
+#else
+#define CLIENT_PORT 68
+#endif
+
+
+/*** clientpacket.h ***/
+
+uint32_t random_xid(void) FAST_FUNC;
+int send_discover(uint32_t xid, uint32_t requested) FAST_FUNC;
+int send_select(uint32_t xid, uint32_t server, uint32_t requested) FAST_FUNC;
+#if ENABLE_FEATURE_UDHCPC_ARPING
+int send_decline(uint32_t xid, uint32_t server, uint32_t requested) FAST_FUNC;
+#endif
+int send_renew(uint32_t xid, uint32_t server, uint32_t ciaddr) FAST_FUNC;
+int send_renew(uint32_t xid, uint32_t server, uint32_t ciaddr) FAST_FUNC;
+int send_release(uint32_t server, uint32_t ciaddr) FAST_FUNC;
+
+int udhcp_recv_raw_packet(struct dhcpMessage *payload, int fd) FAST_FUNC;
+
+POP_SAVED_FUNCTION_VISIBILITY
+
+#endif
diff --git a/networking/udhcp/dhcpd.c b/networking/udhcp/dhcpd.c
new file mode 100644 (file)
index 0000000..a82fd8c
--- /dev/null
@@ -0,0 +1,278 @@
+/* vi: set sw=4 ts=4: */
+/* dhcpd.c
+ *
+ * udhcp Server
+ * Copyright (C) 1999 Matthew Ramsay <matthewr@moreton.com.au>
+ *                     Chris Trew <ctrew@moreton.com.au>
+ *
+ * Rewrite by Russ Dill <Russ.Dill@asu.edu> July 2001
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+#include <syslog.h>
+#include "common.h"
+#include "dhcpc.h"
+#include "dhcpd.h"
+#include "options.h"
+
+
+/* globals */
+struct dhcpOfferedAddr *leases;
+/* struct server_config_t server_config is in bb_common_bufsiz1 */
+
+
+int udhcpd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int udhcpd_main(int argc UNUSED_PARAM, char **argv)
+{
+       fd_set rfds;
+       int server_socket = -1, retval, max_sock;
+       struct dhcpMessage packet;
+       uint8_t *state, *server_id, *requested;
+       uint32_t server_id_aligned = server_id_aligned; /* for compiler */
+       uint32_t requested_aligned = requested_aligned;
+       uint32_t static_lease_ip;
+       unsigned timeout_end;
+       unsigned num_ips;
+       unsigned opt;
+       struct option_set *option;
+       struct dhcpOfferedAddr *lease, static_lease;
+       USE_FEATURE_UDHCP_PORT(char *str_P;)
+
+#if ENABLE_FEATURE_UDHCP_PORT
+       SERVER_PORT = 67;
+       CLIENT_PORT = 68;
+#endif
+
+       opt = getopt32(argv, "fS" USE_FEATURE_UDHCP_PORT("P:", &str_P));
+       argv += optind;
+       if (!(opt & 1)) { /* no -f */
+               bb_daemonize_or_rexec(0, argv);
+               logmode = LOGMODE_NONE;
+       }
+       if (opt & 2) { /* -S */
+               openlog(applet_name, LOG_PID, LOG_DAEMON);
+               logmode |= LOGMODE_SYSLOG;
+       }
+#if ENABLE_FEATURE_UDHCP_PORT
+       if (opt & 4) { /* -P */
+               SERVER_PORT = xatou16(str_P);
+               CLIENT_PORT = SERVER_PORT + 1;
+       }
+#endif
+       /* Would rather not do read_config before daemonization -
+        * otherwise NOMMU machines will parse config twice */
+       read_config(argv[0] ? argv[0] : DHCPD_CONF_FILE);
+
+       /* Make sure fd 0,1,2 are open */
+       bb_sanitize_stdio();
+       /* Equivalent of doing a fflush after every \n */
+       setlinebuf(stdout);
+
+       /* Create pidfile */
+       write_pidfile(server_config.pidfile);
+       /* if (!..) bb_perror_msg("cannot create pidfile %s", pidfile); */
+
+       bb_info_msg("%s (v"BB_VER") started", applet_name);
+
+       option = find_option(server_config.options, DHCP_LEASE_TIME);
+       server_config.lease = LEASE_TIME;
+       if (option) {
+               move_from_unaligned32(server_config.lease, option->data + 2);
+               server_config.lease = ntohl(server_config.lease);
+       }
+
+       /* Sanity check */
+       num_ips = server_config.end_ip - server_config.start_ip + 1;
+       if (server_config.max_leases > num_ips) {
+               bb_error_msg("max_leases=%u is too big, setting to %u",
+                       (unsigned)server_config.max_leases, num_ips);
+               server_config.max_leases = num_ips;
+       }
+
+       leases = xzalloc(server_config.max_leases * sizeof(*leases));
+       read_leases(server_config.lease_file);
+
+       if (udhcp_read_interface(server_config.interface, &server_config.ifindex,
+                          &server_config.server, server_config.arp)) {
+               retval = 1;
+               goto ret;
+       }
+
+       /* Setup the signal pipe */
+       udhcp_sp_setup();
+
+       timeout_end = monotonic_sec() + server_config.auto_time;
+       while (1) { /* loop until universe collapses */
+               int bytes;
+               struct timeval tv;
+
+               if (server_socket < 0) {
+                       server_socket = udhcp_listen_socket(/*INADDR_ANY,*/ SERVER_PORT,
+                                       server_config.interface);
+               }
+
+               max_sock = udhcp_sp_fd_set(&rfds, server_socket);
+               if (server_config.auto_time) {
+                       tv.tv_sec = timeout_end - monotonic_sec();
+                       tv.tv_usec = 0;
+               }
+               retval = 0;
+               if (!server_config.auto_time || tv.tv_sec > 0) {
+                       retval = select(max_sock + 1, &rfds, NULL, NULL,
+                                       server_config.auto_time ? &tv : NULL);
+               }
+               if (retval == 0) {
+                       write_leases();
+                       timeout_end = monotonic_sec() + server_config.auto_time;
+                       continue;
+               }
+               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 = monotonic_sec() + server_config.auto_time;
+                       continue;
+               case SIGTERM:
+                       bb_info_msg("Received a SIGTERM");
+                       goto ret0;
+               case 0: /* no signal: read a packet */
+                       break;
+               default: /* signal or error (probably EINTR): back to select */
+                       continue;
+               }
+
+               bytes = udhcp_recv_kernel_packet(&packet, server_socket);
+               if (bytes < 0) {
+                       /* bytes can also be -2 ("bad packet data") */
+                       if (bytes == -1 && errno != EINTR) {
+                               DEBUG("error on read, %s, reopening socket", strerror(errno));
+                               close(server_socket);
+                               server_socket = -1;
+                       }
+                       continue;
+               }
+
+               state = get_option(&packet, DHCP_MESSAGE_TYPE);
+               if (state == 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 (send_offer(&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)
+                               move_from_unaligned32(requested_aligned, requested);
+                       if (server_id)
+                               move_from_unaligned32(server_id_aligned, server_id);
+
+                       if (lease) {
+                               if (server_id) {
+                                       /* SELECTING State */
+                                       DEBUG("server_id = %08x", ntohl(server_id_aligned));
+                                       if (server_id_aligned == server_config.server
+                                        && requested
+                                        && requested_aligned == lease->yiaddr
+                                       ) {
+                                               send_ACK(&packet, lease->yiaddr);
+                                       }
+                               } else if (requested) {
+                                       /* INIT-REBOOT State */
+                                       if (lease->yiaddr == requested_aligned)
+                                               send_ACK(&packet, lease->yiaddr);
+                                       else
+                                               send_NAK(&packet);
+                               } else if (lease->yiaddr == packet.ciaddr) {
+                                       /* RENEWING or REBINDING State */
+                                       send_ACK(&packet, lease->yiaddr);
+                               } else { /* don't know what to do!!!! */
+                                       send_NAK(&packet);
+                               }
+
+                       /* what to do if we have no record of the client */
+                       } else if (server_id) {
+                               /* SELECTING State */
+
+                       } else if (requested) {
+                               /* INIT-REBOOT State */
+                               lease = find_lease_by_yiaddr(requested_aligned);
+                               if (lease) {
+                                       if (lease_expired(lease)) {
+                                               /* probably best if we drop this lease */
+                                               memset(lease->chaddr, 0, 16);
+                                       /* make some contention for this address */
+                                       } else
+                                               send_NAK(&packet);
+                               } else {
+                                       uint32_t r = ntohl(requested_aligned);
+                                       if (r < server_config.start_ip
+                                        || r > server_config.end_ip
+                                       ) {
+                                               send_NAK(&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(NULL) + server_config.decline_time;
+                       }
+                       break;
+               case DHCPRELEASE:
+                       DEBUG("Received RELEASE");
+                       if (lease)
+                               lease->expires = time(NULL);
+                       break;
+               case DHCPINFORM:
+                       DEBUG("Received INFORM");
+                       send_inform(&packet);
+                       break;
+               default:
+                       bb_info_msg("Unsupported DHCP message (%02x) - ignoring", state[0]);
+               }
+       }
+ ret0:
+       retval = 0;
+ ret:
+       /*if (server_config.pidfile) - server_config.pidfile is never NULL */
+               remove_pidfile(server_config.pidfile);
+       return retval;
+}
diff --git a/networking/udhcp/dhcpd.h b/networking/udhcp/dhcpd.h
new file mode 100644 (file)
index 0000000..9667c61
--- /dev/null
@@ -0,0 +1,136 @@
+/* vi: set sw=4 ts=4: */
+/* dhcpd.h */
+#ifndef UDHCP_DHCPD_H
+#define UDHCP_DHCPD_H 1
+
+PUSH_AND_SET_FUNCTION_VISIBILITY_TO_HIDDEN
+
+/************************************/
+/* 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            CONFIG_DHCPD_LEASES_FILE
+
+/* where to find the DHCP server configuration file */
+#define DHCPD_CONF_FILE         "/etc/udhcpd.conf"
+
+struct option_set {
+       uint8_t *data;
+       struct option_set *next;
+};
+
+struct static_lease {
+       struct static_lease *next;
+       uint32_t ip;
+       uint8_t mac[6];
+};
+
+struct server_config_t {
+       uint32_t server;                /* Our IP, in network order */
+#if ENABLE_FEATURE_UDHCP_PORT
+       uint16_t port;
+#endif
+       /* start,end are in host order: we need to compare start <= ip <= end */
+       uint32_t start_ip;              /* Start address of leases, in host order */
+       uint32_t end_ip;                /* End of leases, in host 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 */
+// disabled: dumpleases has no way of knowing this value,
+// and will break if it's off. Now it's on always.
+//     char remaining;                 /* Should the lease time in lease file
+//                                      * be written as lease time remaining, or
+//                                      * as the absolute time the lease expires */
+       uint32_t lease;                 /* lease time in seconds (host order) */
+       uint32_t max_leases;            /* maximum number of leases (including reserved address) */
+       uint32_t auto_time;             /* how long should udhcpd wait before writing a config file.
+                                        * if this is zero, it will only write one on SIGUSR1 */
+       uint32_t decline_time;          /* how long an address is reserved if a client returns a
+                                        * decline message */
+       uint32_t conflict_time;         /* how long an arp conflict offender is leased for */
+       uint32_t offer_time;            /* how long an offered address is reserved */
+       uint32_t min_lease;             /* minimum lease time a client can request */
+       uint32_t siaddr;                /* next server bootp option */
+       char *lease_file;
+       char *pidfile;
+       char *notify_file;              /* What to run whenever leases are written */
+       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 */
+};
+
+#define server_config (*(struct server_config_t*)&bb_common_bufsiz1)
+/* client_config sits in 2nd half of bb_common_bufsiz1 */
+
+#if ENABLE_FEATURE_UDHCP_PORT
+#define SERVER_PORT (server_config.port)
+#else
+#define SERVER_PORT 67
+#endif
+
+
+/*** leases.h ***/
+
+typedef uint32_t leasetime_t;
+typedef int32_t signed_leasetime_t;
+
+struct dhcpOfferedAddr {
+       uint8_t chaddr[16];
+       /* In network order */
+       uint32_t yiaddr;
+       /* Unix time when lease expires, regardless of value of
+        * server_config.remaining. Kept in memory in host order.
+        * When written to file, converted to network order
+        * and optionally adjusted (current time subtracted)
+        * if server_config.remaining = 1 */
+       leasetime_t expires;
+       uint8_t hostname[20]; /* (size is a multiply of 4) */
+};
+
+extern struct dhcpOfferedAddr *leases;
+
+struct dhcpOfferedAddr *add_lease(
+               const uint8_t *chaddr, uint32_t yiaddr,
+               leasetime_t leasetime, uint8_t *hostname
+               ) FAST_FUNC;
+int lease_expired(struct dhcpOfferedAddr *lease) FAST_FUNC;
+struct dhcpOfferedAddr *find_lease_by_chaddr(const uint8_t *chaddr) FAST_FUNC;
+struct dhcpOfferedAddr *find_lease_by_yiaddr(uint32_t yiaddr) FAST_FUNC;
+uint32_t find_free_or_expired_address(void) FAST_FUNC;
+
+
+/*** 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 */
+void addStaticLease(struct static_lease **lease_struct, uint8_t *mac, uint32_t ip) FAST_FUNC;
+/* Check to see if a mac has an associated static lease */
+uint32_t getIpByMac(struct static_lease *lease_struct, void *arg) FAST_FUNC;
+/* Check to see if an ip is reserved as a static ip */
+int reservedIp(struct static_lease *lease_struct, uint32_t ip) FAST_FUNC;
+/* Print out static leases just to check what's going on (debug code) */
+void printStaticLeases(struct static_lease **lease_struct) FAST_FUNC;
+
+
+/*** serverpacket.h ***/
+
+int send_offer(struct dhcpMessage *oldpacket) FAST_FUNC;
+int send_NAK(struct dhcpMessage *oldpacket) FAST_FUNC;
+int send_ACK(struct dhcpMessage *oldpacket, uint32_t yiaddr) FAST_FUNC;
+int send_inform(struct dhcpMessage *oldpacket) FAST_FUNC;
+
+
+/*** files.h ***/
+
+void read_config(const char *file) FAST_FUNC;
+void write_leases(void) FAST_FUNC;
+void read_leases(const char *file) FAST_FUNC;
+struct option_set *find_option(struct option_set *opt_list, uint8_t code) FAST_FUNC;
+
+
+POP_SAVED_FUNCTION_VISIBILITY
+
+#endif
diff --git a/networking/udhcp/dhcprelay.c b/networking/udhcp/dhcprelay.c
new file mode 100644 (file)
index 0000000..53540d1
--- /dev/null
@@ -0,0 +1,325 @@
+/* vi: set sw=4 ts=4: */
+/* Port to Busybox Copyright (C) 2006 Jesse Dutton <jessedutton@gmail.com>
+ *
+ * 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 <mast@gmx.net>,
+ *                   Zuercher Hochschule Winterthur,
+ *                   Netbeat AG
+ * Upstream has GPL v2 or later
+ */
+
+#include "common.h"
+#include "options.h"
+
+#define SERVER_PORT      67
+#define SELECT_TIMEOUT    5 /* select timeout in sec. */
+#define MAX_LIFETIME   2*60 /* lifetime of an xid entry in sec. */
+
+/* This list holds information about clients. The xid_* functions manipulate this list. */
+struct xid_item {
+       unsigned timestamp;
+       int client;
+       uint32_t xid;
+       struct sockaddr_in ip;
+       struct xid_item *next;
+};
+
+#define dhcprelay_xid_list (*(struct xid_item*)&bb_common_bufsiz1)
+
+static struct xid_item *xid_add(uint32_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 = monotonic_sec();
+       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;
+       unsigned current_time = monotonic_sec();
+
+       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(uint32_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(uint32_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)
+{
+       uint8_t *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;
+}
+
+/**
+ * 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, **client_dev;
+       int i, cn;
+
+       /* copy list */
+       dev_list = xstrdup(dev_list);
+
+       /* get number of items, replace ',' with NULs */
+       s = dev_list;
+       cn = 1;
+       while (*s) {
+               if (*s == ',') {
+                       *s = '\0';
+                       cn++;
+               }
+               s++;
+       }
+       *client_number = cn;
+
+       /* create vector of pointers */
+       client_dev = xzalloc(cn * sizeof(*client_dev));
+       client_dev[0] = dev_list;
+       i = 1;
+       while (i != cn) {
+               client_dev[i] = client_dev[i - 1] + strlen(client_dev[i - 1]) + 1;
+               i++;
+       }
+       return client_dev;
+}
+
+
+/* Creates listen sockets (in fds) bound to client and server ifaces,
+ * and returns numerically max fd.
+ */
+static int init_sockets(char **client_ifaces, int num_clients,
+                       char *server_iface, int *fds)
+{
+       int i, n;
+
+       /* talk to real server on bootps */
+       fds[0] = udhcp_listen_socket(/*INADDR_ANY,*/ SERVER_PORT, server_iface);
+       n = fds[0];
+
+       for (i = 1; i < num_clients; i++) {
+               /* listen for clients on bootps */
+               fds[i] = udhcp_listen_socket(/*INADDR_ANY,*/ SERVER_PORT, client_ifaces[i-1]);
+               if (fds[i] > n)
+                       n = fds[i];
+       }
+       return n;
+}
+
+
+/**
+ * pass_to_server() - forwards dhcp packets from client to server
+ * p - packet to send
+ * client - number of the client
+ */
+static void pass_to_server(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) */
+       errno = 0;
+       res = sendto(fds[0], p, packet_len, 0, (struct sockaddr*)server_addr,
+                       sizeof(struct sockaddr_in));
+       if (res != packet_len) {
+               bb_perror_msg("sendto");
+       }
+}
+
+/**
+ * pass_to_client() - forwards dhcp packets from server to client
+ * p - packet to send
+ */
+static void pass_to_client(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);
+       errno = 0;
+       res = sendto(fds[item->client], p, packet_len, 0, (struct sockaddr*) &(item->ip),
+                       sizeof(item->ip));
+       if (res != packet_len) {
+               bb_perror_msg("sendto");
+               return;
+       }
+
+       /* remove xid entry */
+       xid_del(p->xid);
+}
+
+int dhcprelay_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int dhcprelay_main(int argc, char **argv)
+{
+       struct dhcpMessage dhcp_msg;
+       struct sockaddr_in server_addr;
+       struct sockaddr_in client_addr;
+       fd_set rfds;
+       char **client_ifaces;
+       int *fds;
+       int num_sockets, max_socket;
+       uint32_t our_ip;
+
+       server_addr.sin_family = AF_INET;
+       server_addr.sin_port = htons(SERVER_PORT);
+
+       /* dhcprelay client_iface1,client_iface2,... server_iface [server_IP] */
+       if (argc == 4) {
+               if (!inet_aton(argv[3], &server_addr.sin_addr))
+                       bb_perror_msg_and_die("bad server IP");
+       } else if (argc == 3) {
+               server_addr.sin_addr.s_addr = htonl(INADDR_BROADCAST);
+       } else {
+               bb_show_usage();
+       }
+
+       /* Produce list of client ifaces */
+       client_ifaces = get_client_devices(argv[1], &num_sockets);
+
+       num_sockets++; /* for server socket at fds[0] */
+       fds = xmalloc(num_sockets * sizeof(fds[0]));
+
+       /* Create sockets and bind one to every iface */
+       max_socket = init_sockets(client_ifaces, num_sockets, argv[2], fds);
+
+       /* Get our IP on server_iface */
+       if (udhcp_read_interface(argv[2], NULL, &our_ip, NULL))
+               return 1;
+
+       /* Main loop */
+       while (1) {
+//reinit stuff from time to time? go back to get_client_devices
+//every N minutes?
+               struct timeval tv;
+               size_t packlen;
+               socklen_t addr_size;
+               int i;
+
+               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_recv_kernel_packet(&dhcp_msg, fds[0]);
+                               if (packlen > 0) {
+                                       pass_to_client(&dhcp_msg, packlen, fds);
+                               }
+                       }
+                       /* clients */
+                       for (i = 1; i < num_sockets; i++) {
+                               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;
+
+                               /* Get our IP on corresponding client_iface */
+//why? what if server can't route such IP?
+                               if (udhcp_read_interface(client_ifaces[i-1], NULL, &dhcp_msg.giaddr, NULL)) {
+                                       /* Fall back to our server_iface's IP */
+//this makes more sense!
+                                       dhcp_msg.giaddr = our_ip;
+                               }
+//maybe set dhcp_msg.flags |= BROADCAST_FLAG too?
+                               pass_to_server(&dhcp_msg, packlen, i, fds, &client_addr, &server_addr);
+                       }
+               }
+               xid_expire();
+       } /* while (1) */
+
+       /* return 0; - not reached */
+}
diff --git a/networking/udhcp/domain_codec.c b/networking/udhcp/domain_codec.c
new file mode 100644 (file)
index 0000000..6f051c4
--- /dev/null
@@ -0,0 +1,205 @@
+/* vi: set sw=4 ts=4: */
+
+/* RFC1035 domain compression routines (C) 2007 Gabriel Somlo <somlo at cmu.edu>
+ *
+ * Loosely based on the isc-dhcpd implementation by dhankins@isc.org
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+#if ENABLE_FEATURE_UDHCP_RFC3397
+
+#include "common.h"
+#include "options.h"
+
+#define NS_MAXDNAME  1025      /* max domain name length */
+#define NS_MAXCDNAME  255      /* max compressed domain name length */
+#define NS_MAXLABEL    63      /* max label length */
+#define NS_MAXDNSRCH    6      /* max domains in search path */
+#define NS_CMPRSFLGS 0xc0      /* name compression pointer flag */
+
+
+/* expand a RFC1035-compressed list of domain names "cstr", of length "clen";
+ * returns a newly allocated string containing the space-separated domains,
+ * prefixed with the contents of string pre, or NULL if an error occurs.
+ */
+char* FAST_FUNC dname_dec(const uint8_t *cstr, int clen, const char *pre)
+{
+       const uint8_t *c;
+       int crtpos, retpos, depth, plen = 0, len = 0;
+       char *dst = NULL;
+
+       if (!cstr)
+               return NULL;
+
+       if (pre)
+               plen = strlen(pre);
+
+       /* We make two passes over the cstr string. First, we compute
+        * how long the resulting string would be. Then we allocate a
+        * new buffer of the required length, and fill it in with the
+        * expanded content. The advantage of this approach is not
+        * having to deal with requiring callers to supply their own
+        * buffer, then having to check if it's sufficiently large, etc.
+        */
+
+       while (!dst) {
+
+               if (len > 0) {  /* second pass? allocate dst buffer and copy pre */
+                       dst = xmalloc(len + plen);
+                       memcpy(dst, pre, plen);
+               }
+
+               crtpos = retpos = depth = len = 0;
+
+               while (crtpos < clen) {
+                       c = cstr + crtpos;
+
+                       if ((*c & NS_CMPRSFLGS) != 0) { /* pointer */
+                               if (crtpos + 2 > clen)          /* no offset to jump to? abort */
+                                       return NULL;
+                               if (retpos == 0)                        /* toplevel? save return spot */
+                                       retpos = crtpos + 2;
+                               depth++;
+                               crtpos = ((*c & 0x3f) << 8) | (*(c + 1) & 0xff); /* jump */
+                       } else if (*c) {                        /* label */
+                               if (crtpos + *c + 1 > clen)             /* label too long? abort */
+                                       return NULL;
+                               if (dst)
+                                       memcpy(dst + plen + len, c + 1, *c);
+                               len += *c + 1;
+                               crtpos += *c + 1;
+                               if (dst)
+                                       *(dst + plen + len - 1) = '.';
+                       } else {                                        /* null: end of current domain name */
+                               if (retpos == 0) {                      /* toplevel? keep going */
+                                       crtpos++;
+                               } else {                                        /* return to toplevel saved spot */
+                                       crtpos = retpos;
+                                       retpos = depth = 0;
+                               }
+                               if (dst)
+                                       *(dst + plen + len - 1) = ' ';
+                       }
+
+                       if (depth > NS_MAXDNSRCH || /* too many jumps? abort, it's a loop */
+                               len > NS_MAXDNAME * NS_MAXDNSRCH) /* result too long? abort */
+                               return NULL;
+               }
+
+               if (!len)                       /* expanded string has 0 length? abort */
+                       return NULL;
+
+               if (dst)
+                       *(dst + plen + len - 1) = '\0';
+       }
+
+       return dst;
+}
+
+/* Convert a domain name (src) from human-readable "foo.blah.com" format into
+ * RFC1035 encoding "\003foo\004blah\003com\000". Return allocated string, or
+ * NULL if an error occurs.
+ */
+static uint8_t *convert_dname(const char *src)
+{
+       uint8_t c, *res, *lp, *rp;
+       int len;
+
+       res = xmalloc(strlen(src) + 2);
+       rp = lp = res;
+       rp++;
+
+       for (;;) {
+               c = (uint8_t)*src++;
+               if (c == '.' || c == '\0') {    /* end of label */
+                       len = rp - lp - 1;
+                       /* label too long, too short, or two '.'s in a row? abort */
+                       if (len > NS_MAXLABEL || len == 0 || (c == '.' && *src == '.')) {
+                               free(res);
+                               return NULL;
+                       }
+                       *lp = len;
+                       lp = rp++;
+                       if (c == '\0' || *src == '\0')  /* end of dname */
+                               break;
+               } else {
+                       if (c >= 0x41 && c <= 0x5A)             /* uppercase? convert to lower */
+                               c += 0x20;
+                       *rp++ = c;
+               }
+       }
+
+       *lp = 0;
+       if (rp - res > NS_MAXCDNAME) {  /* dname too long? abort */
+               free(res);
+               return NULL;
+       }
+       return res;
+}
+
+/* returns the offset within cstr at which dname can be found, or -1
+ */
+static int find_offset(const uint8_t *cstr, int clen, const uint8_t *dname)
+{
+       const uint8_t *c, *d;
+       int off, inc;
+
+       /* find all labels in cstr */
+       off = 0;
+       while (off < clen) {
+               c = cstr + off;
+
+               if ((*c & NS_CMPRSFLGS) != 0) { /* pointer, skip */
+                       off += 2;
+               } else if (*c) {        /* label, try matching dname */
+                       inc = *c + 1;
+                       d = dname;
+                       while (*c == *d && memcmp(c + 1, d + 1, *c) == 0) {
+                               if (*c == 0)    /* match, return offset */
+                                       return off;
+                               d += *c + 1;
+                               c += *c + 1;
+                               if ((*c & NS_CMPRSFLGS) != 0)   /* pointer, jump */
+                                       c = cstr + (((*c & 0x3f) << 8) | (*(c + 1) & 0xff));
+                       }
+                       off += inc;
+               } else {        /* null, skip */
+                       off++;
+               }
+       }
+
+       return -1;
+}
+
+/* computes string to be appended to cstr so that src would be added to
+ * the compression (best case, it's a 2-byte pointer to some offset within
+ * cstr; worst case, it's all of src, converted to rfc3011 format).
+ * The computed string is returned directly; its length is returned via retlen;
+ * NULL and 0, respectively, are returned if an error occurs.
+ */
+uint8_t* FAST_FUNC dname_enc(const uint8_t *cstr, int clen, const char *src, int *retlen)
+{
+       uint8_t *d, *dname;
+       int off;
+
+       dname = convert_dname(src);
+       if (dname == NULL) {
+               *retlen = 0;
+               return NULL;
+       }
+
+       for (d = dname; *d != 0; d += *d + 1) {
+               off = find_offset(cstr, clen, d);
+               if (off >= 0) { /* found a match, add pointer and terminate string */
+                       *d++ = NS_CMPRSFLGS;
+                       *d = off;
+                       break;
+               }
+       }
+
+       *retlen = d - dname + 1;
+       return dname;
+}
+
+#endif /* ENABLE_FEATURE_UDHCP_RFC3397 */
diff --git a/networking/udhcp/dumpleases.c b/networking/udhcp/dumpleases.c
new file mode 100644 (file)
index 0000000..1558f88
--- /dev/null
@@ -0,0 +1,93 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
+ */
+
+#include "common.h"
+#include "dhcpd.h"
+
+#if BB_LITTLE_ENDIAN
+static inline uint64_t hton64(uint64_t v)
+{
+        return (((uint64_t)htonl(v)) << 32) | htonl(v >> 32);
+}
+#else
+#define hton64(v) (v)
+#endif
+#define ntoh64(v) hton64(v)
+
+
+int dumpleases_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int dumpleases_main(int argc UNUSED_PARAM, char **argv)
+{
+       int fd;
+       int i;
+       unsigned opt;
+       int64_t written_at, curr, expires_abs;
+       const char *file = LEASES_FILE;
+       struct dhcpOfferedAddr lease;
+       struct in_addr addr;
+
+       enum {
+               OPT_a   = 0x1,  // -a
+               OPT_r   = 0x2,  // -r
+               OPT_f   = 0x4,  // -f
+       };
+#if ENABLE_GETOPT_LONG
+       static const char dumpleases_longopts[] ALIGN1 =
+               "absolute\0"  No_argument       "a"
+               "remaining\0" No_argument       "r"
+               "file\0"      Required_argument "f"
+               ;
+
+       applet_long_options = dumpleases_longopts;
+#endif
+       opt_complementary = "=0:a--r:r--a";
+       opt = getopt32(argv, "arf:", &file);
+
+       fd = xopen(file, O_RDONLY);
+
+       printf("Mac Address       IP Address      Host Name           Expires %s\n", (opt & OPT_a) ? "at" : "in");
+       /*     "00:00:00:00:00:00 255.255.255.255 ABCDEFGHIJKLMNOPQRS Wed Jun 30 21:49:08 1993" */
+       /*     "123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 */
+
+       if (full_read(fd, &written_at, sizeof(written_at)) != sizeof(written_at))
+               return 0;
+       written_at = ntoh64(written_at);
+       curr = time(NULL);
+       if (curr < written_at)
+               written_at = curr; /* lease file from future! :) */
+
+       while (full_read(fd, &lease, sizeof(lease)) == sizeof(lease)) {
+               const char *fmt = ":%02x" + 1;
+               for (i = 0; i < 6; i++) {
+                       printf(fmt, lease.chaddr[i]);
+                       fmt = ":%02x";
+               }
+               addr.s_addr = lease.yiaddr;
+               /* actually, 15+1 and 19+1, +1 is a space between columns */
+               /* lease.hostname is char[20] and is always NUL terminated */
+               printf(" %-16s%-20s", inet_ntoa(addr), lease.hostname);
+               expires_abs = ntohl(lease.expires) + written_at;
+               if (expires_abs <= curr) {
+                       puts("expired");
+                       continue;
+               }
+               if (!(opt & OPT_a)) { /* no -a */
+                       unsigned d, h, m;
+                       unsigned expires = expires_abs - curr;
+                       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 { /* -a */
+                       time_t t = expires_abs;
+                       fputs(ctime(&t), stdout);
+               }
+       }
+       /* close(fd); */
+
+       return 0;
+}
diff --git a/networking/udhcp/files.c b/networking/udhcp/files.c
new file mode 100644 (file)
index 0000000..55f3597
--- /dev/null
@@ -0,0 +1,433 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * files.c -- DHCP server file manipulation *
+ * Rewrite by Russ Dill <Russ.Dill@asu.edu> July 2001
+ *
+ * Licensed under GPLv2, see file LICENSE in this tarball for details.
+ */
+
+#include <netinet/ether.h>
+
+#include "common.h"
+#include "dhcpd.h"
+#include "options.h"
+
+#if BB_LITTLE_ENDIAN
+static inline uint64_t hton64(uint64_t v)
+{
+        return (((uint64_t)htonl(v)) << 32) | htonl(v >> 32);
+}
+#else
+#define hton64(v) (v)
+#endif
+#define ntoh64(v) hton64(v)
+
+
+/* on these functions, make sure your datatype matches */
+static int read_ip(const char *line, void *arg)
+{
+       len_and_sockaddr *lsa;
+
+       lsa = host_and_af2sockaddr(line, 0, AF_INET);
+       if (!lsa)
+               return 0;
+       *(uint32_t*)arg = lsa->u.sin.sin_addr.s_addr;
+       free(lsa);
+       return 1;
+}
+
+
+static int read_mac(const char *line, void *arg)
+{
+       return NULL == ether_aton_r(line, (struct ether_addr *)arg);
+}
+
+
+static int read_str(const char *line, void *arg)
+{
+       char **dest = arg;
+
+       free(*dest);
+       *dest = xstrdup(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;
+
+       if (!strcasecmp("yes", line)) {
+               *dest = 1;
+               return 1;
+       }
+       if (!strcasecmp("no", line)) {
+               *dest = 0;
+               return 1;
+       }
+       return 0;
+}
+
+
+/* find option 'code' in opt_list */
+struct option_set* FAST_FUNC find_option(struct option_set *opt_list, uint8_t 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;
+       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;
+
+       existing = find_option(*opt_list, option->code);
+       if (!existing) {
+               DEBUG("Attaching option %02x to list", option->code);
+
+#if ENABLE_FEATURE_UDHCP_RFC3397
+               if ((option->flags & TYPE_MASK) == OPTION_STR1035)
+                       /* reuse buffer and length for RFC1035-formatted string */
+                       buffer = (char *)dname_enc(NULL, 0, buffer, &length);
+#endif
+
+               /* make a new option */
+               new = xmalloc(sizeof(*new));
+               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;
+#if ENABLE_FEATURE_UDHCP_RFC3397
+               if ((option->flags & TYPE_MASK) == OPTION_STR1035 && buffer != NULL)
+                       free(buffer);
+#endif
+               return;
+       }
+
+       /* add it to an existing option */
+       DEBUG("Attaching option %02x to existing member of list", option->code);
+       if (option->flags & OPTION_LIST) {
+#if ENABLE_FEATURE_UDHCP_RFC3397
+               if ((option->flags & TYPE_MASK) == OPTION_STR1035)
+                       /* reuse buffer and length for RFC1035-formatted string */
+                       buffer = (char *)dname_enc(existing->data + 2,
+                                       existing->data[OPT_LEN], buffer, &length);
+#endif
+               if (existing->data[OPT_LEN] + length <= 255) {
+                       existing->data = xrealloc(existing->data,
+                                       existing->data[OPT_LEN] + length + 3);
+                       if ((option->flags & TYPE_MASK) == OPTION_STRING) {
+                               /* ' ' can bring us to 256 - bad */
+                               if (existing->data[OPT_LEN] + length >= 255)
+                                       return;
+                               /* add space separator between STRING options in a list */
+                               existing->data[existing->data[OPT_LEN] + 2] = ' ';
+                               existing->data[OPT_LEN]++;
+                       }
+                       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 */
+#if ENABLE_FEATURE_UDHCP_RFC3397
+               if ((option->flags & TYPE_MASK) == OPTION_STR1035 && buffer != NULL)
+                       free(buffer);
+#endif
+       } /* else, ignore the new data */
+}
+
+
+/* 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;
+       char *line;
+       const struct dhcp_option *option;
+       int retval, length, idx;
+       char buffer[8] ALIGNED(4);
+       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;
+
+       idx = index_in_strings(dhcp_option_strings, opt); /* NB: was strcasecmp! */
+       if (idx < 0)
+               return 0;
+       option = &dhcp_options[idx];
+
+       retval = 0;
+       do {
+               val = strtok(NULL, ", \t");
+               if (!val) break;
+               length = dhcp_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);
+                       val = strtok(NULL, ", \t/-");
+                       if (!val)
+                               retval = 0;
+                       if (retval)
+                               retval = read_ip(val, buffer + 4);
+                       break;
+               case OPTION_STRING:
+#if ENABLE_FEATURE_UDHCP_RFC3397
+               case OPTION_STR1035:
+#endif
+                       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;
+               /* htonX are macros in older libc's, using temp var
+                * in code below for safety */
+               /* TODO: use bb_strtoX? */
+               case OPTION_U16: {
+                       unsigned long tmp = strtoul(val, &endptr, 0);
+                       *result_u16 = htons(tmp);
+                       retval = (endptr[0] == '\0' /*&& tmp < 0x10000*/);
+                       break;
+               }
+               case OPTION_S16: {
+                       long tmp = strtol(val, &endptr, 0);
+                       *result_u16 = htons(tmp);
+                       retval = (endptr[0] == '\0');
+                       break;
+               }
+               case OPTION_U32: {
+                       unsigned long tmp = strtoul(val, &endptr, 0);
+                       *result_u32 = htonl(tmp);
+                       retval = (endptr[0] == '\0');
+                       break;
+               }
+               case OPTION_S32: {
+                       long tmp = strtol(val, &endptr, 0);
+                       *result_u32 = htonl(tmp);
+                       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;
+       struct ether_addr mac_bytes;
+       uint32_t ip;
+
+       /* Read mac */
+       line = (char *) const_line;
+       mac_string = strtok_r(line, " \t", &line);
+       read_mac(mac_string, &mac_bytes);
+
+       /* Read ip */
+       ip_string = strtok_r(NULL, " \t", &line);
+       read_ip(ip_string, &ip);
+
+       addStaticLease(arg, (uint8_t*) &mac_bytes, ip);
+
+       if (ENABLE_UDHCP_DEBUG) printStaticLeases(arg);
+
+       return 1;
+}
+
+
+struct config_keyword {
+       const char *keyword;
+       int (*handler)(const char *line, void *var);
+       void *var;
+       const char *def;
+};
+
+static const struct config_keyword keywords[] = {
+       /* keyword       handler   variable address               default */
+       {"start",        read_ip,  &(server_config.start_ip),     "192.168.0.20"},
+       {"end",          read_ip,  &(server_config.end_ip),       "192.168.0.254"},
+       {"interface",    read_str, &(server_config.interface),    "eth0"},
+       /* Avoid "max_leases value not sane" warning by setting default
+        * to default_end_ip - default_start_ip + 1: */
+       {"max_leases",   read_u32, &(server_config.max_leases),   "235"},
+//     {"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"},
+       {"siaddr",       read_ip,  &(server_config.siaddr),       "0.0.0.0"},
+       /* keywords with no defaults must be last! */
+       {"option",       read_opt, &(server_config.options),      ""},
+       {"opt",          read_opt, &(server_config.options),      ""},
+       {"notify_file",  read_str, &(server_config.notify_file),  ""},
+       {"sname",        read_str, &(server_config.sname),        ""},
+       {"boot_file",    read_str, &(server_config.boot_file),    ""},
+       {"static_lease", read_staticlease, &(server_config.static_leases), ""},
+};
+enum { KWS_WITH_DEFAULTS = ARRAY_SIZE(keywords) - 6 };
+
+void FAST_FUNC read_config(const char *file)
+{
+       parser_t *parser;
+       const struct config_keyword *k;
+       unsigned i;
+       char *token[2];
+
+       for (i = 0; i < KWS_WITH_DEFAULTS; i++)
+               keywords[i].handler(keywords[i].def, keywords[i].var);
+
+       parser = config_open(file);
+       while (config_read(parser, token, 2, 2, "# \t", PARSE_NORMAL)) {
+               for (k = keywords, i = 0; i < ARRAY_SIZE(keywords); k++, i++) {
+                       if (!strcasecmp(token[0], k->keyword)) {
+                               if (!k->handler(token[1], k->var)) {
+                                       bb_error_msg("can't parse line %u in %s",
+                                                       parser->lineno, file);
+                                       /* reset back to the default value */
+                                       k->handler(k->def, k->var);
+                               }
+                               break;
+                       }
+               }
+       }
+       config_close(parser);
+
+       server_config.start_ip = ntohl(server_config.start_ip);
+       server_config.end_ip = ntohl(server_config.end_ip);
+}
+
+
+void FAST_FUNC write_leases(void)
+{
+       int fd;
+       unsigned i;
+       leasetime_t curr;
+       int64_t written_at;
+
+       fd = open_or_warn(server_config.lease_file, O_WRONLY|O_CREAT|O_TRUNC);
+       if (fd < 0)
+               return;
+
+       curr = written_at = time(NULL);
+
+       written_at = hton64(written_at);
+       full_write(fd, &written_at, sizeof(written_at));
+
+       for (i = 0; i < server_config.max_leases; i++) {
+               leasetime_t tmp_time;
+
+               if (leases[i].yiaddr == 0)
+                       continue;
+
+               /* Screw with the time in the struct, for easier writing */
+               tmp_time = leases[i].expires;
+
+               leases[i].expires -= curr;
+               if ((signed_leasetime_t) leases[i].expires < 0)
+                       leases[i].expires = 0;
+               leases[i].expires = htonl(leases[i].expires);
+
+               /* No error check. If the file gets truncated,
+                * we lose some leases on restart. Oh well. */
+               full_write(fd, &leases[i], sizeof(leases[i]));
+
+               /* Then restore it when done */
+               leases[i].expires = tmp_time;
+       }
+       close(fd);
+
+       if (server_config.notify_file) {
+// TODO: vfork-based child creation
+               char *cmd = xasprintf("%s %s", server_config.notify_file, server_config.lease_file);
+               system(cmd);
+               free(cmd);
+       }
+}
+
+
+void FAST_FUNC read_leases(const char *file)
+{
+       struct dhcpOfferedAddr lease;
+       int64_t written_at, time_passed;
+       int fd;
+       USE_UDHCP_DEBUG(unsigned i;)
+
+       fd = open_or_warn(file, O_RDONLY);
+       if (fd < 0)
+               return;
+
+       if (full_read(fd, &written_at, sizeof(written_at)) != sizeof(written_at))
+               goto ret;
+       written_at = ntoh64(written_at);
+
+       time_passed = time(NULL) - written_at;
+       /* Strange written_at, or lease file from old version of udhcpd
+        * which had no "written_at" field? */
+       if ((uint64_t)time_passed > 12 * 60 * 60)
+               goto ret;
+
+       USE_UDHCP_DEBUG(i = 0;)
+       while (full_read(fd, &lease, sizeof(lease)) == sizeof(lease)) {
+               /* ADDME: what if it matches some static lease? */
+               uint32_t y = ntohl(lease.yiaddr);
+               if (y >= server_config.start_ip && y <= server_config.end_ip) {
+                       signed_leasetime_t expires = ntohl(lease.expires) - (signed_leasetime_t)time_passed;
+                       if (expires <= 0)
+                               continue;
+                       /* NB: add_lease takes "relative time", IOW,
+                        * lease duration, not lease deadline. */
+                       if (!(add_lease(lease.chaddr, lease.yiaddr, expires, NULL /* was lease.hostname. bug in add_lease, disabled */ ))) {
+                               bb_error_msg("too many leases while loading %s", file);
+                               break;
+                       }
+                       USE_UDHCP_DEBUG(i++;)
+               }
+       }
+       DEBUG("Read %d leases", i);
+ ret:
+       close(fd);
+}
diff --git a/networking/udhcp/leases.c b/networking/udhcp/leases.c
new file mode 100644 (file)
index 0000000..e17fb9e
--- /dev/null
@@ -0,0 +1,181 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * leases.c -- tools to manage DHCP leases
+ * Russ Dill <Russ.Dill@asu.edu> July 2001
+ *
+ * Licensed under GPLv2, see file LICENSE in this tarball for details.
+ */
+
+#include "common.h"
+#include "dhcpd.h"
+
+
+/* Find the oldest expired lease, NULL if there are no expired leases */
+static struct dhcpOfferedAddr *oldest_expired_lease(void)
+{
+       struct dhcpOfferedAddr *oldest_lease = NULL;
+       leasetime_t oldest_time = time(NULL);
+       unsigned i;
+
+       /* Unexpired leases have leases[i].expires >= current time
+        * and therefore can't ever match */
+       for (i = 0; i < server_config.max_leases; i++) {
+               if (leases[i].expires < oldest_time) {
+                       oldest_time = leases[i].expires;
+                       oldest_lease = &(leases[i]);
+               }
+       }
+       return oldest_lease;
+}
+
+
+/* Clear every lease out that chaddr OR yiaddr matches and is nonzero */
+static void clear_lease(const uint8_t *chaddr, uint32_t yiaddr)
+{
+       unsigned i, j;
+
+       for (j = 0; j < 16 && !chaddr[j]; j++)
+               continue;
+
+       for (i = 0; i < server_config.max_leases; i++) {
+               if ((j != 16 && memcmp(leases[i].chaddr, chaddr, 16) == 0)
+                || (yiaddr && leases[i].yiaddr == yiaddr)
+               ) {
+                       memset(&(leases[i]), 0, sizeof(leases[i]));
+               }
+       }
+}
+
+
+/* Add a lease into the table, clearing out any old ones */
+struct dhcpOfferedAddr* FAST_FUNC add_lease(
+               const uint8_t *chaddr, uint32_t yiaddr,
+               leasetime_t leasetime, uint8_t *hostname)
+{
+       struct dhcpOfferedAddr *oldest;
+       uint8_t hostname_length;
+
+       /* clean out any old ones */
+       clear_lease(chaddr, yiaddr);
+
+       oldest = oldest_expired_lease();
+
+       if (oldest) {
+               oldest->hostname[0] = '\0';
+               if (hostname) {
+                       /* option size byte, + 1 for NUL */
+                       hostname_length = hostname[-1] + 1;
+                       if (hostname_length > sizeof(oldest->hostname))
+                               hostname_length = sizeof(oldest->hostname);
+                       hostname = (uint8_t*) safe_strncpy((char*)oldest->hostname, (char*)hostname, hostname_length);
+                       /* sanitization (s/non-ASCII/^/g) */
+                       while (*hostname) {
+                               if (*hostname < ' ' || *hostname > 126)
+                                       *hostname = '^';
+                               hostname++;
+                       }
+               }
+               memcpy(oldest->chaddr, chaddr, 16);
+               oldest->yiaddr = yiaddr;
+               oldest->expires = time(NULL) + leasetime;
+       }
+
+       return oldest;
+}
+
+
+/* True if a lease has expired */
+int FAST_FUNC lease_expired(struct dhcpOfferedAddr *lease)
+{
+       return (lease->expires < (leasetime_t) time(NULL));
+}
+
+
+/* Find the first lease that matches chaddr, NULL if no match */
+struct dhcpOfferedAddr* FAST_FUNC find_lease_by_chaddr(const uint8_t *chaddr)
+{
+       unsigned 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* FAST_FUNC find_lease_by_yiaddr(uint32_t yiaddr)
+{
+       unsigned 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 nobody_responds_to_arp(uint32_t addr)
+{
+       /* 16 zero bytes */
+       static const uint8_t blank_chaddr[16] = { 0 };
+       /* = { 0 } helps gcc to put it in rodata, not bss */
+
+       struct in_addr temp;
+       int r;
+
+       r = arpping(addr, server_config.server, server_config.arp, server_config.interface);
+       if (r)
+               return r;
+
+       temp.s_addr = addr;
+       bb_info_msg("%s belongs to someone, reserving it for %u seconds",
+               inet_ntoa(temp), (unsigned)server_config.conflict_time);
+       add_lease(blank_chaddr, addr, server_config.conflict_time, NULL);
+       return 0;
+}
+
+
+/* Find a new usable (we think) address. */
+uint32_t FAST_FUNC find_free_or_expired_address(void)
+{
+       uint32_t addr;
+       struct dhcpOfferedAddr *oldest_lease = NULL;
+
+       addr = server_config.start_ip; /* addr is in host order here */
+       for (; addr <= server_config.end_ip; addr++) {
+               uint32_t net_addr;
+               struct dhcpOfferedAddr *lease;
+
+               /* ie, 192.168.55.0 */
+               if ((addr & 0xff) == 0)
+                       continue;
+               /* ie, 192.168.55.255 */
+               if ((addr & 0xff) == 0xff)
+                       continue;
+               net_addr = htonl(addr);
+               /* addr has a static lease? */
+               if (reservedIp(server_config.static_leases, net_addr))
+                       continue;
+
+               lease = find_lease_by_yiaddr(net_addr);
+               if (!lease) {
+                       if (nobody_responds_to_arp(net_addr))
+                               return net_addr;
+               } else {
+                       if (!oldest_lease || lease->expires < oldest_lease->expires)
+                               oldest_lease = lease;
+               }
+       }
+
+       if (oldest_lease && lease_expired(oldest_lease)
+        && nobody_responds_to_arp(oldest_lease->yiaddr)
+       ) {
+               return oldest_lease->yiaddr;
+       }
+
+       return 0;
+}
diff --git a/networking/udhcp/options.c b/networking/udhcp/options.c
new file mode 100644 (file)
index 0000000..143a1fd
--- /dev/null
@@ -0,0 +1,237 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * options.c -- DHCP server option packet tools
+ * Rewrite by Russ Dill <Russ.Dill@asu.edu> July 2001
+ *
+ * Licensed under GPLv2, see file LICENSE in this tarball for details.
+ */
+
+#include "common.h"
+#include "dhcpd.h"
+#include "options.h"
+
+
+/* Supported options are easily added here */
+const struct dhcp_option dhcp_options[] = {
+       /* flags                                    code */
+       { OPTION_IP                   | OPTION_REQ, 0x01 }, /* DHCP_SUBNET        */
+       { OPTION_S32                              , 0x02 }, /* DHCP_TIME_OFFSET   */
+       { OPTION_IP | OPTION_LIST     | OPTION_REQ, 0x03 }, /* DHCP_ROUTER        */
+       { OPTION_IP | OPTION_LIST                 , 0x04 }, /* DHCP_TIME_SERVER   */
+       { OPTION_IP | OPTION_LIST                 , 0x05 }, /* DHCP_NAME_SERVER   */
+       { OPTION_IP | OPTION_LIST     | OPTION_REQ, 0x06 }, /* DHCP_DNS_SERVER    */
+       { OPTION_IP | OPTION_LIST                 , 0x07 }, /* DHCP_LOG_SERVER    */
+       { OPTION_IP | OPTION_LIST                 , 0x08 }, /* DHCP_COOKIE_SERVER */
+       { OPTION_IP | OPTION_LIST                 , 0x09 }, /* DHCP_LPR_SERVER    */
+       { OPTION_STRING               | OPTION_REQ, 0x0c }, /* DHCP_HOST_NAME     */
+       { OPTION_U16                              , 0x0d }, /* DHCP_BOOT_SIZE     */
+       { OPTION_STRING | OPTION_LIST | OPTION_REQ, 0x0f }, /* DHCP_DOMAIN_NAME   */
+       { OPTION_IP                               , 0x10 }, /* DHCP_SWAP_SERVER   */
+       { OPTION_STRING                           , 0x11 }, /* DHCP_ROOT_PATH     */
+       { OPTION_U8                               , 0x17 }, /* DHCP_IP_TTL        */
+       { OPTION_U16                              , 0x1a }, /* DHCP_MTU           */
+       { OPTION_IP                   | OPTION_REQ, 0x1c }, /* DHCP_BROADCAST     */
+       { OPTION_STRING                           , 0x28 }, /* nisdomain          */
+       { OPTION_IP | OPTION_LIST                 , 0x29 }, /* nissrv             */
+       { OPTION_IP | OPTION_LIST     | OPTION_REQ, 0x2a }, /* DHCP_NTP_SERVER    */
+       { OPTION_IP | OPTION_LIST                 , 0x2c }, /* DHCP_WINS_SERVER   */
+       { OPTION_IP                               , 0x32 }, /* DHCP_REQUESTED_IP  */
+       { OPTION_U32                              , 0x33 }, /* DHCP_LEASE_TIME    */
+       { OPTION_U8                               , 0x35 }, /* dhcptype           */
+       { OPTION_IP                               , 0x36 }, /* DHCP_SERVER_ID     */
+       { OPTION_STRING                           , 0x38 }, /* DHCP_MESSAGE       */
+       { OPTION_STRING                           , 0x3C }, /* DHCP_VENDOR        */
+       { OPTION_STRING                           , 0x3D }, /* DHCP_CLIENT_ID     */
+       { OPTION_STRING                           , 0x42 }, /* tftp               */
+       { OPTION_STRING                           , 0x43 }, /* bootfile           */
+       { OPTION_STRING                           , 0x4D }, /* userclass          */
+#if ENABLE_FEATURE_UDHCP_RFC3397
+       { OPTION_STR1035 | OPTION_LIST            , 0x77 }, /* search             */
+#endif
+       /* MSIE's "Web Proxy Autodiscovery Protocol" support */
+       { OPTION_STRING                           , 0xfc }, /* wpad               */
+
+       /* Options below have no match in dhcp_option_strings[],
+        * are not passed to dhcpc scripts, and cannot be specified
+        * with "option XXX YYY" syntax in dhcpd config file. */
+
+       { OPTION_U16                              , 0x39 }, /* DHCP_MAX_SIZE      */
+       { } /* zeroed terminating entry */
+};
+
+/* Used for converting options from incoming packets to env variables
+ * for udhcpc stript */
+/* Must match dhcp_options[] order */
+const char dhcp_option_strings[] ALIGN1 =
+       "subnet" "\0"      /* DHCP_SUBNET         */
+       "timezone" "\0"    /* DHCP_TIME_OFFSET    */
+       "router" "\0"      /* DHCP_ROUTER         */
+       "timesrv" "\0"     /* DHCP_TIME_SERVER    */
+       "namesrv" "\0"     /* DHCP_NAME_SERVER    */
+       "dns" "\0"         /* DHCP_DNS_SERVER     */
+       "logsrv" "\0"      /* DHCP_LOG_SERVER     */
+       "cookiesrv" "\0"   /* DHCP_COOKIE_SERVER  */
+       "lprsrv" "\0"      /* DHCP_LPR_SERVER     */
+       "hostname" "\0"    /* DHCP_HOST_NAME      */
+       "bootsize" "\0"    /* DHCP_BOOT_SIZE      */
+       "domain" "\0"      /* DHCP_DOMAIN_NAME    */
+       "swapsrv" "\0"     /* DHCP_SWAP_SERVER    */
+       "rootpath" "\0"    /* DHCP_ROOT_PATH      */
+       "ipttl" "\0"       /* DHCP_IP_TTL         */
+       "mtu" "\0"         /* DHCP_MTU            */
+       "broadcast" "\0"   /* DHCP_BROADCAST      */
+       "nisdomain" "\0"   /*                     */
+       "nissrv" "\0"      /*                     */
+       "ntpsrv" "\0"      /* DHCP_NTP_SERVER     */
+       "wins" "\0"        /* DHCP_WINS_SERVER    */
+       "requestip" "\0"   /* DHCP_REQUESTED_IP   */
+       "lease" "\0"       /* DHCP_LEASE_TIME     */
+       "dhcptype" "\0"    /*                     */
+       "serverid" "\0"    /* DHCP_SERVER_ID      */
+       "message" "\0"     /* DHCP_MESSAGE        */
+       "vendorclass" "\0" /* DHCP_VENDOR         */
+       "clientid" "\0"    /* DHCP_CLIENT_ID      */
+       "tftp" "\0"
+       "bootfile" "\0"
+       "userclass" "\0"
+#if ENABLE_FEATURE_UDHCP_RFC3397
+       "search" "\0"
+#endif
+       /* MSIE's "Web Proxy Autodiscovery Protocol" support */
+       "wpad" "\0"
+       ;
+
+
+/* Lengths of the different option types */
+const uint8_t dhcp_option_lengths[] ALIGN1 = {
+       [OPTION_IP] =      4,
+       [OPTION_IP_PAIR] = 8,
+       [OPTION_BOOLEAN] = 1,
+       [OPTION_STRING] =  1,
+#if ENABLE_FEATURE_UDHCP_RFC3397
+       [OPTION_STR1035] = 1,
+#endif
+       [OPTION_U8] =      1,
+       [OPTION_U16] =     2,
+       [OPTION_S16] =     2,
+       [OPTION_U32] =     4,
+       [OPTION_S32] =     4
+};
+
+
+/* get an option with bounds checking (warning, result is not aligned). */
+uint8_t* FAST_FUNC get_option(struct dhcpMessage *packet, int code)
+{
+       uint8_t *optionptr;
+       int len;
+       int rem;
+       int overload = 0;
+       enum {
+               FILE_FIELD101  = FILE_FIELD  * 0x101,
+               SNAME_FIELD101 = SNAME_FIELD * 0x101,
+       };
+
+       /* option bytes: [code][len][data1][data2]..[dataLEN] */
+       optionptr = packet->options;
+       rem = sizeof(packet->options);
+       while (1) {
+               if (rem <= 0) {
+                       bb_error_msg("bogus packet, malformed option field");
+                       return NULL;
+               }
+               if (optionptr[OPT_CODE] == DHCP_PADDING) {
+                       rem--;
+                       optionptr++;
+                       continue;
+               }
+               if (optionptr[OPT_CODE] == DHCP_END) {
+                       if ((overload & FILE_FIELD101) == FILE_FIELD) {
+                               /* can use packet->file, and didn't look at it yet */
+                               overload |= FILE_FIELD101; /* "we looked at it" */
+                               optionptr = packet->file;
+                               rem = sizeof(packet->file);
+                               continue;
+                       }
+                       if ((overload & SNAME_FIELD101) == SNAME_FIELD) {
+                               /* can use packet->sname, and didn't look at it yet */
+                               overload |= SNAME_FIELD101; /* "we looked at it" */
+                               optionptr = packet->sname;
+                               rem = sizeof(packet->sname);
+                               continue;
+                       }
+                       return NULL;
+               }
+               len = 2 + optionptr[OPT_LEN];
+               rem -= len;
+               if (rem < 0)
+                       continue; /* complain and return NULL */
+
+               if (optionptr[OPT_CODE] == code)
+                       return optionptr + OPT_DATA;
+
+               if (optionptr[OPT_CODE] == DHCP_OPTION_OVERLOAD) {
+                       overload |= optionptr[OPT_DATA];
+                       /* fall through */
+               }
+               optionptr += len;
+       }
+       return NULL;
+}
+
+
+/* return the position of the 'end' option (no bounds checking) */
+int FAST_FUNC end_option(uint8_t *optionptr)
+{
+       int i = 0;
+
+       while (optionptr[i] != DHCP_END) {
+               if (optionptr[i] != DHCP_PADDING)
+                       i += optionptr[i + OPT_LEN] + 1;
+               i++;
+       }
+       return i;
+}
+
+
+/* add an option string to the options */
+/* option bytes: [code][len][data1][data2]..[dataLEN] */
+int FAST_FUNC 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 >= DHCP_OPTIONS_BUFSIZE) {
+               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 FAST_FUNC 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 = dhcp_option_lengths[dh->flags & TYPE_MASK];
+                       option[OPT_LEN] = len;
+                       if (BB_BIG_ENDIAN)
+                               data <<= 8 * (4 - len);
+                       /* Assignment is unaligned! */
+                       move_to_unaligned32(&option[OPT_DATA], data);
+                       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 (file)
index 0000000..23370da
--- /dev/null
@@ -0,0 +1,114 @@
+/* vi: set sw=4 ts=4: */
+/* options.h */
+#ifndef UDHCP_OPTIONS_H
+#define UDHCP_OPTIONS_H 1
+
+PUSH_AND_SET_FUNCTION_VISIBILITY_TO_HIDDEN
+
+#define TYPE_MASK       0x0F
+
+enum {
+       OPTION_IP = 1,
+       OPTION_IP_PAIR,
+       OPTION_STRING,
+#if ENABLE_FEATURE_UDHCP_RFC3397
+       OPTION_STR1035, /* RFC1035 compressed domain name list */
+#endif
+       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 */
+
+/*****************************************************************/
+/* Do not modify below here unless you know what you are doing!! */
+/*****************************************************************/
+
+/* DHCP protocol -- see RFC 2131 */
+#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_OVERLOAD    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
+/* Offsets in option byte sequence */
+#define OPT_CODE                0
+#define OPT_LEN                 1
+#define OPT_DATA                2
+/* Bits in "overload" option */
+#define OPTION_FIELD            0
+#define FILE_FIELD              1
+#define SNAME_FIELD             2
+
+#define BOOTREQUEST             1
+#define BOOTREPLY               2
+
+#define ETH_10MB                1
+#define ETH_10MB_LEN            6
+
+#define DHCPDISCOVER            1 /* client -> server */
+#define DHCPOFFER               2 /* client <- server */
+#define DHCPREQUEST             3 /* client -> server */
+#define DHCPDECLINE             4 /* client -> server */
+#define DHCPACK                 5 /* client <- server */
+#define DHCPNAK                 6 /* client <- server */
+#define DHCPRELEASE             7 /* client -> server */
+#define DHCPINFORM              8 /* client -> server */
+
+struct dhcp_option {
+       uint8_t flags;
+       uint8_t code;
+};
+
+extern const struct dhcp_option dhcp_options[];
+extern const char dhcp_option_strings[];
+extern const uint8_t dhcp_option_lengths[];
+
+uint8_t *get_option(struct dhcpMessage *packet, int code) FAST_FUNC;
+int end_option(uint8_t *optionptr) FAST_FUNC;
+int add_option_string(uint8_t *optionptr, uint8_t *string) FAST_FUNC;
+int add_simple_option(uint8_t *optionptr, uint8_t code, uint32_t data) FAST_FUNC;
+#if ENABLE_FEATURE_UDHCP_RFC3397
+char *dname_dec(const uint8_t *cstr, int clen, const char *pre) FAST_FUNC;
+uint8_t *dname_enc(const uint8_t *cstr, int clen, const char *src, int *retlen) FAST_FUNC;
+#endif
+
+POP_SAVED_FUNCTION_VISIBILITY
+
+#endif
diff --git a/networking/udhcp/packet.c b/networking/udhcp/packet.c
new file mode 100644 (file)
index 0000000..e2c8e6e
--- /dev/null
@@ -0,0 +1,244 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * packet.c -- packet ops
+ * Rewrite by Russ Dill <Russ.Dill@asu.edu> July 2001
+ *
+ * Licensed under GPLv2, see file LICENSE in this tarball for details.
+ */
+
+#include <netinet/in.h>
+#if (defined(__GLIBC__) && __GLIBC__ >= 2 && __GLIBC_MINOR__ >= 1) || defined _NEWLIB_VERSION
+#include <netpacket/packet.h>
+#include <net/ethernet.h>
+#else
+#include <asm/types.h>
+#include <linux/if_packet.h>
+#include <linux/if_ether.h>
+#endif
+
+#include "common.h"
+#include "dhcpd.h"
+#include "options.h"
+
+
+void FAST_FUNC udhcp_init_header(struct dhcpMessage *packet, char type)
+{
+       memset(packet, 0, sizeof(struct dhcpMessage));
+       packet->op = BOOTREQUEST; /* if client to a server */
+       switch (type) {
+       case DHCPOFFER:
+       case DHCPACK:
+       case DHCPNAK:
+               packet->op = BOOTREPLY; /* if server to client */
+       }
+       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 FAST_FUNC udhcp_recv_kernel_packet(struct dhcpMessage *packet, int fd)
+{
+       int bytes;
+       unsigned char *vendor;
+
+       memset(packet, 0, sizeof(*packet));
+       bytes = safe_read(fd, packet, sizeof(*packet));
+       if (bytes < 0) {
+               DEBUG("cannot read on listening socket, ignoring");
+               return bytes; /* returns -1 */
+       }
+
+       if (packet->cookie != htonl(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);
+               if (vendor) {
+#if 0
+                       static const char broken_vendors[][8] = {
+                               "MSFT 98",
+                               ""
+                       };
+                       int i;
+                       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 replies",
+                                               broken_vendors[i]);
+                                       packet->flags |= htons(BROADCAST_FLAG);
+                               }
+                       }
+#else
+                       if (vendor[OPT_LEN - 2] == (uint8_t)(sizeof("MSFT 98")-1)
+                        && memcmp(vendor, "MSFT 98", sizeof("MSFT 98")-1) == 0
+                       ) {
+                               DEBUG("broken client (%s), forcing broadcast replies", "MSFT 98");
+                               packet->flags |= htons(BROADCAST_FLAG);
+                       }
+#endif
+               }
+       }
+
+       return bytes;
+}
+
+
+uint16_t FAST_FUNC 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, send packet */
+int FAST_FUNC udhcp_send_raw_packet(struct dhcpMessage *payload,
+               uint32_t source_ip, int source_port,
+               uint32_t dest_ip, int dest_port, const uint8_t *dest_arp,
+               int ifindex)
+{
+       struct sockaddr_ll dest;
+       struct udp_dhcp_packet packet;
+       int fd;
+       int result = -1;
+       const char *msg;
+
+       enum {
+               IP_UPD_DHCP_SIZE = sizeof(struct udp_dhcp_packet) - CONFIG_UDHCPC_SLACK_FOR_BUGGY_SERVERS,
+               UPD_DHCP_SIZE    = IP_UPD_DHCP_SIZE - offsetof(struct udp_dhcp_packet, udp),
+       };
+
+       fd = socket(PF_PACKET, SOCK_DGRAM, htons(ETH_P_IP));
+       if (fd < 0) {
+               msg = "socket(%s)";
+               goto ret_msg;
+       }
+
+       memset(&dest, 0, sizeof(dest));
+       memset(&packet, 0, sizeof(packet));
+       packet.data = *payload; /* struct copy */
+
+       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(dest)) < 0) {
+               msg = "bind(%s)";
+               goto ret_close;
+       }
+
+       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);
+       /* size, excluding IP header: */
+       packet.udp.len = htons(UPD_DHCP_SIZE);
+       /* for UDP checksumming, ip.len is set to UDP packet len */
+       packet.ip.tot_len = packet.udp.len;
+       packet.udp.check = udhcp_checksum(&packet, IP_UPD_DHCP_SIZE);
+       /* but for sending, it is set to IP packet len */
+       packet.ip.tot_len = htons(IP_UPD_DHCP_SIZE);
+       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));
+
+       /* Currently we send full-sized DHCP packets (zero padded).
+        * If you need to change this: last byte of the packet is
+        * packet.data.options[end_option(packet.data.options)]
+        */
+       result = sendto(fd, &packet, IP_UPD_DHCP_SIZE, 0,
+                               (struct sockaddr *) &dest, sizeof(dest));
+       msg = "sendto";
+ ret_close:
+       close(fd);
+       if (result < 0) {
+ ret_msg:
+               bb_perror_msg(msg, "PACKET");
+       }
+       return result;
+}
+
+
+/* Let the kernel do all the work for packet generation */
+int FAST_FUNC udhcp_send_kernel_packet(struct dhcpMessage *payload,
+               uint32_t source_ip, int source_port,
+               uint32_t dest_ip, int dest_port)
+{
+       struct sockaddr_in client;
+       int fd;
+       int result = -1;
+       const char *msg;
+
+       enum {
+               DHCP_SIZE = sizeof(struct dhcpMessage) - CONFIG_UDHCPC_SLACK_FOR_BUGGY_SERVERS,
+       };
+
+       fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
+       if (fd < 0) {
+               msg = "socket(%s)";
+               goto ret_msg;
+       }
+       setsockopt_reuseaddr(fd);
+
+       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(client)) == -1) {
+               msg = "bind(%s)";
+               goto ret_close;
+       }
+
+       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(client)) == -1) {
+               msg = "connect";
+               goto ret_close;
+       }
+
+       /* Currently we send full-sized DHCP packets (see above) */
+       result = safe_write(fd, payload, DHCP_SIZE);
+       msg = "write";
+ ret_close:
+       close(fd);
+       if (result < 0) {
+ ret_msg:
+               bb_perror_msg(msg, "UDP");
+       }
+       return result;
+}
diff --git a/networking/udhcp/script.c b/networking/udhcp/script.c
new file mode 100644 (file)
index 0000000..3029b13
--- /dev/null
@@ -0,0 +1,235 @@
+/* vi: set sw=4 ts=4: */
+/* script.c
+ *
+ * Functions to call the DHCP client notification scripts
+ *
+ * Russ Dill <Russ.Dill@asu.edu> July 2001
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+#include "common.h"
+#include "dhcpc.h"
+#include "options.h"
+
+
+/* get a rough idea of how long an option will be (rounding up...) */
+static const uint8_t max_option_length[] = {
+       [OPTION_IP] =           sizeof("255.255.255.255 "),
+       [OPTION_IP_PAIR] =      sizeof("255.255.255.255 ") * 2,
+       [OPTION_STRING] =       1,
+#if ENABLE_FEATURE_UDHCP_RFC3397
+       [OPTION_STR1035] =      1,
+#endif
+       [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 / dhcp_option_lengths[opt_index]);
+}
+
+
+static int sprintip(char *dest, const char *pre, const 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(uint32_t mask)
+{
+       int i = 0;
+       mask = ntohl(mask); /* 111110000-like bit pattern */
+       while (mask) {
+               i++;
+               mask <<= 1;
+       }
+       return i;
+}
+
+
+/* Allocate and fill with the text of option 'option'. */
+static char *alloc_fill_opts(uint8_t *option, const struct dhcp_option *type_p, const char *opt_name)
+{
+       int len, type, optlen;
+       uint16_t val_u16;
+       int16_t val_s16;
+       uint32_t val_u32;
+       int32_t val_s32;
+       char *dest, *ret;
+
+       len = option[OPT_LEN - 2];
+       type = type_p->flags & TYPE_MASK;
+       optlen = dhcp_option_lengths[type];
+
+       dest = ret = xmalloc(upper_length(len, type) + strlen(opt_name) + 2);
+       dest += sprintf(ret, "%s=", opt_name);
+
+       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:
+                       move_from_unaligned16(val_u16, option);
+                       dest += sprintf(dest, "%u", ntohs(val_u16));
+                       break;
+               case OPTION_S16:
+                       move_from_unaligned16(val_s16, option);
+                       dest += sprintf(dest, "%d", ntohs(val_s16));
+                       break;
+               case OPTION_U32:
+                       move_from_unaligned32(val_u32, option);
+                       dest += sprintf(dest, "%lu", (unsigned long) ntohl(val_u32));
+                       break;
+               case OPTION_S32:
+                       move_from_unaligned32(val_s32, option);
+                       dest += sprintf(dest, "%ld", (long) ntohl(val_s32));
+                       break;
+               case OPTION_STRING:
+                       memcpy(dest, option, len);
+                       dest[len] = '\0';
+                       return ret;      /* Short circuit this case */
+#if ENABLE_FEATURE_UDHCP_RFC3397
+               case OPTION_STR1035:
+                       /* unpack option into dest; use ret for prefix (i.e., "optname=") */
+                       dest = dname_dec(option, len, ret);
+                       free(ret);
+                       return dest;
+#endif
+               }
+               option += optlen;
+               len -= optlen;
+               if (len <= 0)
+                       break;
+               dest += sprintf(dest, " ");
+       }
+       return ret;
+}
+
+
+/* put all the parameters into an environment */
+static char **fill_envp(struct dhcpMessage *packet)
+{
+       int num_options = 0;
+       int i;
+       char **envp, **curr;
+       const char *opt_name;
+       uint8_t *temp;
+       uint8_t over = 0;
+
+       if (packet) {
+               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++;
+               temp = get_option(packet, DHCP_OPTION_OVERLOAD);
+               if (temp)
+                       over = *temp;
+               if (!(over & FILE_FIELD) && packet->file[0])
+                       num_options++;
+               if (!(over & SNAME_FIELD) && packet->sname[0])
+                       num_options++;
+       }
+
+       curr = envp = xzalloc(sizeof(char *) * (num_options + 3));
+       *curr = xasprintf("interface=%s", client_config.interface);
+       putenv(*curr++);
+
+       if (packet == NULL)
+               return envp;
+
+       *curr = xmalloc(sizeof("ip=255.255.255.255"));
+       sprintip(*curr, "ip=", (uint8_t *) &packet->yiaddr);
+       putenv(*curr++);
+
+       opt_name = dhcp_option_strings;
+       i = 0;
+       while (*opt_name) {
+               temp = get_option(packet, dhcp_options[i].code);
+               if (!temp)
+                       goto next;
+               *curr = alloc_fill_opts(temp, &dhcp_options[i], opt_name);
+               putenv(*curr++);
+
+               /* Fill in a subnet bits option for things like /24 */
+               if (dhcp_options[i].code == DHCP_SUBNET) {
+                       uint32_t subnet;
+                       move_from_unaligned32(subnet, temp);
+                       *curr = xasprintf("mask=%d", mton(subnet));
+                       putenv(*curr++);
+               }
+ next:
+               opt_name += strlen(opt_name) + 1;
+               i++;
+       }
+       if (packet->siaddr) {
+               *curr = xmalloc(sizeof("siaddr=255.255.255.255"));
+               sprintip(*curr, "siaddr=", (uint8_t *) &packet->siaddr);
+               putenv(*curr++);
+       }
+       if (!(over & FILE_FIELD) && packet->file[0]) {
+               /* watch out for invalid packets */
+               packet->file[sizeof(packet->file) - 1] = '\0';
+               *curr = xasprintf("boot_file=%s", packet->file);
+               putenv(*curr++);
+       }
+       if (!(over & SNAME_FIELD) && packet->sname[0]) {
+               /* watch out for invalid packets */
+               packet->sname[sizeof(packet->sname) - 1] = '\0';
+               *curr = xasprintf("sname=%s", packet->sname);
+               putenv(*curr++);
+       }
+       return envp;
+}
+
+
+/* Call a script with a par file and env vars */
+void FAST_FUNC udhcp_run_script(struct dhcpMessage *packet, const char *name)
+{
+       char **envp, **curr;
+       char *argv[3];
+
+       if (client_config.script == NULL)
+               return;
+
+       DEBUG("vfork'ing and exec'ing %s", client_config.script);
+
+       envp = fill_envp(packet);
+
+       /* call script */
+       argv[0] = (char*) client_config.script;
+       argv[1] = (char*) name;
+       argv[2] = NULL;
+       wait4pid(spawn(argv));
+
+       for (curr = envp; *curr; curr++) {
+               bb_unsetenv(*curr);
+               free(*curr);
+       }
+       free(envp);
+}
diff --git a/networking/udhcp/serverpacket.c b/networking/udhcp/serverpacket.c
new file mode 100644 (file)
index 0000000..8b0f185
--- /dev/null
@@ -0,0 +1,265 @@
+/* vi: set sw=4 ts=4: */
+/* serverpacket.c
+ *
+ * Construct and send DHCP server packets
+ *
+ * Russ Dill <Russ.Dill@asu.edu> 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 "dhcpc.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_send_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)
+{
+       const 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 (payload->flags & htons(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_send_raw_packet(payload,
+               /*src*/ server_config.server, SERVER_PORT,
+               /*dst*/ 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)
+{
+       if (payload->giaddr)
+               return send_packet_to_relay(payload);
+       return send_packet_to_client(payload, force_broadcast);
+}
+
+
+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 FAST_FUNC send_offer(struct dhcpMessage *oldpacket)
+{
+       struct dhcpMessage packet;
+       uint32_t req_align;
+       uint32_t lease_time_aligned = server_config.lease;
+       uint32_t static_lease_ip;
+       uint8_t *req, *lease_time, *p_host_name;
+       struct option_set *curr;
+       struct in_addr addr;
+
+       init_packet(&packet, oldpacket, DHCPOFFER);
+
+       static_lease_ip = getIpByMac(server_config.static_leases, oldpacket->chaddr);
+
+       /* ADDME: if static, short circuit */
+       if (!static_lease_ip) {
+               struct dhcpOfferedAddr *lease;
+
+               lease = find_lease_by_chaddr(oldpacket->chaddr);
+               /* the client is in our lease/offered table */
+               if (lease) {
+                       signed_leasetime_t tmp = lease->expires - time(NULL);
+                       if (tmp >= 0)
+                               lease_time_aligned = tmp;
+                       packet.yiaddr = lease->yiaddr;
+               /* Or the client has requested an ip */
+               } else if ((req = get_option(oldpacket, DHCP_REQUESTED_IP)) != NULL
+                /* Don't look here (ugly hackish thing to do) */
+                && (move_from_unaligned32(req_align, req), 1)
+                /* and the ip is in the lease range */
+                && ntohl(req_align) >= server_config.start_ip
+                && ntohl(req_align) <= server_config.end_ip
+                /* and is not already taken/offered */
+                && (!(lease = find_lease_by_yiaddr(req_align))
+                       /* or its taken, but expired */
+                       || lease_expired(lease))
+               ) {
+                       packet.yiaddr = req_align;
+               /* otherwise, find a free IP */
+               } else {
+                       packet.yiaddr = find_free_or_expired_address();
+               }
+
+               if (!packet.yiaddr) {
+                       bb_error_msg("no IP addresses to give - OFFER abandoned");
+                       return -1;
+               }
+               p_host_name = get_option(oldpacket, DHCP_HOST_NAME);
+               if (!add_lease(packet.chaddr, packet.yiaddr, server_config.offer_time, p_host_name)) {
+                       bb_error_msg("lease pool is full - OFFER abandoned");
+                       return -1;
+               }
+               lease_time = get_option(oldpacket, DHCP_LEASE_TIME);
+               if (lease_time) {
+                       move_from_unaligned32(lease_time_aligned, lease_time);
+                       lease_time_aligned = ntohl(lease_time_aligned);
+                       if (lease_time_aligned > server_config.lease)
+                               lease_time_aligned = server_config.lease;
+               }
+
+               /* Make sure we aren't just using the lease time from the previous offer */
+               if (lease_time_aligned < server_config.min_lease)
+                       lease_time_aligned = server_config.min_lease;
+       } else {
+               /* It is a static lease... use it */
+               packet.yiaddr = static_lease_ip;
+       }
+
+       add_simple_option(packet.options, DHCP_LEASE_TIME, htonl(lease_time_aligned));
+
+       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 FAST_FUNC send_NAK(struct dhcpMessage *oldpacket)
+{
+       struct dhcpMessage packet;
+
+       init_packet(&packet, oldpacket, DHCPNAK);
+
+       DEBUG("Sending NAK");
+       return send_packet(&packet, 1);
+}
+
+
+int FAST_FUNC send_ACK(struct dhcpMessage *oldpacket, uint32_t yiaddr)
+{
+       struct dhcpMessage packet;
+       struct option_set *curr;
+       uint8_t *lease_time;
+       uint32_t lease_time_aligned = server_config.lease;
+       struct in_addr addr;
+       uint8_t *p_host_name;
+
+       init_packet(&packet, oldpacket, DHCPACK);
+       packet.yiaddr = yiaddr;
+
+       lease_time = get_option(oldpacket, DHCP_LEASE_TIME);
+       if (lease_time) {
+               move_from_unaligned32(lease_time_aligned, lease_time);
+               lease_time_aligned = ntohl(lease_time_aligned);
+               if (lease_time_aligned > server_config.lease)
+                       lease_time_aligned = server_config.lease;
+               else if (lease_time_aligned < server_config.min_lease)
+                       lease_time_aligned = server_config.min_lease;
+       }
+
+       add_simple_option(packet.options, DHCP_LEASE_TIME, htonl(lease_time_aligned));
+
+       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;
+
+       p_host_name = get_option(oldpacket, DHCP_HOST_NAME);
+       add_lease(packet.chaddr, packet.yiaddr, lease_time_aligned, p_host_name);
+       if (ENABLE_FEATURE_UDHCPD_WRITE_LEASES_EARLY) {
+               /* rewrite the file with leases at every new acceptance */
+               write_leases();
+       }
+
+       return 0;
+}
+
+
+int FAST_FUNC 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 (file)
index 0000000..a025bd8
--- /dev/null
@@ -0,0 +1,82 @@
+/* vi: set sw=4 ts=4: */
+/* signalpipe.c
+ *
+ * Signal pipe infrastructure. A reliable way of delivering signals.
+ *
+ * Russ Dill <Russ.Dill@asu.edu> 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 struct fd_pair signal_pipe;
+
+static void signal_handler(int sig)
+{
+       unsigned char ch = sig; /* use char, avoid dealing with partial writes */
+       if (write(signal_pipe.wr, &ch, 1) != 1)
+               bb_perror_msg("cannot send signal");
+}
+
+
+/* Call this before doing anything else. Sets up the socket pair
+ * and installs the signal handler */
+void FAST_FUNC udhcp_sp_setup(void)
+{
+       /* was socketpair, but it needs AF_UNIX in kernel */
+       xpiped_pair(signal_pipe);
+       close_on_exec_on(signal_pipe.rd);
+       close_on_exec_on(signal_pipe.wr);
+       ndelay_on(signal_pipe.wr);
+       bb_signals(0
+               + (1 << SIGUSR1)
+               + (1 << SIGUSR2)
+               + (1 << 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 FAST_FUNC udhcp_sp_fd_set(fd_set *rfds, int extra_fd)
+{
+       FD_ZERO(rfds);
+       FD_SET(signal_pipe.rd, rfds);
+       if (extra_fd >= 0) {
+               close_on_exec_on(extra_fd);
+               FD_SET(extra_fd, rfds);
+       }
+       return signal_pipe.rd > extra_fd ? signal_pipe.rd : 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 FAST_FUNC udhcp_sp_read(const fd_set *rfds)
+{
+       unsigned char sig;
+
+       if (!FD_ISSET(signal_pipe.rd, rfds))
+               return 0;
+
+       if (safe_read(signal_pipe.rd, &sig, 1) != 1)
+               return -1;
+
+       return sig;
+}
diff --git a/networking/udhcp/socket.c b/networking/udhcp/socket.c
new file mode 100644 (file)
index 0000000..edf4355
--- /dev/null
@@ -0,0 +1,111 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * socket.c -- DHCP server client/server socket creation
+ *
+ * udhcp client/server
+ * Copyright (C) 1999 Matthew Ramsay <matthewr@moreton.com.au>
+ *                     Chris Trew <ctrew@moreton.com.au>
+ *
+ * Rewrite by Russ Dill <Russ.Dill@asu.edu> 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 <net/if.h>
+#include <features.h>
+#if (defined(__GLIBC__) && __GLIBC__ >= 2 && __GLIBC_MINOR__ >= 1) || defined _NEWLIB_VERSION
+#include <netpacket/packet.h>
+#include <net/ethernet.h>
+#else
+#include <asm/types.h>
+#include <linux/if_packet.h>
+#include <linux/if_ether.h>
+#endif
+
+#include "common.h"
+
+
+int FAST_FUNC udhcp_read_interface(const char *interface, int *ifindex, uint32_t *addr, uint8_t *arp)
+{
+       int fd;
+       struct ifreq ifr;
+       struct sockaddr_in *our_ip;
+
+       memset(&ifr, 0, sizeof(ifr));
+       fd = xsocket(AF_INET, SOCK_RAW, IPPROTO_RAW);
+
+       ifr.ifr_addr.sa_family = AF_INET;
+       strncpy_IFNAMSIZ(ifr.ifr_name, interface);
+       if (addr) {
+               if (ioctl_or_perror(fd, SIOCGIFADDR, &ifr,
+                       "is interface %s up and configured?", interface)
+               ) {
+                       close(fd);
+                       return -1;
+               }
+               our_ip = (struct sockaddr_in *) &ifr.ifr_addr;
+               *addr = our_ip->sin_addr.s_addr;
+               DEBUG("ip of %s = %s", interface, inet_ntoa(our_ip->sin_addr));
+       }
+
+       if (ifindex) {
+               if (ioctl_or_warn(fd, SIOCGIFINDEX, &ifr) != 0) {
+                       close(fd);
+                       return -1;
+               }
+               DEBUG("adapter index %d", ifr.ifr_ifindex);
+               *ifindex = ifr.ifr_ifindex;
+       }
+
+       if (arp) {
+               if (ioctl_or_warn(fd, SIOCGIFHWADDR, &ifr) != 0) {
+                       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]);
+       }
+
+       close(fd);
+       return 0;
+}
+
+/* 1. None of the callers expects it to ever fail */
+/* 2. ip was always INADDR_ANY */
+int FAST_FUNC udhcp_listen_socket(/*uint32_t ip,*/ int port, const char *inf)
+{
+       int fd;
+       struct sockaddr_in addr;
+
+       DEBUG("Opening listen socket on *:%d %s", port, inf);
+       fd = xsocket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
+
+       setsockopt_reuseaddr(fd);
+       if (setsockopt_broadcast(fd) == -1)
+               bb_perror_msg_and_die("SO_BROADCAST");
+
+       /* NB: bug 1032 says this doesn't work on ethernet aliases (ethN:M) */
+       if (setsockopt_bindtodevice(fd, inf))
+               xfunc_die(); /* warning is already printed */
+
+       memset(&addr, 0, sizeof(addr));
+       addr.sin_family = AF_INET;
+       addr.sin_port = htons(port);
+       /* addr.sin_addr.s_addr = ip; - all-zeros is INADDR_ANY */
+       xbind(fd, (struct sockaddr *)&addr, sizeof(addr));
+
+       return fd;
+}
diff --git a/networking/udhcp/static_leases.c b/networking/udhcp/static_leases.c
new file mode 100644 (file)
index 0000000..1e77a58
--- /dev/null
@@ -0,0 +1,79 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * static_leases.c -- Couple of functions to assist with storing and
+ * retrieving data for static leases
+ *
+ * Wade Berrier <wberrier@myrealbox.com> September 2004
+ *
+ * Licensed under GPLv2, see file LICENSE in this tarball for details.
+ */
+
+#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 */
+void FAST_FUNC addStaticLease(struct static_lease **lease_struct, uint8_t *mac, uint32_t ip)
+{
+       struct static_lease *new_static_lease;
+
+       /* Build new node */
+       new_static_lease = xzalloc(sizeof(struct static_lease));
+       memcpy(new_static_lease->mac, mac, 6);
+       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 {
+               struct static_lease *cur = *lease_struct;
+               while (cur->next)
+                       cur = cur->next;
+               cur->next = new_static_lease;
+       }
+}
+
+/* Check to see if a mac has an associated static lease */
+uint32_t FAST_FUNC getIpByMac(struct static_lease *lease_struct, void *mac)
+{
+       while (lease_struct) {
+               if (memcmp(lease_struct->mac, mac, 6) == 0)
+                       return lease_struct->ip;
+               lease_struct = lease_struct->next;
+       }
+
+       return 0;
+}
+
+/* Check to see if an ip is reserved as a static ip */
+int FAST_FUNC reservedIp(struct static_lease *lease_struct, uint32_t ip)
+{
+       while (lease_struct) {
+               if (lease_struct->ip == ip)
+                       return 1;
+               lease_struct = lease_struct->next;
+       }
+
+       return 0;
+}
+
+#if ENABLE_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 FAST_FUNC printStaticLeases(struct static_lease **arg)
+{
+       struct static_lease *cur = *arg;
+
+       while (cur) {
+               printf("PrintStaticLeases: Lease mac Value: %02x:%02x:%02x:%02x:%02x:%02x\n",
+                       cur->mac[0], cur->mac[1], cur->mac[2],
+                       cur->mac[3], cur->mac[4], cur->mac[5]
+               );
+               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 (file)
index 0000000..00379fc
--- /dev/null
@@ -0,0 +1,162 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * vconfig implementation for busybox
+ *
+ * Copyright (C) 2001  Manuel Novoa III  <mjn3@codepoet.org>
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+/* BB_AUDIT SUSv3 N/A */
+
+#include "libbb.h"
+#include <net/if.h>
+
+/* 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) {
+               table += table[0];
+               if (!*table) {
+                       bb_show_usage();
+               }
+       }
+       return table - 1;
+}
+
+static const char cmds[] ALIGN1 = {
+       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[] ALIGN1 = {
+       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[] ALIGN1 = "/proc/net/vlan/config";
+
+int vconfig_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+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(ifr));
+
+       ++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 {
+               strncpy_IFNAMSIZ(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);
+       ioctl_or_perror_and_die(fd, SIOCSIFVLAN, &ifr,
+                                               "ioctl error for %s", *argv);
+
+       return 0;
+}
diff --git a/networking/wget.c b/networking/wget.c
new file mode 100644 (file)
index 0000000..4875904
--- /dev/null
@@ -0,0 +1,895 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * wget - retrieve a file using HTTP or FTP
+ *
+ * Chip Rosenthal Covad Communications <chip@laserlink.net>
+ *
+ * Licensed under GPLv2, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.h"
+
+struct host_info {
+       // May be used if we ever will want to free() all xstrdup()s...
+       /* char *allocated; */
+       const char *path;
+       const char *user;
+       char       *host;
+       int         port;
+       smallint    is_ftp;
+};
+
+
+/* Globals (can be accessed from signal handlers) */
+struct globals {
+       off_t content_len;        /* Content-length of the file */
+       off_t beg_range;          /* Range at which continue begins */
+#if ENABLE_FEATURE_WGET_STATUSBAR
+       off_t lastsize;
+       off_t totalsize;
+       off_t transferred;        /* Number of bytes transferred so far */
+       const char *curfile;      /* Name of current file being transferred */
+       unsigned lastupdate_sec;
+       unsigned start_sec;
+#endif
+       smallint chunked;             /* chunked transfer encoding */
+};
+#define G (*(struct globals*)&bb_common_bufsiz1)
+struct BUG_G_too_big {
+       char BUG_G_too_big[sizeof(G) <= COMMON_BUFSIZE ? 1 : -1];
+};
+#define content_len     (G.content_len    )
+#define beg_range       (G.beg_range      )
+#define lastsize        (G.lastsize       )
+#define totalsize       (G.totalsize      )
+#define transferred     (G.transferred    )
+#define curfile         (G.curfile        )
+#define lastupdate_sec  (G.lastupdate_sec )
+#define start_sec       (G.start_sec      )
+#define chunked         (G.chunked        )
+#define INIT_G() do { } while (0)
+
+
+#if ENABLE_FEATURE_WGET_STATUSBAR
+enum {
+       STALLTIME = 5                   /* Seconds when xfer considered "stalled" */
+};
+
+static unsigned int get_tty2_width(void)
+{
+       unsigned width;
+       get_terminal_width_height(2, &width, NULL);
+       return width;
+}
+
+static void progress_meter(int flag)
+{
+       /* We can be called from signal handler */
+       int save_errno = errno;
+       off_t abbrevsize;
+       unsigned since_last_update, elapsed;
+       unsigned ratio;
+       int barlength, i;
+
+       if (flag == -1) { /* first call to progress_meter */
+               start_sec = monotonic_sec();
+               lastupdate_sec = start_sec;
+               lastsize = 0;
+               totalsize = content_len + beg_range; /* as content_len changes.. */
+       }
+
+       ratio = 100;
+       if (totalsize != 0 && !chunked) {
+               /* long long helps to have it working even if !LFS */
+               ratio = (unsigned) (100ULL * (transferred+beg_range) / totalsize);
+               if (ratio > 100) ratio = 100;
+       }
+
+       fprintf(stderr, "\r%-20.20s%4d%% ", curfile, ratio);
+
+       barlength = get_tty2_width() - 49;
+       if (barlength > 0) {
+               /* god bless gcc for variable arrays :) */
+               i = barlength * ratio / 100;
+               {
+                       char buf[i+1];
+                       memset(buf, '*', i);
+                       buf[i] = '\0';
+                       fprintf(stderr, "|%s%*s|", buf, barlength - i, "");
+               }
+       }
+       i = 0;
+       abbrevsize = transferred + beg_range;
+       while (abbrevsize >= 100000) {
+               i++;
+               abbrevsize >>= 10;
+       }
+       /* see http://en.wikipedia.org/wiki/Tera */
+       fprintf(stderr, "%6d%c ", (int)abbrevsize, " kMGTPEZY"[i]);
+
+// Nuts! Ain't it easier to update progress meter ONLY when we transferred++?
+
+       elapsed = monotonic_sec();
+       since_last_update = elapsed - lastupdate_sec;
+       if (transferred > lastsize) {
+               lastupdate_sec = elapsed;
+               lastsize = transferred;
+               if (since_last_update >= STALLTIME) {
+                       /* We "cut off" these seconds from elapsed time
+                        * by adjusting start time */
+                       start_sec += since_last_update;
+               }
+               since_last_update = 0; /* we are un-stalled now */
+       }
+       elapsed -= start_sec; /* now it's "elapsed since start" */
+
+       if (since_last_update >= STALLTIME) {
+               fprintf(stderr, " - stalled -");
+       } else {
+               off_t to_download = totalsize - beg_range;
+               if (transferred <= 0 || (int)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 == 0) {
+               /* last call to progress_meter */
+               alarm(0);
+               transferred = 0;
+               fputc('\n', stderr);
+       } else {
+               if (flag == -1) { /* first call to progress_meter */
+                       signal_SA_RESTART_empty_mask(SIGALRM, progress_meter);
+               }
+               alarm(1);
+       }
+
+       errno = save_errno;
+}
+/* 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. <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.
+ *
+ */
+#else /* FEATURE_WGET_STATUSBAR */
+
+static ALWAYS_INLINE void progress_meter(int flag UNUSED_PARAM) { }
+
+#endif
+
+
+/* Read NMEMB bytes into PTR from STREAM.  Returns the number of bytes read,
+ * and a short count if an eof or non-interrupt error is encountered.  */
+static size_t safe_fread(void *ptr, size_t nmemb, FILE *stream)
+{
+       size_t ret;
+       char *p = (char*)ptr;
+
+       do {
+               clearerr(stream);
+               errno = 0;
+               ret = fread(p, 1, nmemb, stream);
+               p += ret;
+               nmemb -= ret;
+       } while (nmemb && ferror(stream) && errno == EINTR);
+
+       return p - (char*)ptr;
+}
+
+/* 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);
+               errno = 0;
+               ret = fgets(s, size, stream);
+       } while (ret == NULL && ferror(stream) && errno == EINTR);
+
+       return ret;
+}
+
+#if ENABLE_FEATURE_WGET_AUTHENTICATION
+/* Base64-encode character string. buf is assumed to be char buf[512]. */
+static char *base64enc_512(char buf[512], const char *str)
+{
+       unsigned len = strlen(str);
+       if (len > 512/4*3 - 10) /* paranoia */
+               len = 512/4*3 - 10;
+       bb_uuencode(buf, str, len, bb_uuenc_tbl_base64);
+       return buf;
+}
+#endif
+
+
+static FILE *open_socket(len_and_sockaddr *lsa)
+{
+       FILE *fp;
+
+       /* glibc 2.4 seems to try seeking on it - ??! */
+       /* hopefully it understands what ESPIPE means... */
+       fp = fdopen(xconnect_stream(lsa), "r+");
+       if (fp == NULL)
+               bb_perror_msg_and_die("fdopen");
+
+       return fp;
+}
+
+
+static int ftpcmd(const char *s1, const 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;
+}
+
+
+static void parse_url(char *src_url, struct host_info *h)
+{
+       char *url, *p, *sp;
+
+       /* 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 + 1;
+       } 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;
+       }
+
+       sp = strrchr(h->host, '@');
+       h->user = NULL;
+       if (sp != NULL) {
+               h->user = h->host;
+               *sp = '\0';
+               h->host = sp + 1;
+       }
+
+       sp = h->host;
+}
+
+
+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)
+               continue;
+       if (*s == '\n')
+               return NULL;
+
+       /* convert the header name to lower case */
+       for (s = buf; isalnum(*s) || *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 */
+       *s++ = '\0';
+       hdrval = skip_whitespace(s);
+
+       /* locate the end of header */
+       while (*s && *s != '\r' && *s != '\n')
+               ++s;
+
+       /* end of header found */
+       if (*s) {
+               *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')
+               continue;
+       /* *istrunc = 1; */
+       return hdrval;
+}
+
+#if ENABLE_FEATURE_WGET_LONG_OPTIONS
+static char *URL_escape(const char *str)
+{
+       /* URL encode, see RFC 2396 */
+       char *dst;
+       char *res = dst = xmalloc(strlen(str) * 3 + 1);
+       unsigned char c;
+
+       while (1) {
+               c = *str++;
+               if (c == '\0'
+               /* || strchr("!&'()*-.=_~", c) - more code */
+                || c == '!'
+                || c == '&'
+                || c == '\''
+                || c == '('
+                || c == ')'
+                || c == '*'
+                || c == '-'
+                || c == '.'
+                || c == '='
+                || c == '_'
+                || c == '~'
+                || (c >= '0' && c <= '9')
+                || ((c|0x20) >= 'a' && (c|0x20) <= 'z')
+               ) {
+                       *dst++ = c;
+                       if (c == '\0')
+                               return res;
+               } else {
+                       *dst++ = '%';
+                       *dst++ = bb_hexdigits_upcase[c >> 4];
+                       *dst++ = bb_hexdigits_upcase[c & 0xf];
+               }
+       }
+}
+#endif
+
+int wget_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int wget_main(int argc UNUSED_PARAM, char **argv)
+{
+       char buf[512];
+       struct host_info server, target;
+       len_and_sockaddr *lsa;
+       int status;
+       int port;
+       int try = 5;
+       unsigned opt;
+       char *str;
+       char *proxy = 0;
+       char *dir_prefix = NULL;
+#if ENABLE_FEATURE_WGET_LONG_OPTIONS
+       char *post_data;
+       char *extra_headers = NULL;
+       llist_t *headers_llist = NULL;
+#endif
+       FILE *sfp = NULL;               /* socket to web/ftp server         */
+       FILE *dfp;                      /* socket to ftp server (data)      */
+       char *fname_out;                /* where to direct output (-O)      */
+       bool got_clen = 0;              /* got content-length: from server  */
+       int output_fd = -1;
+       bool 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";/* "User-Agent" header field        */
+
+       static const char keywords[] ALIGN1 =
+               "content-length\0""transfer-encoding\0""chunked\0""location\0";
+       enum {
+               KEY_content_length = 1, KEY_transfer_encoding, KEY_chunked, KEY_location
+       };
+       enum {
+               WGET_OPT_CONTINUE   = (1 << 0),
+               WGET_OPT_SPIDER     = (1 << 1),
+               WGET_OPT_QUIET      = (1 << 2),
+               WGET_OPT_OUTNAME    = (1 << 3),
+               WGET_OPT_PREFIX     = (1 << 4),
+               WGET_OPT_PROXY      = (1 << 5),
+               WGET_OPT_USER_AGENT = (1 << 6),
+               WGET_OPT_RETRIES    = (1 << 7),
+               WGET_OPT_NETWORK_READ_TIMEOUT = (1 << 8),
+               WGET_OPT_PASSIVE    = (1 << 9),
+               WGET_OPT_HEADER     = (1 << 10) * ENABLE_FEATURE_WGET_LONG_OPTIONS,
+               WGET_OPT_POST_DATA  = (1 << 11) * ENABLE_FEATURE_WGET_LONG_OPTIONS,
+       };
+#if ENABLE_FEATURE_WGET_LONG_OPTIONS
+       static const char wget_longopts[] ALIGN1 =
+               /* name, has_arg, val */
+               "continue\0"         No_argument       "c"
+               "spider\0"           No_argument       "s"
+               "quiet\0"            No_argument       "q"
+               "output-document\0"  Required_argument "O"
+               "directory-prefix\0" Required_argument "P"
+               "proxy\0"            Required_argument "Y"
+               "user-agent\0"       Required_argument "U"
+               /* Ignored: */
+               // "tries\0"            Required_argument "t"
+               // "timeout\0"          Required_argument "T"
+               /* Ignored (we always use PASV): */
+               "passive-ftp\0"      No_argument       "\xff"
+               "header\0"           Required_argument "\xfe"
+               "post-data\0"        Required_argument "\xfd"
+               ;
+#endif
+
+       INIT_G();
+
+#if ENABLE_FEATURE_WGET_LONG_OPTIONS
+       applet_long_options = wget_longopts;
+#endif
+       /* server.allocated = target.allocated = NULL; */
+       opt_complementary = "-1" USE_FEATURE_WGET_LONG_OPTIONS(":\xfe::");
+       opt = getopt32(argv, "csqO:P:Y:U:" /*ignored:*/ "t:T:",
+                               &fname_out, &dir_prefix,
+                               &proxy_flag, &user_agent,
+                               NULL, /* -t RETRIES */
+                               NULL /* -T NETWORK_READ_TIMEOUT */
+                               USE_FEATURE_WGET_LONG_OPTIONS(, &headers_llist)
+                               USE_FEATURE_WGET_LONG_OPTIONS(, &post_data)
+                               );
+       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;
+               while (ll) {
+                       size += strlen(ll->data) + 2;
+                       ll = ll->link;
+               }
+               extra_headers = cp = xmalloc(size);
+               while (headers_llist) {
+                       cp += sprintf(cp, "%s\r\n", (char*)llist_pop(&headers_llist));
+               }
+       }
+#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 there was no -O FILE */
+       if (!(opt & WGET_OPT_OUTNAME)) {
+               fname_out = bb_get_last_path_component_nostrip(target.path);
+               /* handle "wget http://kernel.org//" */
+               if (fname_out[0] == '/' || !fname_out[0])
+                       fname_out = (char*)"index.html";
+               /* -P DIR is considered only if there was no -O FILE */
+               if (dir_prefix)
+                       fname_out = concat_path_file(dir_prefix, fname_out);
+       } else {
+               if (LONE_DASH(fname_out)) {
+                       /* -O - */
+                       output_fd = 1;
+                       opt &= ~WGET_OPT_CONTINUE;
+               }
+       }
+#if ENABLE_FEATURE_WGET_STATUSBAR
+       curfile = bb_get_last_path_component_nostrip(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 (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... */
+       lsa = xhost2sockaddr(server.host, server.port);
+       if (!(opt & WGET_OPT_QUIET)) {
+               fprintf(stderr, "Connecting to %s (%s)\n", server.host,
+                               xmalloc_sockaddr2dotted(&lsa->u.sa));
+               /* We leak result of xmalloc_sockaddr2dotted */
+       }
+
+       if (use_proxy || !target.is_ftp) {
+               /*
+                *  HTTP session
+                */
+               do {
+                       got_clen = 0;
+                       chunked = 0;
+
+                       if (!--try)
+                               bb_error_msg_and_die("too many redirections");
+
+                       /* Open socket to http server */
+                       if (sfp) fclose(sfp);
+                       sfp = open_socket(lsa);
+
+                       /* Send HTTP request.  */
+                       if (use_proxy) {
+                               fprintf(sfp, "GET %stp://%s/%s HTTP/1.1\r\n",
+                                       target.is_ftp ? "f" : "ht", target.host,
+                                       target.path);
+                       } else {
+                               if (opt & WGET_OPT_POST_DATA)
+                                       fprintf(sfp, "POST /%s HTTP/1.1\r\n", 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, "Proxy-Authorization: Basic %s\r\n"+6,
+                                       base64enc_512(buf, target.user));
+                       }
+                       if (use_proxy && server.user) {
+                               fprintf(sfp, "Proxy-Authorization: Basic %s\r\n",
+                                       base64enc_512(buf, server.user));
+                       }
+#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);
+
+                       if (opt & WGET_OPT_POST_DATA) {
+                               char *estr = URL_escape(post_data);
+                               fprintf(sfp, "Content-Type: application/x-www-form-urlencoded\r\n");
+                               fprintf(sfp, "Content-Length: %u\r\n" "\r\n" "%s",
+                                               (int) strlen(estr), estr);
+                               /*fprintf(sfp, "Connection: Keep-Alive\r\n\r\n");*/
+                               /*fprintf(sfp, "%s\r\n", estr);*/
+                               free(estr);
+                       } else
+#endif
+                       { /* If "Connection:" is needed, document why */
+                               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");
+
+                       str = buf;
+                       str = skip_non_whitespace(str);
+                       str = skip_whitespace(str);
+                       // FIXME: no error check
+                       // xatou wouldn't work: "200 OK"
+                       status = atoi(str);
+                       switch (status) {
+                       case 0:
+                       case 100:
+                               while (gethdr(buf, sizeof(buf), sfp /*, &n*/) != NULL)
+                                       /* eat all remaining headers */;
+                               goto read_response;
+                       case 200:
+/*
+Response 204 doesn't say "null file", it says "metadata
+has changed but data didn't":
+
+"10.2.5 204 No Content
+The server has fulfilled the request but does not need to return
+an entity-body, and might want to return updated metainformation.
+The response MAY include new or updated metainformation in the form
+of entity-headers, which if present SHOULD be associated with
+the requested variant.
+
+If the client is a user agent, it SHOULD NOT change its document
+view from that which caused the request to be sent. This response
+is primarily intended to allow input for actions to take place
+without causing a change to the user agent's active document view,
+although any new or updated metainformation SHOULD be applied
+to the document currently in the user agent's active view.
+
+The 204 response MUST NOT include a message-body, and thus
+is always terminated by the first empty line after the header fields."
+
+However, in real world it was observed that some web servers
+(e.g. Boa/0.94.14rc21) simply use code 204 when file size is zero.
+*/
+                       case 204:
+                               break;
+                       case 300:       /* redirection */
+                       case 301:
+                       case 302:
+                       case 303:
+                               break;
+                       case 206:
+                               if (beg_range)
+                                       break;
+                               /* fall through */
+                       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 ((str = gethdr(buf, sizeof(buf), sfp /*, &n*/)) != NULL) {
+                               /* gethdr did already convert the "FOO:" string to lowercase */
+                               smalluint key = index_in_strings(keywords, *&buf) + 1;
+                               if (key == KEY_content_length) {
+                                       content_len = BB_STRTOOFF(str, NULL, 10);
+                                       if (errno || content_len < 0) {
+                                               bb_error_msg_and_die("content-length %s is garbage", str);
+                                       }
+                                       got_clen = 1;
+                                       continue;
+                               }
+                               if (key == KEY_transfer_encoding) {
+                                       if (index_in_strings(keywords, str_tolower(str)) + 1 != KEY_chunked)
+                                               bb_error_msg_and_die("transfer encoding '%s' is not supported", str);
+                                       chunked = got_clen = 1;
+                               }
+                               if (key == KEY_location) {
+                                       if (str[0] == '/')
+                                               /* free(target.allocated); */
+                                               target.path = /* target.allocated = */ xstrdup(str+1);
+                                       else {
+                                               parse_url(str, &target);
+                                               if (use_proxy == 0) {
+                                                       server.host = target.host;
+                                                       server.port = target.port;
+                                               }
+                                               free(lsa);
+                                               lsa = xhost2sockaddr(server.host, server.port);
+                                               break;
+                                       }
+                               }
+                       }
+               } while (status >= 300);
+
+               dfp = sfp;
+
+       } else {
+
+               /*
+                *  FTP session
+                */
+               if (!target.user)
+                       target.user = xstrdup("anonymous:busybox@");
+
+               sfp = open_socket(lsa);
+               if (ftpcmd(NULL, NULL, sfp, buf) != 220)
+                       bb_error_msg_and_die("%s", buf+4);
+
+               /*
+                * Splitting username:password pair,
+                * trying to log in
+                */
+               str = strchr(target.user, ':');
+               if (str)
+                       *(str++) = '\0';
+               switch (ftpcmd("USER ", target.user, sfp, buf)) {
+               case 230:
+                       break;
+               case 331:
+                       if (ftpcmd("PASS ", str, sfp, buf) == 230)
+                               break;
+                       /* fall through (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
+               str = strrchr(buf, ')');
+               if (str) str[0] = '\0';
+               str = strrchr(buf, ',');
+               if (!str) goto pasv_error;
+               port = xatou_range(str+1, 0, 255);
+               *str = '\0';
+               str = strrchr(buf, ',');
+               if (!str) goto pasv_error;
+               port += xatou_range(str+1, 0, 255) * 256;
+               set_nport(lsa, htons(port));
+               dfp = open_socket(lsa);
+
+               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 %s: %s", "RETR", buf);
+       }
+
+       if (opt & WGET_OPT_SPIDER) {
+               if (ENABLE_FEATURE_CLEAN_UP)
+                       fclose(sfp);
+               return EXIT_SUCCESS;
+       }
+
+       /*
+        * Retrieve file
+        */
+
+       /* Do it before progress_meter (want to have nice error message) */
+       if (output_fd < 0) {
+               int o_flags = O_WRONLY | O_CREAT | O_TRUNC | O_EXCL;
+               /* compat with wget: -O FILE can overwrite */
+               if (opt & WGET_OPT_OUTNAME)
+                       o_flags = O_WRONLY | O_CREAT | O_TRUNC;
+               output_fd = xopen(fname_out, o_flags);
+       }
+
+       if (!(opt & WGET_OPT_QUIET))
+               progress_meter(-1);
+
+       if (chunked)
+               goto get_clen;
+
+       /* Loops only if chunked */
+       while (1) {
+               while (content_len > 0 || !got_clen) {
+                       int n;
+                       unsigned rdsz = sizeof(buf);
+
+                       if (content_len < sizeof(buf) && (chunked || got_clen))
+                               rdsz = (unsigned)content_len;
+                       n = safe_fread(buf, rdsz, dfp);
+                       if (n <= 0) {
+                               if (ferror(dfp)) {
+                                       /* perror will not work: ferror doesn't set errno */
+                                       bb_error_msg_and_die(bb_msg_read_error);
+                               }
+                               break;
+                       }
+                       xwrite(output_fd, buf, n);
+#if ENABLE_FEATURE_WGET_STATUSBAR
+                       transferred += n;
+#endif
+                       if (got_clen)
+                               content_len -= n;
+               }
+
+               if (!chunked)
+                       break;
+
+               safe_fgets(buf, sizeof(buf), dfp); /* This is a newline */
+ get_clen:
+               safe_fgets(buf, sizeof(buf), dfp);
+               content_len = STRTOOFF(buf, NULL, 16);
+               /* FIXME: error check? */
+               if (content_len == 0)
+                       break; /* all done! */
+       }
+
+       if (!(opt & WGET_OPT_QUIET))
+               progress_meter(0);
+
+       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);
+       }
+
+       return EXIT_SUCCESS;
+}
diff --git a/networking/zcip.c b/networking/zcip.c
new file mode 100644 (file)
index 0000000..df4c0ec
--- /dev/null
@@ -0,0 +1,567 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * RFC3927 ZeroConf IPv4 Link-Local addressing
+ * (see <http://www.zeroconf.org/>)
+ *
+ * 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 <netinet/ether.h>
+#include <net/ethernet.h>
+#include <net/if.h>
+#include <net/if_arp.h>
+#include <linux/if_packet.h>
+#include <linux/sockios.h>
+
+#include "libbb.h"
+#include <syslog.h>
+
+/* We don't need more than 32 bits of the counter */
+#define MONOTONIC_US() ((unsigned)monotonic_us())
+
+struct arp_packet {
+       struct ether_header eth;
+       struct ether_arp arp;
+} 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(...) do { } while (0)
+
+
+enum {
+       sock_fd = 3
+};
+
+struct globals {
+       struct sockaddr saddr;
+       struct ether_addr eth_addr;
+};
+#define G (*(struct globals*)&bb_common_bufsiz1)
+#define saddr    (G.saddr   )
+#define eth_addr (G.eth_addr)
+
+
+/**
+ * Pick a random link local IP address on 169.254/16, except that
+ * the first and last 256 addresses are reserved.
+ */
+static uint32_t pick(void)
+{
+       unsigned tmp;
+
+       do {
+               tmp = rand() & IN_CLASSB_HOST;
+       } while (tmp > (IN_CLASSB_HOST - 0x0200));
+       return htonl((LINKLOCAL_ADDR + 0x0100) + tmp);
+}
+
+/**
+ * Broadcast an ARP packet.
+ */
+static void arp(
+       /* int op, - always ARPOP_REQUEST */
+       /* const struct ether_addr *source_eth, - always &eth_addr */
+                                       struct in_addr source_ip,
+       const struct ether_addr *target_eth, struct in_addr target_ip)
+{
+       enum { op = ARPOP_REQUEST };
+#define source_eth (&eth_addr)
+
+       struct arp_packet p;
+       memset(&p, 0, sizeof(p));
+
+       // ether header
+       p.eth.ether_type = htons(ETHERTYPE_ARP);
+       memcpy(p.eth.ether_shost, source_eth, ETH_ALEN);
+       memset(p.eth.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_eth, ETH_ALEN);
+       memcpy(&p.arp.arp_spa, &source_ip, sizeof(p.arp.arp_spa));
+       memcpy(&p.arp.arp_tha, target_eth, ETH_ALEN);
+       memcpy(&p.arp.arp_tpa, &target_ip, sizeof(p.arp.arp_tpa));
+
+       // send it
+       // Even though sock_fd is already bound to saddr, just send()
+       // won't work, because "socket is not connected"
+       // (and connect() won't fix that, "operation not supported").
+       // Thus we sendto() to saddr. I wonder which sockaddr
+       // (from bind() or from sendto()?) kernel actually uses
+       // to determine iface to emit the packet from...
+       xsendto(sock_fd, &p, sizeof(p), &saddr, sizeof(saddr));
+#undef source_eth
+}
+
+/**
+ * Run a script.
+ * argv[0]:intf argv[1]:script_name argv[2]:junk argv[3]:NULL
+ */
+static int run(char *argv[3], const char *param, struct in_addr *ip)
+{
+       int status;
+       char *addr = addr; /* for gcc */
+       const char *fmt = "%s %s %s" + 3;
+
+       argv[2] = (char*)param;
+
+       VDBG("%s run %s %s\n", argv[0], argv[1], argv[2]);
+
+       if (ip) {
+               addr = inet_ntoa(*ip);
+               xsetenv("ip", addr);
+               fmt -= 3;
+       }
+       bb_info_msg(fmt, argv[2], argv[0], addr);
+
+       status = wait4pid(spawn(argv + 1));
+       if (status < 0) {
+               bb_perror_msg("%s %s %s" + 3, argv[2], argv[0]);
+               return -errno;
+       }
+       if (status != 0)
+               bb_error_msg("script %s %s failed, exitcode=%d", argv[1], argv[2], status);
+       return status;
+}
+
+/**
+ * Return milliseconds of random delay, up to "secs" seconds.
+ */
+static ALWAYS_INLINE unsigned random_delay_ms(unsigned secs)
+{
+       return rand() % (secs * 1000);
+}
+
+/**
+ * main program
+ */
+int zcip_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int zcip_main(int argc, char **argv)
+{
+       int state;
+       char *r_opt;
+       unsigned opts;
+
+       // ugly trick, but I want these zeroed in one go
+       struct {
+               const struct in_addr null_ip;
+               const struct ether_addr null_addr;
+               struct in_addr ip;
+               struct ifreq ifr;
+               int timeout_ms; /* must be signed */
+               unsigned conflicts;
+               unsigned nprobes;
+               unsigned nclaims;
+               int ready;
+               int verbose;
+       } L;
+#define null_ip    (L.null_ip   )
+#define null_addr  (L.null_addr )
+#define ip         (L.ip        )
+#define ifr        (L.ifr       )
+#define timeout_ms (L.timeout_ms)
+#define conflicts  (L.conflicts )
+#define nprobes    (L.nprobes   )
+#define nclaims    (L.nclaims   )
+#define ready      (L.ready     )
+#define verbose    (L.verbose   )
+
+       memset(&L, 0, sizeof(L));
+
+#define FOREGROUND (opts & 1)
+#define QUIT       (opts & 2)
+       // parse commandline: prog [options] ifname script
+       // exactly 2 args; -v accumulates and implies -f
+       opt_complementary = "=2:vv:vf";
+       opts = getopt32(argv, "fqr:v", &r_opt, &verbose);
+#if !BB_MMU
+       // on NOMMU reexec early (or else we will rerun things twice)
+       if (!FOREGROUND)
+               bb_daemonize_or_rexec(0 /*was: DAEMON_CHDIR_ROOT*/, argv);
+#endif
+       // open an ARP socket
+       // (need to do it before openlog to prevent openlog from taking
+       // fd 3 (sock_fd==3))
+       xmove_fd(xsocket(AF_PACKET, SOCK_PACKET, htons(ETH_P_ARP)), sock_fd);
+       if (!FOREGROUND) {
+               // do it before all bb_xx_msg calls
+               openlog(applet_name, 0, LOG_DAEMON);
+               logmode |= LOGMODE_SYSLOG;
+       }
+       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 - 1;
+
+       /* Now: argv[0]:junk argv[1]:intf argv[2]:script argv[3]:NULL */
+       /* We need to make space for script argument: */
+       argv[0] = argv[1];
+       argv[1] = argv[2];
+       /* Now: argv[0]:intf argv[1]:script argv[2]:junk argv[3]:NULL */
+#define argv_intf (argv[0])
+
+       xsetenv("interface", argv_intf);
+
+       // initialize the interface (modprobe, ifup, etc)
+       if (run(argv, "init", NULL))
+               return EXIT_FAILURE;
+
+       // initialize saddr
+       // saddr is: { u16 sa_family; u8 sa_data[14]; }
+       //memset(&saddr, 0, sizeof(saddr));
+       //TODO: are we leaving sa_family == 0 (AF_UNSPEC)?!
+       safe_strncpy(saddr.sa_data, argv_intf, sizeof(saddr.sa_data));
+
+       // bind to the interface's ARP socket
+       xbind(sock_fd, &saddr, sizeof(saddr));
+
+       // get the interface's ethernet address
+       //memset(&ifr, 0, sizeof(ifr));
+       strncpy_IFNAMSIZ(ifr.ifr_name, argv_intf);
+       xioctl(sock_fd, SIOCGIFHWADDR, &ifr);
+       memcpy(&eth_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.
+       // we are taking low-order four bytes, as top-order ones
+       // aren't random enough.
+       // NOTE: the sequence of addresses we try changes only
+       // depending on when we detect conflicts.
+       {
+               uint32_t t;
+               move_from_unaligned32(t, ((char *)&eth_addr + 2));
+               srand(t);
+       }
+       if (ip.s_addr == 0)
+               ip.s_addr = pick();
+
+       // FIXME cases to handle:
+       //  - zcip already running!
+       //  - link already has local address... just defend/update
+
+       // daemonize now; don't delay system startup
+       if (!FOREGROUND) {
+#if BB_MMU
+               bb_daemonize(0 /*was: DAEMON_CHDIR_ROOT*/);
+#endif
+               bb_info_msg("start, interface %s", argv_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 uses it
+       //  - arp announcements that we're claiming it
+       //  - use it
+       //  - defend it, within limits
+       // exit if:
+       // - address is successfully obtained and -q was given:
+       //   run "<script> config", then exit with exitcode 0
+       // - poll error (when does this happen?)
+       // - read error (when does this happen?)
+       // - sendto error (in arp()) (when does this happen?)
+       // - revents & POLLERR (link down). run "<script> deconfig" first
+       state = PROBE;
+       while (1) {
+               struct pollfd fds[1];
+               unsigned deadline_us;
+               struct arp_packet p;
+               int source_ip_conflict;
+               int target_ip_conflict;
+
+               fds[0].fd = sock_fd;
+               fds[0].events = POLLIN;
+               fds[0].revents = 0;
+
+               // poll, being ready to adjust current timeout
+               if (!timeout_ms) {
+                       timeout_ms = random_delay_ms(PROBE_WAIT);
+                       // FIXME setsockopt(sock_fd, SO_ATTACH_FILTER, ...) to
+                       // make the kernel filter out all packets except
+                       // ones we'd care about.
+               }
+               // set deadline_us to the point in time when we timeout
+               deadline_us = MONOTONIC_US() + timeout_ms * 1000;
+
+               VDBG("...wait %d %s nprobes=%u, nclaims=%u\n",
+                               timeout_ms, argv_intf, nprobes, nclaims);
+
+               switch (safe_poll(fds, 1, timeout_ms)) {
+
+               default:
+                       //bb_perror_msg("poll"); - done in safe_poll
+                       return EXIT_FAILURE;
+
+               // 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/%u %s@%s\n",
+                                                       nprobes, argv_intf, inet_ntoa(ip));
+                                       arp(/* ARPOP_REQUEST, */
+                                                       /* &eth_addr, */ null_ip,
+                                                       &null_addr, ip);
+                                       timeout_ms = PROBE_MIN * 1000;
+                                       timeout_ms += random_delay_ms(PROBE_MAX - PROBE_MIN);
+                               }
+                               else {
+                                       // Switch to announce state.
+                                       state = ANNOUNCE;
+                                       nclaims = 0;
+                                       VDBG("announce/%u %s@%s\n",
+                                                       nclaims, argv_intf, inet_ntoa(ip));
+                                       arp(/* ARPOP_REQUEST, */
+                                                       /* &eth_addr, */ ip,
+                                                       &eth_addr, ip);
+                                       timeout_ms = 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/%u %s@%s\n",
+                                               nclaims, argv_intf, inet_ntoa(ip));
+                               arp(/* ARPOP_REQUEST, */
+                                               /* &eth_addr, */ ip,
+                                               &eth_addr, ip);
+                               timeout_ms = 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/%u %s@%s\n",
+                                                       nclaims, argv_intf, inet_ntoa(ip));
+                                       arp(/* ARPOP_REQUEST, */
+                                                       /* &eth_addr, */ ip,
+                                                       &eth_addr, ip);
+                                       timeout_ms = ANNOUNCE_INTERVAL * 1000;
+                               }
+                               else {
+                                       // Switch to monitor state.
+                                       state = MONITOR;
+                                       // link is ok to use earlier
+                                       // FIXME update filters
+                                       run(argv, "config", &ip);
+                                       ready = 1;
+                                       conflicts = 0;
+                                       timeout_ms = -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_ms = -1;
+                               conflicts = 0;
+                               break;
+                       default:
+                               // Invalid, should never happen.  Restart the whole protocol.
+                               state = PROBE;
+                               ip.s_addr = pick();
+                               timeout_ms = 0;
+                               nprobes = 0;
+                               nclaims = 0;
+                               break;
+                       } // switch (state)
+                       break; // case 0 (timeout)
+
+               // packets arriving, or link went down
+               case 1:
+                       // We need to adjust the timeout in case we didn't receive
+                       // a conflicting packet.
+                       if (timeout_ms > 0) {
+                               unsigned diff = deadline_us - MONOTONIC_US();
+                               if ((int)(diff) < 0) {
+                                       // Current time is greater than the expected timeout time.
+                                       // Should never happen.
+                                       VDBG("missed an expected timeout\n");
+                                       timeout_ms = 0;
+                               } else {
+                                       VDBG("adjusting timeout\n");
+                                       timeout_ms = (diff / 1000) | 1; /* never 0 */
+                               }
+                       }
+
+                       if ((fds[0].revents & POLLIN) == 0) {
+                               if (fds[0].revents & POLLERR) {
+                                       // FIXME: links routinely go down;
+                                       // this shouldn't necessarily exit.
+                                       bb_error_msg("iface %s is down", argv_intf);
+                                       if (ready) {
+                                               run(argv, "deconfig", &ip);
+                                       }
+                                       return EXIT_FAILURE;
+                               }
+                               continue;
+                       }
+
+                       // read ARP packet
+                       if (safe_read(sock_fd, &p, sizeof(p)) < 0) {
+                               bb_perror_msg_and_die(bb_msg_read_error);
+                       }
+                       if (p.eth.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",
+                                       argv_intf, ntohs(p.eth.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;
+
+                       source_ip_conflict = 0;
+                       target_ip_conflict = 0;
+
+                       if (memcmp(p.arp.arp_spa, &ip.s_addr, sizeof(struct in_addr)) == 0
+                        && memcmp(&p.arp.arp_sha, &eth_addr, ETH_ALEN) != 0
+                       ) {
+                               source_ip_conflict = 1;
+                       }
+                       if (p.arp.arp_op == htons(ARPOP_REQUEST)
+                        && memcmp(p.arp.arp_tpa, &ip.s_addr, sizeof(struct in_addr)) == 0
+                        && memcmp(&p.arp.arp_tha, &eth_addr, 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", argv_intf);
+                                               timeout_ms = RATE_LIMIT_INTERVAL * 1000;
+                                               state = RATE_LIMIT_PROBE;
+                                       }
+
+                                       // restart the whole protocol
+                                       ip.s_addr = pick();
+                                       timeout_ms = 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_ms = DEFEND_INTERVAL * 1000;
+                                       arp(/* ARPOP_REQUEST, */
+                                               /* &eth_addr, */ ip,
+                                               &eth_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(argv, "deconfig", &ip);
+
+                                       // restart the whole protocol
+                                       ip.s_addr = pick();
+                                       timeout_ms = 0;
+                                       nprobes = 0;
+                                       nclaims = 0;
+                               }
+                               break;
+                       default:
+                               // Invalid, should never happen.  Restart the whole protocol.
+                               VDBG("invalid state -- starting over\n");
+                               state = PROBE;
+                               ip.s_addr = pick();
+                               timeout_ms = 0;
+                               nprobes = 0;
+                               nclaims = 0;
+                               break;
+                       } // switch state
+                       break; // case 1 (packets arriving)
+               } // switch poll
+       } // while (1)
+#undef argv_intf
+}
diff --git a/printutils/Config.in b/printutils/Config.in
new file mode 100644 (file)
index 0000000..6912ece
--- /dev/null
@@ -0,0 +1,26 @@
+#
+# For a description of the syntax of this configuration file,
+# see scripts/kbuild/config-language.txt.
+#
+
+menu "Print Utilities"
+
+config LPD
+       bool "lpd"
+       default n
+       help
+         lpd is a print spooling daemon.
+
+config LPR
+       bool "lpr"
+       default n
+       help
+         lpr sends files (or standard input) to a print spooling daemon.
+
+config LPQ
+       bool "lpq"
+       default n
+       help
+         lpq is a print spool queue examination and manipulation program.
+
+endmenu
diff --git a/printutils/Kbuild b/printutils/Kbuild
new file mode 100644 (file)
index 0000000..008290e
--- /dev/null
@@ -0,0 +1,9 @@
+# Makefile for busybox
+#
+# Licensed under the GPL v2, see the file LICENSE in this tarball.
+
+lib-y :=
+
+lib-$(CONFIG_LPD) += lpd.o
+lib-$(CONFIG_LPR) += lpr.o
+lib-$(CONFIG_LPQ) += lpr.o
diff --git a/printutils/lpd.c b/printutils/lpd.c
new file mode 100644 (file)
index 0000000..43c2294
--- /dev/null
@@ -0,0 +1,282 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * micro lpd
+ *
+ * Copyright (C) 2008 by Vladimir Dronnikov <dronnikov@gmail.com>
+ *
+ * Licensed under GPLv2, see file LICENSE in this tarball for details.
+ */
+
+/*
+ * A typical usage of BB lpd looks as follows:
+ * # tcpsvd -E 0 515 lpd [SPOOLDIR] [HELPER-PROG [ARGS...]]
+ *
+ * This starts TCP listener on port 515 (default for LP protocol).
+ * When a client connection is made (via lpr) lpd first changes its
+ * working directory to SPOOLDIR (current dir is the default).
+ *
+ * SPOOLDIR is the spool directory which contains printing queues
+ * and should have the following structure:
+ *
+ * SPOOLDIR/
+ *      <queue1>
+ *      ...
+ *      <queueN>
+ *
+ * <queueX> can be of two types:
+ *      A. a printer character device, an ordinary file or a link to such;
+ *      B. a directory.
+ *
+ * In case A lpd just dumps the data it receives from client (lpr) to the
+ * end of queue file/device. This is non-spooling mode.
+ *
+ * In case B lpd enters spooling mode. It reliably saves client data along
+ * with control info in two unique files under the queue directory. These
+ * files are named dfAXXXHHHH and cfAXXXHHHH, where XXX is the job number
+ * and HHHH is the client hostname. Unless a printing helper application
+ * is specified lpd is done at this point.
+ *
+ * NB: file names are produced by peer! They actually may be anything at all.
+ * lpd only sanitizes them (by removing most non-alphanumerics).
+ *
+ * If HELPER-PROG (with optional arguments) is specified then lpd continues
+ * to process client data:
+ *      1. it reads and parses control file (cfA...). The parse process
+ *      results in setting environment variables whose values were passed
+ *      in control file; when parsing is complete, lpd deletes control file.
+ *      2. it spawns specified helper application. It is then
+ *      the helper application who is responsible for both actual printing
+ *      and deleting of processed data file.
+ *
+ * A good lpr passes control files which when parsed provides the following
+ * variables:
+ * $H = host which issues the job
+ * $P = user who prints
+ * $C = class of printing (what is printed on banner page)
+ * $J = the name of the job
+ * $L = print banner page
+ * $M = the user to whom a mail should be sent if a problem occurs
+ *
+ * We specifically filter out and NOT provide:
+ * $l = name of datafile ("dfAxxx") - file whose content are to be printed
+ *
+ * lpd provides $DATAFILE instead - the ACTUAL name
+ * of the datafile under which it was saved.
+ * $l would be not reliable (you would be at mercy of remote peer).
+ *
+ * Thus, a typical helper can be something like this:
+ * #!/bin/sh
+ * cat ./"$DATAFILE" >/dev/lp0
+ * mv -f ./"$DATAFILE" save/
+ */
+
+#include "libbb.h"
+
+// strip argument of bad chars
+static char *sane(char *str)
+{
+       char *s = str;
+       char *p = s;
+       while (*s) {
+               if (isalnum(*s) || '-' == *s || '_' == *s) {
+                       *p++ = *s;
+               }
+               s++;
+       }
+       *p = '\0';
+       return str;
+}
+
+static char *xmalloc_read_stdin(void)
+{
+       // SECURITY:
+       size_t max = 4 * 1024; // more than enough for commands!
+       return xmalloc_reads(STDIN_FILENO, NULL, &max);
+}
+
+int lpd_main(int argc, char *argv[]) MAIN_EXTERNALLY_VISIBLE;
+int lpd_main(int argc UNUSED_PARAM, char *argv[])
+{
+       int spooling = spooling; // for compiler
+       char *s, *queue;
+       char *filenames[2];
+
+       // goto spool directory
+       if (*++argv)
+               xchdir(*argv++);
+
+       // error messages of xfuncs will be sent over network
+       xdup2(STDOUT_FILENO, STDERR_FILENO);
+
+       // nullify ctrl/data filenames
+       memset(filenames, 0, sizeof(filenames));
+
+       // read command
+       s = queue = xmalloc_read_stdin();
+       // we understand only "receive job" command
+       if (2 != *queue) {
+ unsupported_cmd:
+               printf("Command %02x %s\n",
+                       (unsigned char)s[0], "is not supported");
+               goto err_exit;
+       }
+
+       // parse command: "2 | QUEUE_NAME | '\n'"
+       queue++;
+       // protect against "/../" attacks
+       // *strchrnul(queue, '\n') = '\0'; - redundant, sane() will do
+       if (!*sane(queue))
+               return EXIT_FAILURE;
+
+       // queue is a directory -> chdir to it and enter spooling mode
+       spooling = chdir(queue) + 1; // 0: cannot chdir, 1: done
+       // we don't free(s), we might need "queue" var later
+
+       while (1) {
+               char *fname;
+               int fd;
+               // int is easier than ssize_t: can use xatoi_u,
+               // and can correctly display error returns (-1)
+               int expected_len, real_len;
+
+               // signal OK
+               safe_write(STDOUT_FILENO, "", 1);
+
+               // get subcommand
+               // valid s must be of form: "SUBCMD | LEN | space | FNAME"
+               // N.B. we bail out on any error
+               s = xmalloc_read_stdin();
+               if (!s) { // (probably) EOF
+                       char *p, *q, var[2];
+
+                       // non-spooling mode or no spool helper specified
+                       if (!spooling || !*argv)
+                               return EXIT_SUCCESS; // the only non-error exit
+                       // spooling mode but we didn't see both ctrlfile & datafile
+                       if (spooling != 7)
+                               goto err_exit; // reject job
+
+                       // spooling mode and spool helper specified -> exec spool helper
+                       // (we exit 127 if helper cannot be executed)
+                       var[1] = '\0';
+                       // read and delete ctrlfile
+                       q = xmalloc_xopen_read_close(filenames[0], NULL);
+                       unlink(filenames[0]);
+                       // provide datafile name
+                       // we can use leaky setenv since we are about to exec or exit
+                       xsetenv("DATAFILE", filenames[1]);
+                       // parse control file by "\n"
+                       while ((p = strchr(q, '\n')) != NULL && isalpha(*q)) {
+                               *p++ = '\0';
+                               // q is a line of <SYM><VALUE>,
+                               // we are setting environment string <SYM>=<VALUE>.
+                               // Ignoring "l<datafile>", exporting others:
+                               if (*q != 'l') {
+                                       var[0] = *q++;
+                                       xsetenv(var, q);
+                               }
+                               q = p; // next line
+                       }
+                       // helper should not talk over network.
+                       // this call reopens stdio fds to "/dev/null"
+                       // (no daemonization is done)
+                       bb_daemonize_or_rexec(DAEMON_DEVNULL_STDIO | DAEMON_ONLY_SANITIZE, NULL);
+                       BB_EXECVP(*argv, argv);
+                       exit(127);
+               }
+
+               // validate input.
+               // we understand only "control file" or "data file" cmds
+               if (2 != s[0] && 3 != s[0])
+                       goto unsupported_cmd;
+               if (spooling & (1 << (s[0]-1))) {
+                       printf("Duplicated subcommand\n");
+                       goto err_exit;
+               }
+               // get filename
+               *strchrnul(s, '\n') = '\0';
+               fname = strchr(s, ' ');
+               if (!fname) {
+// bad_fname:
+                       printf("No or bad filename\n");
+                       goto err_exit;
+               }
+               *fname++ = '\0';
+//             // s[0]==2: ctrlfile, must start with 'c'
+//             // s[0]==3: datafile, must start with 'd'
+//             if (fname[0] != s[0] + ('c'-2))
+//                     goto bad_fname;
+               // get length
+               expected_len = bb_strtou(s + 1, NULL, 10);
+               if (errno || expected_len < 0) {
+                       printf("Bad length\n");
+                       goto err_exit;
+               }
+               if (2 == s[0] && expected_len > 16 * 1024) {
+                       // SECURITY:
+                       // ctrlfile can't be big (we want to read it back later!)
+                       printf("File is too big\n");
+                       goto err_exit;
+               }
+
+               // open the file
+               if (spooling) {
+                       // spooling mode: dump both files
+                       // job in flight has mode 0200 "only writable"
+                       sane(fname);
+                       fd = open3_or_warn(fname, O_CREAT | O_WRONLY | O_TRUNC | O_EXCL, 0200);
+                       if (fd < 0)
+                               goto err_exit;
+                       filenames[s[0] - 2] = xstrdup(fname);
+               } else {
+                       // non-spooling mode:
+                       // 2: control file (ignoring), 3: data file
+                       fd = -1;
+                       if (3 == s[0])
+                               fd = xopen(queue, O_RDWR | O_APPEND);
+               }
+
+               // signal OK
+               safe_write(STDOUT_FILENO, "", 1);
+
+               // copy the file
+               real_len = bb_copyfd_size(STDIN_FILENO, fd, expected_len);
+               if (real_len != expected_len) {
+                       printf("Expected %d but got %d bytes\n",
+                               expected_len, real_len);
+                       goto err_exit;
+               }
+               // get EOF indicator, see whether it is NUL (ok)
+               // (and don't trash s[0]!)
+               if (safe_read(STDIN_FILENO, &s[1], 1) != 1 || s[1] != 0) {
+                       // don't send error msg to peer - it obviously
+                       // doesn't follow the protocol, so probably
+                       // it can't understand us either
+                       goto err_exit;
+               }
+
+               if (spooling) {
+                       // chmod completely downloaded file as "readable+writable"
+                       fchmod(fd, 0600);
+                       // accumulate dump state
+                       // N.B. after all files are dumped spooling should be 1+2+4==7
+                       spooling |= (1 << (s[0]-1)); // bit 1: ctrlfile; bit 2: datafile
+               }
+
+               free(s);
+               close(fd); // NB: can do close(-1). Who cares?
+
+               // NB: don't do "signal OK" write here, it will be done
+               // at the top of the loop
+       } // while (1)
+
+ err_exit:
+       // don't keep corrupted files
+       if (spooling) {
+#define i spooling
+               for (i = 2; --i >= 0; )
+                       if (filenames[i])
+                               unlink(filenames[i]);
+       }
+       return EXIT_FAILURE;
+}
diff --git a/printutils/lpr.c b/printutils/lpr.c
new file mode 100644 (file)
index 0000000..f21cffd
--- /dev/null
@@ -0,0 +1,256 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * bare bones version of lpr & lpq: BSD printing utilities
+ *
+ * Copyright (C) 2008 by Vladimir Dronnikov <dronnikov@gmail.com>
+ *
+ * Original idea and code:
+ *      Walter Harms <WHarms@bfs.de>
+ *
+ * Licensed under GPLv2, see file LICENSE in this tarball for details.
+ *
+ * See RFC 1179 for protocol description.
+ */
+#include "libbb.h"
+
+/*
+ * LPD returns binary 0 on success.
+ * Otherwise it returns error message.
+ */
+static void get_response_or_say_and_die(int fd, const char *errmsg)
+{
+       ssize_t sz;
+       char buf[128];
+
+       buf[0] = ' ';
+       sz = safe_read(fd, buf, 1);
+       if ('\0' != buf[0]) {
+               // request has failed
+               // try to make sure last char is '\n', but do not add
+               // superfluous one
+               sz = full_read(fd, buf + 1, 126);
+               bb_error_msg("error while %s%s", errmsg,
+                               (sz > 0 ? ". Server said:" : ""));
+               if (sz > 0) {
+                       // sz = (bytes in buf) - 1
+                       if (buf[sz] != '\n')
+                               buf[++sz] = '\n';
+                       safe_write(STDERR_FILENO, buf, sz + 1);
+               }
+               xfunc_die();
+       }
+}
+
+int lpqr_main(int argc, char *argv[]) MAIN_EXTERNALLY_VISIBLE;
+int lpqr_main(int argc UNUSED_PARAM, char *argv[])
+{
+       enum {
+               OPT_P           = 1 << 0, // -P queue[@host[:port]]. If no -P is given use $PRINTER, then "lp@localhost:515"
+               OPT_U           = 1 << 1, // -U username
+
+               LPR_V           = 1 << 2, // -V: be verbose
+               LPR_h           = 1 << 3, // -h: want banner printed
+               LPR_C           = 1 << 4, // -C class: job "class" (? supposedly printed on banner)
+               LPR_J           = 1 << 5, // -J title: the job title for the banner page
+               LPR_m           = 1 << 6, // -m: send mail back to user
+
+               LPQ_SHORT_FMT   = 1 << 2, // -s: short listing format
+               LPQ_DELETE      = 1 << 3, // -d: delete job(s)
+               LPQ_FORCE       = 1 << 4, // -f: force waiting job(s) to be printed
+       };
+       char tempfile[sizeof("/tmp/lprXXXXXX")];
+       const char *job_title;
+       const char *printer_class = "";   // printer class, max 32 char
+       const char *queue;                // name of printer queue
+       const char *server = "localhost"; // server[:port] of printer queue
+       char *hostname;
+       // N.B. IMHO getenv("USER") can be way easily spoofed!
+       const char *user = xuid2uname(getuid());
+       unsigned job;
+       unsigned opts;
+       int fd;
+
+       // parse options
+       // TODO: set opt_complementary: s,d,f are mutually exclusive
+       opts = getopt32(argv,
+               (/*lp*/'r' == applet_name[2]) ? "P:U:VhC:J:m" : "P:U:sdf"
+               , &queue, &user
+               , &printer_class, &job_title
+       );
+       argv += optind;
+
+       // if queue is not specified -> use $PRINTER
+       if (!(opts & OPT_P))
+               queue = getenv("PRINTER");
+       // if queue is still not specified ->
+       if (!queue) {
+               // ... queue defaults to "lp"
+               // server defaults to "localhost"
+               queue = "lp";
+       // if queue is specified ->
+       } else {
+               // queue name is to the left of '@'
+               char *s = strchr(queue, '@');
+               if (s) {
+                       // server name is to the right of '@'
+                       *s = '\0';
+                       server = s + 1;
+               }
+       }
+
+       // do connect
+       fd = create_and_connect_stream_or_die(server, 515);
+
+       //
+       // LPQ ------------------------
+       //
+       if (/*lp*/'q' == applet_name[2]) {
+               char cmd;
+               // force printing of every job still in queue
+               if (opts & LPQ_FORCE) {
+                       cmd = 1;
+                       goto command;
+               // delete job(s)
+               } else if (opts & LPQ_DELETE) {
+                       fdprintf(fd, "\x5" "%s %s", queue, user);
+                       while (*argv) {
+                               fdprintf(fd, " %s", *argv++);
+                       }
+                       bb_putchar('\n');
+               // dump current jobs status
+               // N.B. periodical polling should be achieved
+               // via "watch -n delay lpq"
+               // They say it's the UNIX-way :)
+               } else {
+                       cmd = (opts & LPQ_SHORT_FMT) ? 3 : 4;
+ command:
+                       fdprintf(fd, "%c" "%s\n", cmd, queue);
+                       bb_copyfd_eof(fd, STDOUT_FILENO);
+               }
+
+               return EXIT_SUCCESS;
+       }
+
+       //
+       // LPR ------------------------
+       //
+       if (opts & LPR_V)
+               bb_error_msg("connected to server");
+
+       job = getpid() % 1000;
+       hostname = safe_gethostname();
+
+       // no files given on command line? -> use stdin
+       if (!*argv)
+               *--argv = (char *)"-";
+
+       fdprintf(fd, "\x2" "%s\n", queue);
+       get_response_or_say_and_die(fd, "setting queue");
+
+       // process files
+       do {
+               unsigned cflen;
+               int dfd;
+               struct stat st;
+               char *c;
+               char *remote_filename;
+               char *controlfile;
+
+               // if data file is stdin, we need to dump it first
+               if (LONE_DASH(*argv)) {
+                       strcpy(tempfile, "/tmp/lprXXXXXX");
+                       dfd = mkstemp(tempfile);
+                       if (dfd < 0)
+                               bb_perror_msg_and_die("mkstemp");
+                       bb_copyfd_eof(STDIN_FILENO, dfd);
+                       xlseek(dfd, 0, SEEK_SET);
+                       *argv = (char*)bb_msg_standard_input;
+               } else {
+                       dfd = xopen(*argv, O_RDONLY);
+               }
+
+               /* "The name ... should start with ASCII "cfA",
+                * followed by a three digit job number, followed
+                * by the host name which has constructed the file."
+                * We supply 'c' or 'd' as needed for control/data file. */
+               remote_filename = xasprintf("fA%03u%s", job, hostname);
+
+               // create control file
+               // TODO: all lines but 2 last are constants! How we can use this fact?
+               controlfile = xasprintf(
+                       "H" "%.32s\n" "P" "%.32s\n" /* H HOST, P USER */
+                       "C" "%.32s\n" /* C CLASS - printed on banner page (if L cmd is also given) */
+                       "J" "%.99s\n" /* J JOBNAME */
+                       /* "class name for banner page and job name
+                        * for banner page commands must precede L command" */
+                       "L" "%.32s\n" /* L USER - print banner page, with given user's name */
+                       "M" "%.32s\n" /* M WHOM_TO_MAIL */
+                       "l" "d%.31s\n" /* l DATA_FILE_NAME ("dfAxxx") */
+                       , hostname, user
+                       , printer_class /* can be "" */
+                       , ((opts & LPR_J) ? job_title : *argv)
+                       , (opts & LPR_h) ? user : ""
+                       , (opts & LPR_m) ? user : ""
+                       , remote_filename
+               );
+               // delete possible "\nX\n" patterns
+               c = controlfile;
+               cflen = (unsigned)strlen(controlfile);
+               while ((c = strchr(c, '\n')) != NULL) {
+                       if (c[1] && c[2] == '\n') {
+                               /* can't use strcpy, results are undefined */
+                               memmove(c, c+2, cflen - (c-controlfile) - 1);
+                               cflen -= 2;
+                       } else {
+                               c++;
+                       }
+               }
+
+               // send control file
+               if (opts & LPR_V)
+                       bb_error_msg("sending control file");
+               /* "Acknowledgement processing must occur as usual
+                * after the command is sent." */
+               fdprintf(fd, "\x2" "%u c%s\n", cflen, remote_filename);
+               get_response_or_say_and_die(fd, "sending control file");
+               /* "Once all of the contents have
+                * been delivered, an octet of zero bits is sent as
+                * an indication that the file being sent is complete.
+                * A second level of acknowledgement processing
+                * must occur at this point." */
+               full_write(fd, controlfile, cflen + 1); /* writes NUL byte too */
+               get_response_or_say_and_die(fd, "sending control file");
+
+               // send data file, with name "dfaXXX"
+               if (opts & LPR_V)
+                       bb_error_msg("sending data file");
+               st.st_size = 0; /* paranoia: fstat may theoretically fail */
+               fstat(dfd, &st);
+               fdprintf(fd, "\x3" "%"OFF_FMT"u d%s\n", st.st_size, remote_filename);
+               get_response_or_say_and_die(fd, "sending data file");
+               if (bb_copyfd_size(dfd, fd, st.st_size) != st.st_size) {
+                       // We're screwed. We sent less bytes than we advertised.
+                       bb_error_msg_and_die("local file changed size?!");
+               }
+               write(fd, "", 1); // send ACK
+               get_response_or_say_and_die(fd, "sending data file");
+
+               // delete temporary file if we dumped stdin
+               if (*argv == (char*)bb_msg_standard_input)
+                       unlink(tempfile);
+
+               // cleanup
+               close(fd);
+               free(remote_filename);
+               free(controlfile);
+
+               // say job accepted
+               if (opts & LPR_V)
+                       bb_error_msg("job accepted");
+
+               // next, please!
+               job = (job + 1) % 1000;
+       } while (*++argv);
+
+       return EXIT_SUCCESS;
+}
diff --git a/procps/Config.in b/procps/Config.in
new file mode 100644 (file)
index 0000000..702442a
--- /dev/null
@@ -0,0 +1,200 @@
+#
+# 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 NMETER
+       bool "nmeter"
+       default n
+       help
+         Prints selected system stats continuously, one line per update.
+
+config PGREP
+       bool "pgrep"
+       default n
+       help
+         Look for processes by name.
+
+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 PKILL
+       bool "pkill"
+       default n
+       help
+         Send signals to processes by name.
+
+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 FEATURE_PS_TIME
+       bool "Enable time and elapsed time output"
+       default n
+       depends on PS && DESKTOP
+       help
+         Support -o time and -o etime output specifiers.
+
+config FEATURE_PS_UNUSUAL_SYSTEMS
+       bool "Support Linux prior to 2.4.0 and non-ELF systems"
+       default n
+       depends on FEATURE_PS_TIME
+       help
+         Include support for measuring HZ on old kernels and non-ELF systems
+         (if you are on Linux 2.4.0+ and use ELF, you don't need this)
+
+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 "Show CPU per-process usage percentage"
+       default y
+       depends on TOP
+       help
+         Make top display CPU usage for each process.
+         This adds about 2k.
+
+config FEATURE_TOP_CPU_GLOBAL_PERCENTS
+       bool "Show CPU global usage percentage"
+       default y
+       depends on FEATURE_TOP_CPU_USAGE_PERCENTAGE
+       help
+         Makes top display "CPU: NN% usr NN% sys..." line.
+         This adds about 0.5k.
+
+config FEATURE_TOP_SMP_CPU
+       bool "SMP CPU usage display ('c' key)"
+       default n
+       depends on FEATURE_TOP_CPU_GLOBAL_PERCENTS
+       help
+         Allow 'c' key to switch between individual/cumulative CPU stats
+         This adds about 0.5k.
+
+config FEATURE_TOP_DECIMALS
+       bool "Show 1/10th of a percent in CPU/mem statistics"
+       default n
+       depends on FEATURE_TOP_CPU_USAGE_PERCENTAGE
+       help
+         Show 1/10th of a percent in CPU/mem statistics.
+         This adds about 0.3k.
+
+config FEATURE_TOP_SMP_PROCESS
+       bool "Show CPU process runs on ('j' field)"
+       default n
+       depends on TOP
+       help
+         Show CPU where process was last found running on.
+         This is the 'j' field.
+
+config FEATURE_TOPMEM
+       bool "Topmem command ('s' key)"
+       default n
+       depends on TOP
+       help
+         Enable 's' in top (gives lots of memory info).
+
+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.
+
+config WATCH
+       bool "watch"
+       default n
+       help
+         watch is used to execute a program periodically, showing
+         output to the screen.
+
+
+endmenu
diff --git a/procps/Kbuild b/procps/Kbuild
new file mode 100644 (file)
index 0000000..8e62fdf
--- /dev/null
@@ -0,0 +1,21 @@
+# 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_FREE)     += free.o
+lib-$(CONFIG_FUSER)    += fuser.o
+lib-$(CONFIG_KILL)     += kill.o
+lib-$(CONFIG_ASH)      += kill.o  # used for built-in kill by ash
+lib-$(CONFIG_NMETER)    += nmeter.o
+lib-$(CONFIG_PGREP)    += pgrep.o
+lib-$(CONFIG_PKILL)    += pgrep.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_WATCH)     += watch.o
diff --git a/procps/free.c b/procps/free.c
new file mode 100644 (file)
index 0000000..e76dd21
--- /dev/null
@@ -0,0 +1,68 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Mini free 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.
+ */
+
+/* getopt not needed */
+
+#include "libbb.h"
+
+int free_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+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 (file)
index 0000000..dc3d01b
--- /dev/null
@@ -0,0 +1,345 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * tiny fuser implementation
+ *
+ * Copyright 2004 Tony J. White
+ *
+ * Licensed under GPLv2, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.h"
+
+#define MAX_LINE 255
+
+#define OPTION_STRING "mks64"
+enum {
+       OPT_MOUNT  = (1 << 0),
+       OPT_KILL   = (1 << 1),
+       OPT_SILENT = (1 << 2),
+       OPT_IP6    = (1 << 3),
+       OPT_IP4    = (1 << 4),
+};
+
+typedef struct inode_list {
+       struct inode_list *next;
+       ino_t inode;
+       dev_t dev;
+} inode_list;
+
+typedef struct pid_list {
+       struct pid_list *next;
+       pid_t pid;
+} pid_list;
+
+static dev_t find_socket_dev(void)
+{
+       int fd = socket(AF_INET, SOCK_DGRAM, 0);
+       if (fd >= 0) {
+               struct stat buf;
+               int r = fstat(fd, &buf);
+               close(fd);
+               if (r == 0)
+                       return buf.st_dev;
+       }
+       return 0;
+}
+
+static int file_to_dev_inode(const char *filename, dev_t *dev, ino_t *inode)
+{
+       struct stat f_stat;
+       if (stat(filename, &f_stat))
+               return 0;
+       *inode = f_stat.st_ino;
+       *dev = f_stat.st_dev;
+       return 1;
+}
+
+static char *parse_net_arg(const char *arg, unsigned *port)
+{
+       char path[20], tproto[5];
+
+       if (sscanf(arg, "%u/%4s", port, tproto) != 2)
+               return NULL;
+       sprintf(path, "/proc/net/%s", tproto);
+       if (access(path, R_OK) != 0)
+               return NULL;
+       return xstrdup(tproto);
+}
+
+static pid_list *add_pid(pid_list *plist, pid_t pid)
+{
+       pid_list *curr = plist;
+       while (curr != NULL) {
+               if (curr->pid == pid)
+                       return plist;
+               curr = curr->next;
+       }
+       curr = xmalloc(sizeof(pid_list));
+       curr->pid = pid;
+       curr->next = plist;
+       return curr;
+}
+
+static inode_list *add_inode(inode_list *ilist, dev_t dev, ino_t inode)
+{
+       inode_list *curr = ilist;
+       while (curr != NULL) {
+               if (curr->inode == inode && curr->dev == dev)
+                       return ilist;
+               curr = curr->next;
+       }
+       curr = xmalloc(sizeof(inode_list));
+       curr->dev = dev;
+       curr->inode = inode;
+       curr->next = ilist;
+       return curr;
+}
+
+static inode_list *scan_proc_net(const char *proto,
+                               unsigned port, inode_list *ilist)
+{
+       char path[20], line[MAX_LINE + 1];
+       ino_t tmp_inode;
+       dev_t tmp_dev;
+       long long uint64_inode;
+       unsigned tmp_port;
+       FILE *f;
+
+       tmp_dev = find_socket_dev();
+
+       sprintf(path, "/proc/net/%s", proto);
+       f = fopen_for_read(path);
+       if (!f)
+               return ilist;
+
+       while (fgets(line, MAX_LINE, f)) {
+               char addr[68];
+               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
+               ) {
+                       int len = strlen(addr);
+                       if (len == 8 && (option_mask32 & OPT_IP6))
+                               continue;
+                       if (len > 8 && (option_mask32 & OPT_IP4))
+                               continue;
+                       if (tmp_port == port) {
+                               tmp_inode = uint64_inode;
+                               ilist = add_inode(ilist, tmp_dev, tmp_inode);
+                       }
+               }
+       }
+       fclose(f);
+       return ilist;
+}
+
+static int search_dev_inode(inode_list *ilist, dev_t dev, ino_t inode)
+{
+       while (ilist) {
+               if (ilist->dev == dev) {
+                       if (option_mask32 & OPT_MOUNT)
+                               return 1;
+                       if (ilist->inode == inode)
+                               return 1;
+               }
+               ilist = ilist->next;
+       }
+       return 0;
+}
+
+static pid_list *scan_pid_maps(const char *fname, pid_t pid,
+                               inode_list *ilist, pid_list *plist)
+{
+       FILE *file;
+       char line[MAX_LINE + 1];
+       int major, minor;
+       ino_t inode;
+       long long uint64_inode;
+       dev_t dev;
+
+       file = fopen_for_read(fname);
+       if (!file)
+               return plist;
+       while (fgets(line, 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 (search_dev_inode(ilist, dev, inode))
+                       plist = add_pid(plist, pid);
+       }
+       fclose(file);
+       return plist;
+}
+
+static pid_list *scan_link(const char *lname, pid_t pid,
+                               inode_list *ilist, pid_list *plist)
+{
+       ino_t inode;
+       dev_t dev;
+
+       if (!file_to_dev_inode(lname, &dev, &inode))
+               return plist;
+       if (search_dev_inode(ilist, dev, inode))
+               plist = add_pid(plist, pid);
+       return plist;
+}
+
+static pid_list *scan_dir_links(const char *dname, pid_t pid,
+                               inode_list *ilist, pid_list *plist)
+{
+       DIR *d;
+       struct dirent *de;
+       char *lname;
+
+       d = opendir(dname);
+       if (!d)
+               return plist;
+       while ((de = readdir(d)) != NULL) {
+               lname = concat_subpath_file(dname, de->d_name);
+               if (lname == NULL)
+                       continue;
+               plist = scan_link(lname, pid, ilist, plist);
+               free(lname);
+       }
+       closedir(d);
+       return plist;
+}
+
+/* NB: does chdir internally */
+static pid_list *scan_proc_pids(inode_list *ilist)
+{
+       DIR *d;
+       struct dirent *de;
+       pid_t pid;
+       pid_list *plist;
+
+       xchdir("/proc");
+       d = opendir("/proc");
+       if (!d)
+               return NULL;
+
+       plist = NULL;
+       while ((de = readdir(d)) != NULL) {
+               pid = (pid_t)bb_strtou(de->d_name, NULL, 10);
+               if (errno)
+                       continue;
+               if (chdir(de->d_name) < 0)
+                       continue;
+               plist = scan_link("cwd", pid, ilist, plist);
+               plist = scan_link("exe", pid, ilist, plist);
+               plist = scan_link("root", pid, ilist, plist);
+               plist = scan_dir_links("fd", pid, ilist, plist);
+               plist = scan_dir_links("lib", pid, ilist, plist);
+               plist = scan_dir_links("mmap", pid, ilist, plist);
+               plist = scan_pid_maps("maps", pid, ilist, plist);
+               xchdir("/proc");
+       }
+       closedir(d);
+       return plist;
+}
+
+static int print_pid_list(pid_list *plist)
+{
+       while (plist != NULL) {
+               printf("%u ", (unsigned)plist->pid);
+               plist = plist->next;
+       }
+       bb_putchar('\n');
+       return 1;
+}
+
+static int kill_pid_list(pid_list *plist, int sig)
+{
+       pid_t mypid = getpid();
+       int success = 1;
+
+       while (plist != NULL) {
+               if (plist->pid != mypid) {
+                       if (kill(plist->pid, sig) != 0) {
+                               bb_perror_msg("kill pid %u", (unsigned)plist->pid);
+                               success = 0;
+                       }
+               }
+               plist = plist->next;
+       }
+       return success;
+}
+
+int fuser_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int fuser_main(int argc UNUSED_PARAM, char **argv)
+{
+       pid_list *plist;
+       inode_list *ilist;
+       char **pp;
+       dev_t dev;
+       ino_t inode;
+       unsigned port;
+       int opt;
+       int success;
+       int killsig;
+/*
+fuser [options] FILEs or PORT/PROTOs
+Find processes which use FILEs or PORTs
+        -m      Find processes which use same fs as FILEs
+        -4      Search only IPv4 space
+        -6      Search only IPv6 space
+        -s      Silent: just exit with 0 if any processes are found
+        -k      Kill found processes (otherwise display PIDs)
+        -SIGNAL Signal to send (default: TERM)
+*/
+       /* Handle -SIGNAL. Oh my... */
+       killsig = SIGTERM;
+       pp = argv;
+       while (*++pp) {
+               char *arg = *pp;
+               if (arg[0] != '-')
+                       continue;
+               if (arg[1] == '-' && arg[2] == '\0') /* "--" */
+                       break;
+               if ((arg[1] == '4' || arg[1] == '6') && arg[2] == '\0')
+                       continue; /* it's "-4" or "-6" */
+               opt = get_signum(&arg[1]);
+               if (opt < 0)
+                       continue;
+               /* "-SIGNAL" option found. Remove it and bail out */
+               killsig = opt;
+               do {
+                       pp[0] = arg = pp[1];
+                       pp++;
+               } while (arg);
+               break;
+       }
+
+       opt = getopt32(argv, OPTION_STRING);
+       argv += optind;
+
+       ilist = NULL;
+       pp = argv;
+       while (*pp) {
+               char *proto = parse_net_arg(*pp, &port);
+               if (proto) { /* PORT/PROTO */
+                       ilist = scan_proc_net(proto, port, ilist);
+                       free(proto);
+               } else { /* FILE */
+                       if (!file_to_dev_inode(*pp, &dev, &inode))
+                               bb_perror_msg_and_die("can't open '%s'", *pp);
+                       ilist = add_inode(ilist, dev, inode);
+               }
+               pp++;
+       }
+
+       plist = scan_proc_pids(ilist); /* changes dir to "/proc" */
+
+       if (!plist)
+               return EXIT_FAILURE;
+       success = 1;
+       if (opt & OPT_KILL) {
+               success = kill_pid_list(plist, killsig);
+       } else if (!(opt & OPT_SILENT)) {
+               success = print_pid_list(plist);
+       }
+       return (success != 1); /* 0 == success */
+}
diff --git a/procps/kill.c b/procps/kill.c
new file mode 100644 (file)
index 0000000..1f82069
--- /dev/null
@@ -0,0 +1,223 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Mini kill/killall[5] implementation for busybox
+ *
+ * Copyright (C) 1995, 1996 by Bruce Perens <bruce@pixar.com>.
+ * Copyright (C) 1999-2004 by Erik Andersen <andersen@codepoet.org>
+ *
+ * Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
+ */
+
+#include "libbb.h"
+
+/* Note: kill_main is directly called from shell in order to implement
+ * kill built-in. Shell substitutes job ids with process groups first.
+ *
+ * This brings some complications:
+ *
+ * + we can't use xfunc here
+ * + we can't use applet_name
+ * + we can't use bb_show_usage
+ * (Above doesn't apply for killall[5] cases)
+ *
+ * kill %n gets translated into kill ' -<process group>' by shell (note space!)
+ * This is needed to avoid collision with kill -9 ... syntax
+ */
+
+int kill_main(int argc, char **argv)
+{
+       char *arg;
+       pid_t pid;
+       int signo = SIGTERM, errors = 0, quiet = 0;
+#if !ENABLE_KILLALL && !ENABLE_KILLALL5
+#define killall 0
+#define killall5 0
+#else
+/* How to determine who we are? find 3rd char from the end:
+ * kill, killall, killall5
+ *  ^i       ^a        ^l  - it's unique
+ * (checking from the start is complicated by /bin/kill... case) */
+       const char char3 = argv[0][strlen(argv[0]) - 3];
+#define killall (ENABLE_KILLALL && char3 == 'a')
+#define killall5 (ENABLE_KILLALL5 && char3 == 'l')
+#endif
+
+       /* Parse any options */
+       argc--;
+       arg = *++argv;
+
+       if (argc < 1 || arg[0] != '-') {
+               goto do_it_now;
+       }
+
+       /* The -l option, which prints out signal names.
+        * Intended usage in shell:
+        * echo "Died of SIG`kill -l $?`"
+        * We try to mimic what kill from coreutils-6.8 does */
+       if (arg[1] == 'l' && arg[2] == '\0') {
+               if (argc == 1) {
+                       /* Print the whole signal list */
+                       print_signames();
+                       return 0;
+               }
+               /* -l <sig list> */
+               while ((arg = *++argv)) {
+                       if (isdigit(arg[0])) {
+                               signo = bb_strtou(arg, NULL, 10);
+                               if (errno) {
+                                       bb_error_msg("unknown signal '%s'", arg);
+                                       return EXIT_FAILURE;
+                               }
+                               /* Exitcodes >= 0x80 are to be treated
+                                * as "killed by signal (exitcode & 0x7f)" */
+                               puts(get_signame(signo & 0x7f));
+                               /* TODO: 'bad' signal# - coreutils says:
+                                * kill: 127: invalid signal
+                                * we just print "127" instead */
+                       } else {
+                               signo = get_signum(arg);
+                               if (signo < 0) {
+                                       bb_error_msg("unknown signal '%s'", arg);
+                                       return EXIT_FAILURE;
+                               }
+                               printf("%d\n", signo);
+                       }
+               }
+               /* 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;
+       }
+
+       arg++; /* skip '-' */
+
+       /* -o PID? (if present, it always is at the end of command line) */
+       if (killall5 && arg[0] == 'o')
+               goto do_it_now;
+
+       if (argc > 1 && arg[0] == 's' && arg[1] == '\0') { /* -s SIG? */
+               argc--;
+               arg = *++argv;
+       } /* else it must be -SIG */
+       signo = get_signum(arg);
+       if (signo < 0) { /* || signo > MAX_SIGNUM ? */
+               bb_error_msg("bad signal name '%s'", arg);
+               return EXIT_FAILURE;
+       }
+       arg = *++argv;
+       argc--;
+
+ do_it_now:
+       pid = getpid();
+
+       if (killall5) {
+               pid_t sid;
+               procps_status_t* p = NULL;
+               int ret = 0;
+
+               /* Find out our session id */
+               sid = getsid(pid);
+               /* Stop all processes */
+               kill(-1, SIGSTOP);
+               /* Signal all processes except those in our session */
+               while ((p = procps_scan(p, PSSCAN_PID|PSSCAN_SID))) {
+                       int i;
+
+                       if (p->sid == (unsigned)sid
+                        || p->pid == (unsigned)pid
+                        || p->pid == 1)
+                               continue;
+
+                       /* All remaining args must be -o PID options.
+                        * Check p->pid against them. */
+                       for (i = 0; i < argc; i++) {
+                               pid_t omit;
+
+                               arg = argv[i];
+                               if (arg[0] != '-' || arg[1] != 'o') {
+                                       bb_error_msg("bad option '%s'", arg);
+                                       ret = 1;
+                                       goto resume;
+                               }
+                               arg += 2;
+                               if (!arg[0] && argv[++i])
+                                       arg = argv[i];
+                               omit = bb_strtoi(arg, NULL, 10);
+                               if (errno) {
+                                       bb_error_msg("bad pid '%s'", arg);
+                                       ret = 1;
+                                       goto resume;
+                               }
+                               if (p->pid == omit)
+                                       goto dont_kill;
+                       }
+                       kill(p->pid, signo);
+ dont_kill: ;
+               }
+ resume:
+               /* And let them continue */
+               kill(-1, SIGCONT);
+               return ret;
+       }
+
+       /* Pid or name is required for kill/killall */
+       if (argc < 1) {
+               bb_error_msg("you need to specify whom to kill");
+               return EXIT_FAILURE;
+       }
+
+       if (killall) {
+               /* Looks like they want to do a killall.  Do that */
+               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) {
+               /* Support shell 'space' trick */
+               if (arg[0] == ' ')
+                       arg++;
+               pid = bb_strtoi(arg, NULL, 10);
+               if (errno) {
+                       bb_error_msg("bad pid '%s'", arg);
+                       errors++;
+               } else if (kill(pid, signo) != 0) {
+                       bb_perror_msg("cannot kill pid %d", (int)pid);
+                       errors++;
+               }
+               arg = *++argv;
+       }
+       return errors;
+}
diff --git a/procps/nmeter.c b/procps/nmeter.c
new file mode 100644 (file)
index 0000000..0358ccd
--- /dev/null
@@ -0,0 +1,910 @@
+/*
+** 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
+//TODO: use sysinfo libc call/syscall, if appropriate
+// (faster than open/read/close):
+// sysinfo({uptime=15017, loads=[5728, 15040, 16480]
+//  totalram=2107416576, freeram=211525632, sharedram=0, bufferram=157204480}
+//  totalswap=134209536, freeswap=134209536, procs=157})
+
+#include "libbb.h"
+
+typedef unsigned long long ullong;
+
+enum {  /* Preferably use powers of 2 */
+       PROC_MIN_FILE_SIZE = 256,
+       PROC_MAX_FILE_SIZE = 16 * 1024,
+};
+
+typedef struct proc_file {
+       char *file;
+       int file_sz;
+       smallint last_gen;
+} proc_file;
+
+static const char *const proc_name[] = {
+       "stat",         // Must match the order of proc_file's!
+       "loadavg",
+       "net/dev",
+       "meminfo",
+       "diskstats",
+       "sys/fs/file-nr"
+};
+
+struct globals {
+       // Sample generation flip-flop
+       smallint gen;
+       // Linux 2.6? (otherwise assumes 2.4)
+       smallint is26;
+       // 1 if sample delay is not an integer fraction of a second
+       smallint need_seconds;
+       char *cur_outbuf;
+       const char *final_str;
+       int delta;
+       int deltanz;
+       struct timeval tv;
+#define first_proc_file proc_stat
+       proc_file proc_stat;    // Must match the order of proc_name's!
+       proc_file proc_loadavg;
+       proc_file proc_net_dev;
+       proc_file proc_meminfo;
+       proc_file proc_diskstats;
+       proc_file proc_sys_fs_filenr;
+};
+#define G (*ptr_to_globals)
+#define gen                (G.gen               )
+#define is26               (G.is26              )
+#define need_seconds       (G.need_seconds      )
+#define cur_outbuf         (G.cur_outbuf        )
+#define final_str          (G.final_str         )
+#define delta              (G.delta             )
+#define deltanz            (G.deltanz           )
+#define tv                 (G.tv                )
+#define proc_stat          (G.proc_stat         )
+#define proc_loadavg       (G.proc_loadavg      )
+#define proc_net_dev       (G.proc_net_dev      )
+#define proc_meminfo       (G.proc_meminfo      )
+#define proc_diskstats     (G.proc_diskstats    )
+#define proc_sys_fs_filenr (G.proc_sys_fs_filenr)
+#define INIT_G() do { \
+       SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
+       cur_outbuf = outbuf; \
+       final_str = "\n"; \
+       deltanz = delta = 1000000; \
+} while (0)
+
+// We depend on this being a char[], not char* - we take sizeof() of it
+#define outbuf bb_common_bufsiz1
+
+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) {
+               xwrite(STDOUT_FILENO, 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 void readfile_z(proc_file *pf, const char* fname)
+{
+// open_read_close() will do two reads in order to be sure we are at EOF,
+// and we don't need/want that.
+       int fd;
+       int sz, rdsz;
+       char *buf;
+
+       sz = pf->file_sz;
+       buf = pf->file;
+       if (!buf) {
+               buf = xmalloc(PROC_MIN_FILE_SIZE);
+               sz = PROC_MIN_FILE_SIZE;
+       }
+ again:
+       fd = xopen(fname, O_RDONLY);
+       buf[0] = '\0';
+       rdsz = read(fd, buf, sz-1);
+       close(fd);
+       if (rdsz > 0) {
+               if (rdsz == sz-1 && sz < PROC_MAX_FILE_SIZE) {
+                       sz *= 2;
+                       buf = xrealloc(buf, sz);
+                       goto again;
+               }
+               buf[rdsz] = '\0';
+       }
+       pf->file_sz = sz;
+       pf->file = buf;
+}
+
+static const char* get_file(proc_file *pf)
+{
+       if (pf->last_gen != gen) {
+               pf->last_gen = gen;
+               readfile_z(pf, proc_name[pf - &first_proc_file]);
+       }
+       return pf->file;
+}
+
+static 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 = rd; // for compiler
+       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];
+
+       /* see http://en.wikipedia.org/wiki/Tera */
+       smart_ulltoa4(ul, buf, " kmgtpezy");
+       buf[4] = '\0';
+       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 UNUSED_PARAM)
+{
+}
+
+static s_stat* init_literal(void)
+{
+       s_stat *s = xzalloc(sizeof(*s));
+       s->collect = collect_literal;
+       return (s_stat*)s;
+}
+
+static s_stat* init_delay(const char *param)
+{
+       delta = strtoul(param, NULL, 0) * 1000; /* param can be "" */
+       deltanz = delta > 0 ? delta : 1;
+       need_seconds = (1000000%deltanz) != 0;
+       return NULL;
+}
+
+static s_stat* init_cr(const char *param UNUSED_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; i < CPU_FIELDCNT; i++) {
+               ullong old = s->old[i];
+               if (data[i] < old) old = data[i];               //sanitize
+               s->old[i] = data[i];
+               all += (data[i] -= old);
+       }
+
+       if (all) {
+               for (i = 0; i < CPU_FIELDCNT; i++) {
+                       ullong t = bar_sz * data[i];
+                       norm_all += data[i] = t / all;
+                       frac[i] = t % all;
+               }
+
+               while (norm_all < bar_sz) {
+                       unsigned max = frac[0];
+                       int pos = 0;
+                       for (i = 1; i < CPU_FIELDCNT; i++) {
+                               if (frac[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 = xzalloc(sizeof(*s));
+       s->collect = collect_cpu;
+       sz = strtoul(param, NULL, 0); /* param can be "" */
+       if (sz < 10) sz = 10;
+       if (sz > 1000) sz = 1000;
+       s->bar = xzalloc(sz+1);
+       /*s->bar[sz] = '\0'; - xzalloc did it */
+       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 = xzalloc(sizeof(*s));
+       s->collect = collect_int;
+       if (param[0] == '\0') {
+               s->no = 1;
+       } else {
+               int n = xatoi_u(param);
+               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 UNUSED_PARAM)
+{
+       ctx_stat *s = xzalloc(sizeof(*s));
+       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 UNUSED_PARAM)
+{
+       blk_stat *s = xzalloc(sizeof(*s));
+       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 UNUSED_PARAM)
+{
+       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 = xzalloc(sizeof(*s));
+       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 = xzalloc(sizeof(*s));
+
+       if (!device || !device[0])
+               bb_show_usage();
+       s->collect = collect_if;
+
+       s->device = device;
+       s->device_colon = xasprintf("%s:", device);
+       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 == 't') {
+               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 = xzalloc(sizeof(*s));
+       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 UNUSED_PARAM)
+{
+       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 UNUSED_PARAM)
+{
+       swp_stat *s = xzalloc(sizeof(*s));
+       s->collect = collect_swp;
+       return (s_stat*)s;
+}
+
+
+S_STAT(fd_stat)
+S_STAT_END(fd_stat)
+
+static void collect_fd(fd_stat *s UNUSED_PARAM)
+{
+       ullong data[2];
+
+       if (rdval(get_file(&proc_sys_fs_filenr), "", data, 1, 2)) {
+               put_question_marks(4);
+               return;
+       }
+
+       scale(data[0] - data[1]);
+}
+
+static s_stat* init_fd(const char *param UNUSED_PARAM)
+{
+       fd_stat *s = xzalloc(sizeof(*s));
+       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 = xzalloc(sizeof(*s));
+
+       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 ^= 1;
+       while (s) {
+               put(s->label);
+               s->collect(s);
+               s = s->next;
+       }
+}
+
+
+typedef s_stat* init_func(const char *param);
+
+static const char options[] ALIGN1 = "ncmsfixptbdr";
+static init_func *const 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) MAIN_EXTERNALLY_VISIBLE;
+int nmeter_main(int argc, char **argv)
+{
+       char buf[32];
+       s_stat *first = NULL;
+       s_stat *last = NULL;
+       s_stat *s;
+       char *cur, *prev;
+
+       INIT_G();
+
+       xchdir("/proc");
+
+       if (argc != 2)
+               bb_show_usage();
+
+       if (open_read_close("version", buf, sizeof(buf)-1) > 0) {
+               buf[sizeof(buf)-1] = '\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] == '%') {    // %%
+                       overlapping_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 = NULL; - all initXXX funcs use xzalloc */
+                       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 = NULL; - all initXXX funcs use xzalloc */
+               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, NULL);
+               usleep(delta > 1000000 ? 1000000 : delta - tv.tv_usec%deltanz);
+       }
+
+       while (1) {
+               gettimeofday(&tv, NULL);
+               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, NULL);
+                       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/procps/pgrep.c b/procps/pgrep.c
new file mode 100644 (file)
index 0000000..0e8e529
--- /dev/null
@@ -0,0 +1,144 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Mini pgrep/pkill implementation for busybox
+ *
+ * Copyright (C) 2007 Loic Grenie <loic.grenie@gmail.com>
+ *
+ * Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
+ */
+
+#include "libbb.h"
+#include "xregex.h"
+
+/* Idea taken from kill.c */
+#define pgrep (ENABLE_PGREP && applet_name[1] == 'g')
+#define pkill (ENABLE_PKILL && applet_name[1] == 'k')
+
+enum {
+       /* "vlfxon" */
+       PGREPOPTBIT_V = 0, /* must be first, we need OPT_INVERT = 0/1 */
+       PGREPOPTBIT_L,
+       PGREPOPTBIT_F,
+       PGREPOPTBIT_X,
+       PGREPOPTBIT_O,
+       PGREPOPTBIT_N,
+};
+
+#define OPT_INVERT     (opt & (1 << PGREPOPTBIT_V))
+#define OPT_LIST       (opt & (1 << PGREPOPTBIT_L))
+#define OPT_FULL       (opt & (1 << PGREPOPTBIT_F))
+#define OPT_ANCHOR     (opt & (1 << PGREPOPTBIT_X))
+#define OPT_FIRST      (opt & (1 << PGREPOPTBIT_O))
+#define OPT_LAST       (opt & (1 << PGREPOPTBIT_N))
+
+static void act(unsigned pid, char *cmd, int signo, unsigned opt)
+{
+       if (pgrep) {
+               if (OPT_LIST)
+                       printf("%d %s\n", pid, cmd);
+               else
+                       printf("%d\n", pid);
+       } else
+               kill(pid, signo);
+}
+
+int pgrep_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int pgrep_main(int argc UNUSED_PARAM, char **argv)
+{
+       unsigned pid = getpid();
+       int signo = SIGTERM;
+       unsigned opt;
+       int scan_mask = PSSCAN_COMM;
+       char *first_arg;
+       int first_arg_idx;
+       int matched_pid;
+       char *cmd_last;
+       procps_status_t *proc;
+       /* These are initialized to 0 */
+       struct {
+               regex_t re_buffer;
+               regmatch_t re_match[1];
+       } Z;
+#define re_buffer (Z.re_buffer)
+#define re_match  (Z.re_match )
+
+       memset(&Z, 0, sizeof(Z));
+
+       /* We must avoid interpreting -NUM (signal num) as an option */
+       first_arg_idx = 1;
+       while (1) {
+               first_arg = argv[first_arg_idx];
+               if (!first_arg)
+                       break;
+               /* not "-<small_letter>..."? */
+               if (first_arg[0] != '-' || first_arg[1] < 'a' || first_arg[1] > 'z') {
+                       argv[first_arg_idx] = NULL; /* terminate argv here */
+                       break;
+               }
+               first_arg_idx++;
+       }
+       opt = getopt32(argv, "vlfxon");
+       argv[first_arg_idx] = first_arg;
+
+       argv += optind;
+       //argc -= optind; - unused anyway
+       if (OPT_FULL)
+               scan_mask |= PSSCAN_ARGVN;
+
+       if (pkill) {
+               if (OPT_LIST) { /* -l: print the whole signal list */
+                       print_signames();
+                       return 0;
+               }
+               if (first_arg && first_arg[0] == '-') {
+                       signo = get_signum(&first_arg[1]);
+                       if (signo < 0) /* || signo > MAX_SIGNUM ? */
+                               bb_error_msg_and_die("bad signal name '%s'", &first_arg[1]);
+                       argv++;
+               }
+       }
+
+       /* One pattern is required */
+       if (!argv[0] || argv[1])
+               bb_show_usage();
+
+       xregcomp(&re_buffer, argv[0], 0);
+       matched_pid = 0;
+       cmd_last = NULL;
+       proc = NULL;
+       while ((proc = procps_scan(proc, scan_mask)) != NULL) {
+               char *cmd;
+               if (proc->pid == pid)
+                       continue;
+               cmd = proc->argv0;
+               if (!cmd) {
+                       cmd = proc->comm;
+               } else {
+                       int i = proc->argv_len;
+                       while (i) {
+                               if (!cmd[i]) cmd[i] = ' ';
+                               i--;
+                       }
+               }
+               /* NB: OPT_INVERT is always 0 or 1 */
+               if ((regexec(&re_buffer, cmd, 1, re_match, 0) == 0 /* match found */
+                    && (!OPT_ANCHOR || (re_match[0].rm_so == 0 && re_match[0].rm_eo == (regoff_t)strlen(cmd)))) ^ OPT_INVERT
+               ) {
+                       matched_pid = proc->pid;
+                       if (OPT_LAST) {
+                               free(cmd_last);
+                               cmd_last = xstrdup(cmd);
+                               continue;
+                       }
+                       act(proc->pid, cmd, signo, opt);
+                       if (OPT_FIRST)
+                               break;
+               }
+       }
+       if (cmd_last) {
+               act(matched_pid, cmd_last, signo, opt);
+               if (ENABLE_FEATURE_CLEAN_UP)
+                       free(cmd_last);
+       }
+       return matched_pid == 0; /* return 1 if no processes listed/signaled */
+}
diff --git a/procps/pidof.c b/procps/pidof.c
new file mode 100644 (file)
index 0000000..1942399
--- /dev/null
@@ -0,0 +1,86 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * pidof 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 "libbb.h"
+
+enum {
+       USE_FEATURE_PIDOF_SINGLE(OPTBIT_SINGLE,)
+       USE_FEATURE_PIDOF_OMIT(  OPTBIT_OMIT  ,)
+       OPT_SINGLE = USE_FEATURE_PIDOF_SINGLE((1<<OPTBIT_SINGLE)) + 0,
+       OPT_OMIT   = USE_FEATURE_PIDOF_OMIT(  (1<<OPTBIT_OMIT  )) + 0,
+};
+
+int pidof_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int pidof_main(int argc UNUSED_PARAM, char **argv)
+{
+       unsigned first = 1;
+       unsigned opt;
+#if ENABLE_FEATURE_PIDOF_OMIT
+       llist_t *omits = NULL; /* list of pids to omit */
+       opt_complementary = "o::";
+#endif
+
+       /* do unconditional option parsing */
+       opt = getopt32(argv, ""
+                       USE_FEATURE_PIDOF_SINGLE ("s")
+                       USE_FEATURE_PIDOF_OMIT("o:", &omits));
+
+#if ENABLE_FEATURE_PIDOF_OMIT
+       /* fill omit list.  */
+       {
+               llist_t *omits_p = omits;
+               while (1) {
+                       omits_p = llist_find_str(omits_p, "%PPID");
+                       if (!omits_p)
+                               break;
+                       /* are we asked to exclude the parent's process ID?  */
+                       omits_p->data = utoa((unsigned)getppid());
+               }
+       }
+#endif
+       /* Looks like everything is set to go.  */
+       argv += optind;
+       while (*argv) {
+               pid_t *pidList;
+               pid_t *pl;
+
+               /* reverse the pidlist like GNU pidof does.  */
+               pidList = pidlist_reverse(find_pid_by_name(*argv));
+               for (pl = pidList; *pl; pl++) {
+#if ENABLE_FEATURE_PIDOF_OMIT
+                       if (opt & OPT_OMIT) {
+                               llist_t *omits_p = omits;
+                               while (omits_p) {
+                                       if (xatoul(omits_p->data) == (unsigned long)(*pl)) {
+                                               goto omitting;
+                                       }
+                                       omits_p = omits_p->link;
+                               }
+                       }
+#endif
+                       printf(" %u" + first, (unsigned)*pl);
+                       first = 0;
+                       if (ENABLE_FEATURE_PIDOF_SINGLE && (opt & OPT_SINGLE))
+                               break;
+#if ENABLE_FEATURE_PIDOF_OMIT
+ omitting: ;
+#endif
+               }
+               free(pidList);
+               argv++;
+       }
+       if (!first)
+               bb_putchar('\n');
+
+#if ENABLE_FEATURE_PIDOF_OMIT
+       if (ENABLE_FEATURE_CLEAN_UP)
+               llist_free(omits, NULL);
+#endif
+       return first; /* 1 (failure) - no processes found */
+}
diff --git a/procps/ps.c b/procps/ps.c
new file mode 100644 (file)
index 0000000..395cfcf
--- /dev/null
@@ -0,0 +1,570 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Mini ps implementation(s) for busybox
+ *
+ * Copyright (C) 1999-2004 by Erik Andersen <andersen@codepoet.org>
+ * Fix for SELinux Support:(c)2007 Hiroshi Shinji <shiroshi@my.email.ne.jp>
+ *                         (c)2007 Yuichi Nakamura <ynakam@hitachisoft.jp>
+ *
+ * Licensed under the GPL version 2, see the file LICENSE in this tarball.
+ */
+
+#include "libbb.h"
+
+/* Absolute maximum on output line length */
+enum { MAX_WIDTH = 2*1024 };
+
+#if ENABLE_DESKTOP
+
+#include <sys/times.h> /* for times() */
+//#include <sys/sysinfo.h> /* for sysinfo() */
+#ifndef AT_CLKTCK
+#define AT_CLKTCK 17
+#endif
+
+
+#if ENABLE_SELINUX
+#define SELINUX_O_PREFIX "label,"
+#define DEFAULT_O_STR    (SELINUX_O_PREFIX "pid,user" USE_FEATURE_PS_TIME(",time") ",args")
+#else
+#define DEFAULT_O_STR    ("pid,user" USE_FEATURE_PS_TIME(",time") ",args")
+#endif
+
+typedef struct {
+       uint16_t width;
+       char name[6];
+       const char *header;
+       void (*f)(char *buf, int size, const procps_status_t *ps);
+       int ps_flags;
+} ps_out_t;
+
+struct globals {
+       ps_out_t* out;
+       int out_cnt;
+       int print_header;
+       int need_flags;
+       char *buffer;
+       unsigned terminal_width;
+#if ENABLE_FEATURE_PS_TIME
+       unsigned kernel_HZ;
+       unsigned long long seconds_since_boot;
+#endif
+       char default_o[sizeof(DEFAULT_O_STR)];
+};
+#define G (*(struct globals*)&bb_common_bufsiz1)
+#define out                (G.out               )
+#define out_cnt            (G.out_cnt           )
+#define print_header       (G.print_header      )
+#define need_flags         (G.need_flags        )
+#define buffer             (G.buffer            )
+#define terminal_width     (G.terminal_width    )
+#define kernel_HZ          (G.kernel_HZ         )
+#define seconds_since_boot (G.seconds_since_boot)
+#define default_o          (G.default_o         )
+
+#if ENABLE_FEATURE_PS_TIME
+/* for ELF executables, notes are pushed before environment and args */
+static ptrdiff_t find_elf_note(ptrdiff_t findme)
+{
+       ptrdiff_t *ep = (ptrdiff_t *) environ;
+
+       while (*ep++);
+       while (*ep) {
+               if (ep[0] == findme) {
+                       return ep[1];
+               }
+               ep += 2;
+       }
+       return -1;
+}
+
+#if ENABLE_FEATURE_PS_UNUSUAL_SYSTEMS
+static unsigned get_HZ_by_waiting(void)
+{
+       struct timeval tv1, tv2;
+       unsigned t1, t2, r, hz;
+       unsigned cnt = cnt; /* for compiler */
+       int diff;
+
+       r = 0;
+
+       /* Wait for times() to reach new tick */
+       t1 = times(NULL);
+       do {
+               t2 = times(NULL);
+       } while (t2 == t1);
+       gettimeofday(&tv2, NULL);
+
+       do {
+               t1 = t2;
+               tv1.tv_usec = tv2.tv_usec;
+
+               /* Wait exactly one times() tick */
+               do {
+                       t2 = times(NULL);
+               } while (t2 == t1);
+               gettimeofday(&tv2, NULL);
+
+               /* Calculate ticks per sec, rounding up to even */
+               diff = tv2.tv_usec - tv1.tv_usec;
+               if (diff <= 0) diff += 1000000;
+               hz = 1000000u / (unsigned)diff;
+               hz = (hz+1) & ~1;
+
+               /* Count how many same hz values we saw */
+               if (r != hz) {
+                       r = hz;
+                       cnt = 0;
+               }
+               cnt++;
+       } while (cnt < 3); /* exit if saw 3 same values */
+
+       return r;
+}
+#else
+static inline unsigned get_HZ_by_waiting(void)
+{
+       /* Better method? */
+       return 100;
+}
+#endif
+
+static unsigned get_kernel_HZ(void)
+{
+       //char buf[64];
+       struct sysinfo info;
+
+       if (kernel_HZ)
+               return kernel_HZ;
+
+       /* Works for ELF only, Linux 2.4.0+ */
+       kernel_HZ = find_elf_note(AT_CLKTCK);
+       if (kernel_HZ == (unsigned)-1)
+               kernel_HZ = get_HZ_by_waiting();
+
+       //if (open_read_close("/proc/uptime", buf, sizeof(buf) <= 0)
+       //      bb_perror_msg_and_die("cannot read %s", "/proc/uptime");
+       //buf[sizeof(buf)-1] = '\0';
+       ///sscanf(buf, "%llu", &seconds_since_boot);
+       sysinfo(&info);
+       seconds_since_boot = info.uptime;
+
+       return kernel_HZ;
+}
+#endif
+
+/* Print value to buf, max size+1 chars (including trailing '\0') */
+
+static void func_user(char *buf, int size, const procps_status_t *ps)
+{
+#if 1
+       safe_strncpy(buf, get_cached_username(ps->uid), size+1);
+#else
+       /* "compatible" version, but it's larger */
+       /* procps 2.18 shows numeric UID if name overflows the field */
+       /* TODO: get_cached_username() returns numeric string if
+        * user has no passwd record, we will display it
+        * left-justified here; too long usernames are shown
+        * as _right-justified_ IDs. Is it worth fixing? */
+       const char *user = get_cached_username(ps->uid);
+       if (strlen(user) <= size)
+               safe_strncpy(buf, user, size+1);
+       else
+               sprintf(buf, "%*u", size, (unsigned)ps->uid);
+#endif
+}
+
+static void func_comm(char *buf, int size, const procps_status_t *ps)
+{
+       safe_strncpy(buf, ps->comm, size+1);
+}
+
+static void func_args(char *buf, int size, const procps_status_t *ps)
+{
+       read_cmdline(buf, size, ps->pid, ps->comm);
+}
+
+static void func_pid(char *buf, int size, const procps_status_t *ps)
+{
+       sprintf(buf, "%*u", size, ps->pid);
+}
+
+static void func_ppid(char *buf, int size, const procps_status_t *ps)
+{
+       sprintf(buf, "%*u", size, ps->ppid);
+}
+
+static void func_pgid(char *buf, int size, const procps_status_t *ps)
+{
+       sprintf(buf, "%*u", size, ps->pgid);
+}
+
+static void put_lu(char *buf, int size, unsigned long u)
+{
+       char buf4[5];
+
+       /* see http://en.wikipedia.org/wiki/Tera */
+       smart_ulltoa4(u, buf4, " mgtpezy");
+       buf4[4] = '\0';
+       sprintf(buf, "%.*s", size, buf4);
+}
+
+static void func_vsz(char *buf, int size, const procps_status_t *ps)
+{
+       put_lu(buf, size, ps->vsz);
+}
+
+static void func_rss(char *buf, int size, const procps_status_t *ps)
+{
+       put_lu(buf, size, ps->rss);
+}
+
+static void func_tty(char *buf, int size, const procps_status_t *ps)
+{
+       buf[0] = '?';
+       buf[1] = '\0';
+       if (ps->tty_major) /* tty field of "0" means "no tty" */
+               snprintf(buf, size+1, "%u,%u", ps->tty_major, ps->tty_minor);
+}
+
+#if ENABLE_FEATURE_PS_TIME
+static void func_etime(char *buf, int size, const procps_status_t *ps)
+{
+       /* elapsed time [[dd-]hh:]mm:ss; here only mm:ss */
+       unsigned long mm;
+       unsigned ss;
+
+       mm = ps->start_time / get_kernel_HZ();
+       /* must be after get_kernel_HZ()! */
+       mm = seconds_since_boot - mm;
+       ss = mm % 60;
+       mm /= 60;
+       snprintf(buf, size+1, "%3lu:%02u", mm, ss);
+}
+
+static void func_time(char *buf, int size, const procps_status_t *ps)
+{
+       /* cumulative time [[dd-]hh:]mm:ss; here only mm:ss */
+       unsigned long mm;
+       unsigned ss;
+
+       mm = (ps->utime + ps->stime) / get_kernel_HZ();
+       ss = mm % 60;
+       mm /= 60;
+       snprintf(buf, size+1, "%3lu:%02u", mm, ss);
+}
+#endif
+
+#if ENABLE_SELINUX
+static void func_label(char *buf, int size, const procps_status_t *ps)
+{
+       safe_strncpy(buf, ps->context ? ps->context : "unknown", size+1);
+}
+#endif
+
+/*
+static void func_nice(char *buf, int size, const procps_status_t *ps)
+{
+       ps->???
+}
+
+static void func_pcpu(char *buf, int size, const procps_status_t *ps)
+{
+}
+*/
+
+static const ps_out_t out_spec[] = {
+// Mandated by POSIX:
+       { 8                  , "user"  ,"USER"   ,func_user  ,PSSCAN_UIDGID  },
+       { 16                 , "comm"  ,"COMMAND",func_comm  ,PSSCAN_COMM    },
+       { 256                , "args"  ,"COMMAND",func_args  ,PSSCAN_COMM    },
+       { 5                  , "pid"   ,"PID"    ,func_pid   ,PSSCAN_PID     },
+       { 5                  , "ppid"  ,"PPID"   ,func_ppid  ,PSSCAN_PPID    },
+       { 5                  , "pgid"  ,"PGID"   ,func_pgid  ,PSSCAN_PGID    },
+#if ENABLE_FEATURE_PS_TIME
+       { sizeof("ELAPSED")-1, "etime" ,"ELAPSED",func_etime ,PSSCAN_START_TIME },
+#endif
+//     { sizeof("GROUP"  )-1, "group" ,"GROUP"  ,func_group ,PSSCAN_UIDGID  },
+//     { sizeof("NI"     )-1, "nice"  ,"NI"     ,func_nice  ,PSSCAN_        },
+//     { sizeof("%CPU"   )-1, "pcpu"  ,"%CPU"   ,func_pcpu  ,PSSCAN_        },
+//     { sizeof("RGROUP" )-1, "rgroup","RGROUP" ,func_rgroup,PSSCAN_UIDGID  },
+//     { sizeof("RUSER"  )-1, "ruser" ,"RUSER"  ,func_ruser ,PSSCAN_UIDGID  },
+#if ENABLE_FEATURE_PS_TIME
+       { 6                  , "time"  ,"TIME"   ,func_time  ,PSSCAN_STIME | PSSCAN_UTIME },
+#endif
+       { 6                  , "tty"   ,"TT"     ,func_tty   ,PSSCAN_TTY     },
+       { 4                  , "vsz"   ,"VSZ"    ,func_vsz   ,PSSCAN_VSZ     },
+// Not mandated by POSIX, but useful:
+       { 4                  , "rss"   ,"RSS"    ,func_rss   ,PSSCAN_RSS     },
+#if ENABLE_SELINUX
+       { 35                 , "label" ,"LABEL"  ,func_label ,PSSCAN_CONTEXT },
+#endif
+};
+
+static ps_out_t* new_out_t(void)
+{
+       out = xrealloc_vector(out, 2, out_cnt);
+       return &out[out_cnt++];
+}
+
+static const ps_out_t* find_out_spec(const char *name)
+{
+       unsigned i;
+       for (i = 0; i < ARRAY_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;
+       }
+       // opt points to last spec in comma separated list.
+       // This one can have =HEADER part.
+       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++) {
+               need_flags |= out[i].ps_flags;
+               if (out[i].header[0]) {
+                       print_header = 1;
+               }
+               width += out[i].width + 1; /* "FIELD " */
+       }
+#if ENABLE_SELINUX
+       if (!is_selinux_enabled())
+               need_flags &= ~PSSCAN_CONTEXT;
+#endif
+       buffer = xmalloc(width + 1); /* for trailing \0 */
+}
+
+static void format_header(void)
+{
+       int i;
+       ps_out_t* op;
+       char *p;
+
+       if (!print_header)
+               return;
+       p = buffer;
+       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[0]) {
+                       p[0] = '-';
+                       p[1] = '\0';
+               }
+               len = strlen(p);
+               p += len;
+               len = out[i].width - len + 1;
+               if (++i == out_cnt) /* do not pad last field */
+                       break;
+               p += sprintf(p, "%*s", len, "");
+       }
+       printf("%.*s\n", terminal_width, buffer);
+}
+
+int ps_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int ps_main(int argc UNUSED_PARAM, char **argv)
+{
+       procps_status_t *p;
+       llist_t* opt_o = NULL;
+       USE_SELINUX(int opt;)
+
+       // 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 display
+       /* We allow (and ignore) most of the above. FIXME */
+       opt_complementary = "o::";
+       USE_SELINUX(opt =) getopt32(argv, "Zo:aAdefl", &opt_o);
+       if (opt_o) {
+               do {
+                       parse_o(llist_pop(&opt_o));
+               } while (opt_o);
+       } else {
+               /* Below: parse_o() needs char*, NOT const char*... */
+#if ENABLE_SELINUX
+               if (!(opt & 1) || !is_selinux_enabled()) {
+                       /* no -Z or no SELinux: do not show LABEL */
+                       strcpy(default_o, DEFAULT_O_STR + sizeof(SELINUX_O_PREFIX)-1);
+               } else
+#endif
+               {
+                       strcpy(default_o, DEFAULT_O_STR);
+               }
+               parse_o(default_o);
+       }
+       post_process();
+
+       /* Was INT_MAX, but some libc's go belly up with printf("%.*s")
+        * and such large widths */
+       terminal_width = MAX_WIDTH;
+       if (isatty(1)) {
+               get_terminal_width_height(0, &terminal_width, NULL);
+               if (--terminal_width > MAX_WIDTH)
+                       terminal_width = MAX_WIDTH;
+       }
+       format_header();
+
+       p = NULL;
+       while ((p = procps_scan(p, need_flags))) {
+               format_process(p);
+       }
+
+       return EXIT_SUCCESS;
+}
+
+
+#else /* !ENABLE_DESKTOP */
+
+
+int ps_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int ps_main(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
+{
+       procps_status_t *p = NULL;
+       int len;
+       SKIP_SELINUX(const) int use_selinux = 0;
+       USE_SELINUX(int i;)
+#if !ENABLE_FEATURE_PS_WIDE
+       enum { terminal_width = 79 };
+#else
+       unsigned 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(argv, USE_SELINUX("Z") "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 : MAX_WIDTH;
+       } else {
+               get_terminal_width_height(0, &terminal_width, NULL);
+               /* Go one less... */
+               if (--terminal_width > MAX_WIDTH)
+                       terminal_width = MAX_WIDTH;
+       }
+#else /* only ENABLE_SELINUX */
+       i = getopt32(argv, "Z");
+#endif
+#if ENABLE_SELINUX
+       if ((i & 1) && is_selinux_enabled())
+               use_selinux = PSSCAN_CONTEXT;
+#endif
+#endif /* ENABLE_FEATURE_PS_WIDE || ENABLE_SELINUX */
+
+       if (use_selinux)
+               puts("  PID CONTEXT                          STAT COMMAND");
+       else
+               puts("  PID USER       VSZ STAT COMMAND");
+
+       while ((p = procps_scan(p, 0
+                       | PSSCAN_PID
+                       | PSSCAN_UIDGID
+                       | PSSCAN_STATE
+                       | PSSCAN_VSZ
+                       | PSSCAN_COMM
+                       | use_selinux
+       ))) {
+#if ENABLE_SELINUX
+               if (use_selinux) {
+                       len = printf("%5u %-32.32s %s  ",
+                                       p->pid,
+                                       p->context ? p->context : "unknown",
+                                       p->state);
+               } else
+#endif
+               {
+                       const char *user = get_cached_username(p->uid);
+                       //if (p->vsz == 0)
+                       //      len = printf("%5u %-8.8s        %s ",
+                       //              p->pid, user, p->state);
+                       //else
+                       {
+                               char buf6[6];
+                               smart_ulltoa5(p->vsz, buf6, " mgtpezy");
+                               buf6[5] = '\0';
+                               len = printf("%5u %-8.8s %s %s  ",
+                                       p->pid, user, buf6, p->state);
+                       }
+               }
+
+               {
+                       int sz = terminal_width - len;
+                       char buf[sz + 1];
+                       read_cmdline(buf, sz, p->pid, p->comm);
+                       puts(buf);
+               }
+       }
+       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 (file)
index 0000000..57f4fa8
--- /dev/null
@@ -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  <blank>  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 <blank> 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  <space>-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 <blank> 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  <blank>  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 <blank> 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  <blank>
+       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, <blank> 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 <blank>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 (file)
index 0000000..ea5fc70
--- /dev/null
@@ -0,0 +1,128 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * renice implementation for busybox
+ *
+ * Copyright (C) 2005  Manuel Novoa III  <mjn3@codepoet.org>
+ *
+ * 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 "libbb.h"
+#include <sys/resource.h>
+
+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) MAIN_EXTERNALLY_VISIBLE;
+int renice_main(int argc UNUSED_PARAM, char **argv)
+{
+       static const char Xetpriority_msg[] ALIGN1 = "%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[] ALIGN1 = {
+                               '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 (file)
index 0000000..c9063bf
--- /dev/null
@@ -0,0 +1,258 @@
+/* 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 <preload> to preload values from a file
+ * v1.01.1 - busybox applet aware by <solar@gentoo.org>
+ */
+
+#include "libbb.h"
+
+enum {
+       FLAG_SHOW_KEYS       = 1 << 0,
+       FLAG_SHOW_KEY_ERRORS = 1 << 1,
+       FLAG_TABLE_FORMAT    = 1 << 2, /* not implemented */
+       FLAG_SHOW_ALL        = 1 << 3,
+       FLAG_PRELOAD_FILE    = 1 << 4,
+       FLAG_WRITE           = 1 << 5,
+};
+#define OPTION_STR "neAapw"
+
+static void sysctl_dots_to_slashes(char *name)
+{
+       char *cptr, *last_good, *end;
+
+       /* Convert minimum number of '.' to '/' so that
+        * we end up with existing file's name.
+        *
+        * Example from bug 3894:
+        * net.ipv4.conf.eth0.100.mc_forwarding ->
+        * net/ipv4/conf/eth0.100/mc_forwarding
+        * NB: net/ipv4/conf/eth0/mc_forwarding *also exists*,
+        * therefore we must start from the end, and if
+        * we replaced even one . -> /, start over again,
+        * but never replace dots before the position
+        * where last replacement occurred.
+        *
+        * Another bug we later had is that
+        * net.ipv4.conf.eth0.100
+        * (without .mc_forwarding) was mishandled.
+        *
+        * To set up testing: modprobe 8021q; vconfig add eth0 100
+        */
+       end = name + strlen(name);
+       last_good = name - 1;
+       *end = '.'; /* trick the loop into trying full name too */
+
+ again:
+       cptr = end;
+       while (cptr > last_good) {
+               if (*cptr == '.') {
+                       *cptr = '\0';
+                       //bb_error_msg("trying:'%s'", name);
+                       if (access(name, F_OK) == 0) {
+                               if (cptr != end) /* prevent trailing '/' */
+                                       *cptr = '/';
+                               //bb_error_msg("replaced:'%s'", name);
+                               last_good = cptr;
+                               goto again;
+                       }
+                       *cptr = '.';
+               }
+               cptr--;
+       }
+       *end = '\0';
+}
+
+static int sysctl_act_on_setting(char *setting)
+{
+       int fd, retval = EXIT_SUCCESS;
+       char *cptr, *outname;
+       char *value = value; /* for compiler */
+
+       outname = xstrdup(setting);
+
+       cptr = outname;
+       while (*cptr) {
+               if (*cptr == '/')
+                       *cptr = '.';
+               cptr++;
+       }
+
+       if (option_mask32 & FLAG_WRITE) {
+               cptr = strchr(setting, '=');
+               if (cptr == NULL) {
+                       bb_error_msg("error: '%s' must be of the form name=value",
+                               outname);
+                       retval = EXIT_FAILURE;
+                       goto end;
+               }
+               value = cptr + 1;       /* point to the value in name=value */
+               if (setting == cptr || !*value) {
+                       bb_error_msg("error: malformed setting '%s'", outname);
+                       retval = EXIT_FAILURE;
+                       goto end;
+               }
+               *cptr = '\0';
+               outname[cptr - setting] = '\0';
+               /* procps 3.2.7 actually uses these flags */
+               fd = open(setting, O_WRONLY|O_CREAT|O_TRUNC, 0666);
+       } else {
+               fd = open(setting, O_RDONLY);
+       }
+
+       if (fd < 0) {
+               switch (errno) {
+               case ENOENT:
+                       if (option_mask32 & FLAG_SHOW_KEY_ERRORS)
+                               bb_error_msg("error: '%s' is an unknown key", outname);
+                       break;
+               default:
+                       bb_perror_msg("error %sing key '%s'",
+                                       option_mask32 & FLAG_WRITE ?
+                                               "sett" : "read",
+                                       outname);
+                       break;
+               }
+               retval = EXIT_FAILURE;
+               goto end;
+       }
+
+       if (option_mask32 & FLAG_WRITE) {
+//TODO: procps 3.2.7 writes "value\n", note trailing "\n"
+               xwrite_str(fd, value);
+               close(fd);
+               if (option_mask32 & FLAG_SHOW_KEYS)
+                       printf("%s = ", outname);
+               puts(value);
+       } else {
+               char c;
+
+               value = cptr = xmalloc_read(fd, NULL);
+               close(fd);
+               if (value == NULL) {
+                       bb_perror_msg("error reading key '%s'", outname);
+                       goto end;
+               }
+
+               /* dev.cdrom.info and sunrpc.transports, for example,
+                * are multi-line. Try "sysctl sunrpc.transports"
+                */
+               while ((c = *cptr) != '\0') {
+                       if (option_mask32 & FLAG_SHOW_KEYS)
+                               printf("%s = ", outname);
+                       while (1) {
+                               fputc(c, stdout);
+                               cptr++;
+                               if (c == '\n')
+                                       break;
+                               c = *cptr;
+                               if (c == '\0')
+                                       break;
+                       }
+               }
+               free(value);
+       }
+ end:
+       free(outname);
+       return retval;
+}
+
+static int sysctl_act_recursive(const char *path)
+{
+       DIR *dirp;
+       struct stat buf;
+       struct dirent *entry;
+       char *next;
+       int retval = 0;
+
+       stat(path, &buf);
+       if (S_ISDIR(buf.st_mode) && !(option_mask32 & FLAG_WRITE)) {
+               dirp = opendir(path);
+               if (dirp == NULL)
+                       return -1;
+               while ((entry = readdir(dirp)) != NULL) {
+                       next = concat_subpath_file(path, entry->d_name);
+                       if (next == NULL)
+                               continue; /* d_name is "." or ".." */
+                       /* if path was ".", drop "./" prefix: */
+                       retval |= sysctl_act_recursive((next[0] == '.' && next[1] == '/') ?
+                                           next + 2 : next);
+                       free(next);
+               }
+               closedir(dirp);
+       } else {
+               char *name = xstrdup(path);
+               retval |= sysctl_act_on_setting(name);
+               free(name);
+       }
+
+       return retval;
+}
+
+/* Set sysctl's from a conf file. Format example:
+ * # Controls IP packet forwarding
+ * net.ipv4.ip_forward = 0
+ */
+static int sysctl_handle_preload_file(const char *filename)
+{
+       char *token[2];
+       parser_t *parser;
+
+       parser = config_open(filename);
+       /* Must do it _after_ config_open(): */
+       xchdir("/proc/sys");
+       /* xchroot(".") - if you are paranoid */
+
+//TODO: ';' is comment char too
+//TODO: comment may be only at line start. "var=1 #abc" - "1 #abc" is the value
+// (but _whitespace_ from ends should be trimmed first (and we do it right))
+//TODO: "var==1" is mishandled (must use "=1" as a value, but uses "1")
+       while (config_read(parser, token, 2, 2, "# \t=", PARSE_NORMAL)) {
+               char *tp;
+               sysctl_dots_to_slashes(token[0]);
+               tp = xasprintf("%s=%s", token[0], token[1]);
+               sysctl_act_recursive(tp);
+               free(tp);
+       }
+       if (ENABLE_FEATURE_CLEAN_UP)
+               config_close(parser);
+       return 0;
+}
+
+int sysctl_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int sysctl_main(int argc UNUSED_PARAM, char **argv)
+{
+       int retval;
+       int opt;
+
+       opt = getopt32(argv, "+" OPTION_STR); /* '+' - stop on first non-option */
+       argv += optind;
+       opt ^= (FLAG_SHOW_KEYS | FLAG_SHOW_KEY_ERRORS);
+       option_mask32 = opt;
+
+       if (opt & FLAG_PRELOAD_FILE) {
+               option_mask32 |= FLAG_WRITE;
+               /* xchdir("/proc/sys") is inside */
+               return sysctl_handle_preload_file(*argv ? *argv : "/etc/sysctl.conf");
+       }
+       xchdir("/proc/sys");
+       /* xchroot(".") - if you are paranoid */
+       if (opt & (FLAG_TABLE_FORMAT | FLAG_SHOW_ALL)) {
+               return sysctl_act_recursive(".");
+       }
+
+       retval = 0;
+       while (*argv) {
+               sysctl_dots_to_slashes(*argv);
+               retval |= sysctl_act_recursive(*argv);
+               argv++;
+       }
+
+       return retval;
+}
diff --git a/procps/top.c b/procps/top.c
new file mode 100644 (file)
index 0000000..b595142
--- /dev/null
@@ -0,0 +1,1120 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * A tiny 'top' utility.
+ *
+ * This is written specifically for the linux /proc/<PID>/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 <oak at welho dot com>
+ *
+ * Rewritten by Vladimir Oleynik (C) 2002 <dzo@simtreas.ru>
+ *
+ * Sept 2008: Vineet Gupta <vineet.gupta@arc.com>
+ * Added Support for reporting SMP Information
+ * - CPU where Process was last seen running
+ *   (to see effect of sched_setaffinity() etc)
+ * - CPU Time Split (idle/IO/wait etc) PER CPU
+ *
+ * Copyright (c) 1992 Branko Lankester
+ * Copyright (c) 1992 Roger Binns
+ * Copyright (C) 1994-1996 Charles L. Blake.
+ * Copyright (C) 1992-1998 Michael K. Johnson
+ *
+ * Licensed under GPLv2, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.h"
+
+
+typedef struct top_status_t {
+       unsigned long vsz;
+#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];
+#if ENABLE_FEATURE_TOP_SMP_PROCESS
+       int last_seen_on_cpu;
+#endif
+} top_status_t;
+
+typedef struct jiffy_counts_t {
+       /* Linux 2.4.x has only first four */
+       unsigned long long usr, nic, sys, idle;
+       unsigned long long iowait, irq, softirq, steal;
+       unsigned long long total;
+       unsigned long long busy;
+} jiffy_counts_t;
+
+/* This structure stores some critical information from one frame to
+   the next. Used for finding deltas. */
+typedef struct save_hist {
+       unsigned long ticks;
+       pid_t pid;
+} save_hist;
+
+typedef int (*cmp_funcp)(top_status_t *P, top_status_t *Q);
+
+
+enum { SORT_DEPTH = 3 };
+
+
+struct globals {
+       top_status_t *top;
+       int ntop;
+#if ENABLE_FEATURE_TOPMEM
+       smallint sort_field;
+       smallint inverted;
+#endif
+#if ENABLE_FEATURE_TOP_SMP_CPU
+       smallint smp_cpu_info; /* one/many cpu info lines? */
+#endif
+#if ENABLE_FEATURE_USE_TERMIOS
+       struct termios initial_settings;
+#endif
+#if !ENABLE_FEATURE_TOP_CPU_USAGE_PERCENTAGE
+       cmp_funcp sort_function[1];
+#else
+       cmp_funcp sort_function[SORT_DEPTH];
+       struct save_hist *prev_hist;
+       int prev_hist_count;
+       jiffy_counts_t cur_jif, prev_jif;
+       /* int hist_iterations; */
+       unsigned total_pcpu;
+       /* unsigned long total_vsz; */
+#endif
+#if ENABLE_FEATURE_TOP_SMP_CPU
+       /* Per CPU samples: current and last */
+       jiffy_counts_t *cpu_jif, *cpu_prev_jif;
+       int num_cpus;
+#endif
+       char line_buf[80];
+};
+
+enum { LINE_BUF_SIZE = COMMON_BUFSIZE - offsetof(struct globals, line_buf) };
+
+#define G (*(struct globals*)&bb_common_bufsiz1)
+#define INIT_G() do { \
+       struct G_sizecheck { \
+               char G_sizecheck[sizeof(G) > COMMON_BUFSIZE ? -1 : 1]; \
+       }; \
+} while (0)
+#define top              (G.top               )
+#define ntop             (G.ntop              )
+#define sort_field       (G.sort_field        )
+#define inverted         (G.inverted          )
+#define smp_cpu_info     (G.smp_cpu_info      )
+#define initial_settings (G.initial_settings  )
+#define sort_function    (G.sort_function     )
+#define prev_hist        (G.prev_hist         )
+#define prev_hist_count  (G.prev_hist_count   )
+#define cur_jif          (G.cur_jif           )
+#define prev_jif         (G.prev_jif          )
+#define cpu_jif          (G.cpu_jif           )
+#define cpu_prev_jif     (G.cpu_prev_jif      )
+#define num_cpus         (G.num_cpus          )
+#define total_pcpu       (G.total_pcpu        )
+#define line_buf         (G.line_buf          )
+
+enum {
+       OPT_d = (1 << 0),
+       OPT_n = (1 << 1),
+       OPT_b = (1 << 2),
+       OPT_EOF = (1 << 3), /* pseudo: "we saw EOF in stdin" */
+};
+#define OPT_BATCH_MODE (option_mask32 & OPT_b)
+
+
+#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->vsz < P->vsz) return -1;
+       return Q->vsz != P->vsz; /* 0 if ==, 1 if > */
+}
+
+
+#if ENABLE_FEATURE_TOP_CPU_USAGE_PERCENTAGE
+
+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;
+}
+
+static NOINLINE int read_cpu_jiffy(FILE *fp, jiffy_counts_t *p_jif)
+{
+#if !ENABLE_FEATURE_TOP_SMP_CPU
+       static const char fmt[] = "cpu %llu %llu %llu %llu %llu %llu %llu %llu";
+#else
+       static const char fmt[] = "cp%*s %llu %llu %llu %llu %llu %llu %llu %llu";
+#endif
+       int ret;
+
+       if (!fgets(line_buf, LINE_BUF_SIZE, fp) || line_buf[0] != 'c' /* not "cpu" */)
+               return 0;
+       ret = sscanf(line_buf, fmt,
+                       &p_jif->usr, &p_jif->nic, &p_jif->sys, &p_jif->idle,
+                       &p_jif->iowait, &p_jif->irq, &p_jif->softirq,
+                       &p_jif->steal);
+       if (ret >= 4) {
+               p_jif->total = p_jif->usr + p_jif->nic + p_jif->sys + p_jif->idle
+                       + p_jif->iowait + p_jif->irq + p_jif->softirq + p_jif->steal;
+               /* procps 2.x does not count iowait as busy time */
+               p_jif->busy = p_jif->total - p_jif->idle - p_jif->iowait;
+       }
+
+       return ret;
+}
+
+static void get_jiffy_counts(void)
+{
+       FILE* fp = xfopen_for_read("stat");
+
+       /* We need to parse cumulative counts even if SMP CPU display is on,
+        * they are used to calculate per process CPU% */
+       prev_jif = cur_jif;
+       if (read_cpu_jiffy(fp, &cur_jif) < 4)
+               bb_error_msg_and_die("can't read /proc/stat");
+
+#if !ENABLE_FEATURE_TOP_SMP_CPU
+       fclose(fp);
+       return;
+#else
+       if (!smp_cpu_info) {
+               fclose(fp);
+               return;
+       }
+
+       if (!num_cpus) {
+               /* First time here. How many CPUs?
+                * There will be at least 1 /proc/stat line with cpu%d
+                */
+               while (1) {
+                       cpu_jif = xrealloc_vector(cpu_jif, 1, num_cpus);
+                       if (read_cpu_jiffy(fp, &cpu_jif[num_cpus]) <= 4)
+                               break;
+                       num_cpus++;
+               }
+               if (num_cpus == 0) /* /proc/stat with only "cpu ..." line?! */
+                       smp_cpu_info = 0;
+
+               cpu_prev_jif = xzalloc(sizeof(cpu_prev_jif[0]) * num_cpus);
+
+               /* Otherwise the first per cpu display shows all 100% idles */
+               usleep(50000);
+       } else { /* Non first time invocation */
+               jiffy_counts_t *tmp;
+               int i;
+
+               /* First switch the sample pointers: no need to copy */
+               tmp = cpu_prev_jif;
+               cpu_prev_jif = cpu_jif;
+               cpu_jif = tmp;
+
+               /* Get the new samples */
+               for (i = 0; i < num_cpus; i++)
+                       read_cpu_jiffy(fp, &cpu_jif[i]);
+       }
+#endif
+       fclose(fp);
+}
+
+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_vsz = 0; */
+       new_hist = xmalloc(sizeof(new_hist[0]) * 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_vsz += cur->vsz; */
+       }
+
+       /*
+        * Save cur frame's information.
+        */
+       free(prev_hist);
+       prev_hist = new_hist;
+       prev_hist_count = ntop;
+}
+
+#endif /* FEATURE_TOP_CPU_USAGE_PERCENTAGE */
+
+#if ENABLE_FEATURE_TOP_CPU_GLOBAL_PERCENTS && ENABLE_FEATURE_TOP_DECIMALS
+/* formats 7 char string (8 with terminating NUL) */
+static char *fmt_100percent_8(char pbuf[8], unsigned value, unsigned total)
+{
+       unsigned t;
+       if (value >= total) { /* 100% ? */
+               strcpy(pbuf, "  100% ");
+               return pbuf;
+       }
+       /* else generate " [N/space]N.N% " string */
+       value = 1000 * value / total;
+       t = value / 100;
+       value = value % 100;
+       pbuf[0] = ' ';
+       pbuf[1] = t ? t + '0' : ' ';
+       pbuf[2] = '0' + (value / 10);
+       pbuf[3] = '.';
+       pbuf[4] = '0' + (value % 10);
+       pbuf[5] = '%';
+       pbuf[6] = ' ';
+       pbuf[7] = '\0';
+       return pbuf;
+}
+#endif
+
+#if ENABLE_FEATURE_TOP_CPU_GLOBAL_PERCENTS
+static void display_cpus(int scr_width, char *scrbuf, int *lines_rem_p)
+{
+       /*
+        * xxx% = (cur_jif.xxx - prev_jif.xxx) / (cur_jif.total - prev_jif.total) * 100%
+        */
+       unsigned total_diff;
+       jiffy_counts_t *p_jif, *p_prev_jif;
+       int i;
+
+#if ENABLE_FEATURE_TOP_SMP_CPU
+       int n_cpu_lines;
+#endif
+
+       /* using (unsigned) casts to make operations cheaper */
+#define  CALC_TOT_DIFF  ((unsigned)(p_jif->total - p_prev_jif->total) ? : 1)
+
+#if ENABLE_FEATURE_TOP_DECIMALS
+#define CALC_STAT(xxx) char xxx[8]
+#define SHOW_STAT(xxx) fmt_100percent_8(xxx, (unsigned)(p_jif->xxx - p_prev_jif->xxx), total_diff)
+#define FMT "%s"
+#else
+#define CALC_STAT(xxx) unsigned xxx = 100 * (unsigned)(p_jif->xxx - p_prev_jif->xxx) / total_diff
+#define SHOW_STAT(xxx) xxx
+#define FMT "%4u%% "
+#endif
+
+#if !ENABLE_FEATURE_TOP_SMP_CPU
+       {
+               i = 1;
+               p_jif = &cur_jif;
+               p_prev_jif = &prev_jif;
+#else
+       /* Loop thru CPU(s) */
+       n_cpu_lines = smp_cpu_info ? num_cpus : 1;
+       if (n_cpu_lines > *lines_rem_p)
+               n_cpu_lines = *lines_rem_p;
+
+       for (i = 0; i < n_cpu_lines; i++) {
+               p_jif = &cpu_jif[i];
+               p_prev_jif = &cpu_prev_jif[i];
+#endif
+               total_diff = CALC_TOT_DIFF;
+
+               { /* Need a block: CALC_STAT are declarations */
+                       CALC_STAT(usr);
+                       CALC_STAT(sys);
+                       CALC_STAT(nic);
+                       CALC_STAT(idle);
+                       CALC_STAT(iowait);
+                       CALC_STAT(irq);
+                       CALC_STAT(softirq);
+                       /*CALC_STAT(steal);*/
+
+                       snprintf(scrbuf, scr_width,
+                               /* Barely fits in 79 chars when in "decimals" mode. */
+#if ENABLE_FEATURE_TOP_SMP_CPU
+                               "CPU%s:"FMT"usr"FMT"sys"FMT"nic"FMT"idle"FMT"io"FMT"irq"FMT"sirq",
+                               (smp_cpu_info ? utoa(i) : ""),
+#else
+                               "CPU:"FMT"usr"FMT"sys"FMT"nic"FMT"idle"FMT"io"FMT"irq"FMT"sirq",
+#endif
+                               SHOW_STAT(usr), SHOW_STAT(sys), SHOW_STAT(nic), SHOW_STAT(idle),
+                               SHOW_STAT(iowait), SHOW_STAT(irq), SHOW_STAT(softirq)
+                               /*, SHOW_STAT(steal) - what is this 'steal' thing? */
+                               /* I doubt anyone wants to know it */
+                       );
+                       puts(scrbuf);
+               }
+       }
+#undef SHOW_STAT
+#undef CALC_STAT
+#undef FMT
+       *lines_rem_p -= i;
+}
+#else  /* !ENABLE_FEATURE_TOP_CPU_GLOBAL_PERCENTS */
+#define display_cpus(scr_width, scrbuf, lines_rem) ((void)0)
+#endif
+
+static unsigned long display_header(int scr_width, int *lines_rem_p)
+{
+       FILE *fp;
+       char buf[80];
+       char scrbuf[80];
+       unsigned long total, used, mfree, shared, buffers, cached;
+
+       /* read memory info */
+       fp = xfopen_for_read("meminfo");
+
+       /*
+        * 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);
+               /* convert to kilobytes */
+               used /= 1024;
+               mfree /= 1024;
+               shared /= 1024;
+               buffers /= 1024;
+               cached /= 1024;
+               total /= 1024;
+       } else {
+               /*
+                * Revert to manual parsing, which incidentally already has the
+                * sizes in kilobytes. This should be safe for both 2.4 and
+                * 2.6.
+                */
+               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);
+
+       /* output memory info */
+       if (scr_width > (int)sizeof(scrbuf))
+               scr_width = sizeof(scrbuf);
+       snprintf(scrbuf, scr_width,
+               "Mem: %luK used, %luK free, %luK shrd, %luK buff, %luK cached",
+               used, mfree, shared, buffers, cached);
+       /* clear screen & go to top */
+       printf(OPT_BATCH_MODE ? "%s\n" : "\e[H\e[J%s\n", scrbuf);
+       (*lines_rem_p)--;
+
+       /* Display CPU time split as percentage of total time
+        * This displays either a cumulative line or one line per CPU
+        */
+       display_cpus(scr_width, scrbuf, lines_rem_p);
+
+       /* read load average as a string */
+       buf[0] = '\0';
+       open_read_close("loadavg", buf, sizeof(buf) - 1);
+       buf[sizeof(buf) - 1] = '\n';
+       *strchr(buf, '\n') = '\0';
+       snprintf(scrbuf, scr_width, "Load average: %s", buf);
+       puts(scrbuf);
+       (*lines_rem_p)--;
+
+       return total;
+}
+
+static NOINLINE void display_process_list(int lines_rem, int scr_width)
+{
+       enum {
+               BITS_PER_INT = sizeof(int) * 8
+       };
+
+       top_status_t *s;
+       char vsz_str_buf[8];
+       unsigned long total_memory = display_header(scr_width, &lines_rem); /* or use total_vsz? */
+       /* xxx_shift and xxx_scale variables allow us to replace
+        * expensive divides with multiply and shift */
+       unsigned pmem_shift, pmem_scale, pmem_half;
+#if ENABLE_FEATURE_TOP_CPU_USAGE_PERCENTAGE
+       unsigned pcpu_shift, pcpu_scale, pcpu_half;
+       unsigned busy_jifs;
+#endif
+
+       /* what info of the processes is shown */
+       printf(OPT_BATCH_MODE ? "%.*s" : "\e[7m%.*s\e[0m", scr_width,
+               "  PID  PPID USER     STAT   VSZ %MEM"
+#if ENABLE_FEATURE_TOP_SMP_PROCESS
+               " CPU"
+#endif
+#if ENABLE_FEATURE_TOP_CPU_USAGE_PERCENTAGE
+               " %CPU"
+#endif
+               " COMMAND");
+       lines_rem--;
+
+#if ENABLE_FEATURE_TOP_DECIMALS
+#define UPSCALE 1000
+#define CALC_STAT(name, val) div_t name = div((val), 10)
+#define SHOW_STAT(name) name.quot, '0'+name.rem
+#define FMT "%3u.%c"
+#else
+#define UPSCALE 100
+#define CALC_STAT(name, val) unsigned name = (val)
+#define SHOW_STAT(name) name
+#define FMT "%4u%%"
+#endif
+       /*
+        * MEM% = s->vsz/MemTotal
+        */
+       pmem_shift = BITS_PER_INT-11;
+       pmem_scale = UPSCALE*(1U<<(BITS_PER_INT-11)) / total_memory;
+       /* s->vsz is in kb. we want (s->vsz * pmem_scale) to never overflow */
+       while (pmem_scale >= 512) {
+               pmem_scale /= 4;
+               pmem_shift -= 2;
+       }
+       pmem_half = (1U << pmem_shift) / (ENABLE_FEATURE_TOP_DECIMALS? 20 : 2);
+#if ENABLE_FEATURE_TOP_CPU_USAGE_PERCENTAGE
+       busy_jifs = cur_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)
+        */
+       /* (cur_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 = (UPSCALE*64 * (uint16_t)busy_jifs ? : 1);
+       while (pcpu_scale < (1U << (BITS_PER_INT-2))) {
+               pcpu_scale *= 4;
+               pcpu_shift += 2;
+       }
+       pcpu_scale /= ( (uint16_t)(cur_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;
+       }
+       pcpu_half = (1U << pcpu_shift) / (ENABLE_FEATURE_TOP_DECIMALS? 20 : 2);
+       /* printf(" pmem_scale=%u pcpu_scale=%u ", pmem_scale, pcpu_scale); */
+#endif
+
+       /* Ok, all preliminary data is ready, go through the list */
+       scr_width += 2; /* account for leading '\n' and trailing NUL */
+       if (lines_rem > ntop)
+               lines_rem = ntop;
+       s = top;
+       while (--lines_rem >= 0) {
+               unsigned col;
+               CALC_STAT(pmem, (s->vsz*pmem_scale + pmem_half) >> pmem_shift);
+#if ENABLE_FEATURE_TOP_CPU_USAGE_PERCENTAGE
+               CALC_STAT(pcpu, (s->pcpu*pcpu_scale + pcpu_half) >> pcpu_shift);
+#endif
+
+               if (s->vsz >= 100000)
+                       sprintf(vsz_str_buf, "%6ldm", s->vsz/1024);
+               else
+                       sprintf(vsz_str_buf, "%7ld", s->vsz);
+               /* PID PPID USER STAT VSZ %MEM [%CPU] COMMAND */
+               col = snprintf(line_buf, scr_width,
+                               "\n" "%5u%6u %-8.8s %s%s" FMT
+#if ENABLE_FEATURE_TOP_SMP_PROCESS
+                               " %3d"
+#endif
+#if ENABLE_FEATURE_TOP_CPU_USAGE_PERCENTAGE
+                               FMT
+#endif
+                               " ",
+                               s->pid, s->ppid, get_cached_username(s->uid),
+                               s->state, vsz_str_buf,
+                               SHOW_STAT(pmem)
+#if ENABLE_FEATURE_TOP_SMP_PROCESS
+                               , s->last_seen_on_cpu
+#endif
+#if ENABLE_FEATURE_TOP_CPU_USAGE_PERCENTAGE
+                               , SHOW_STAT(pcpu)
+#endif
+               );
+               if ((int)(col + 1) < scr_width)
+                       read_cmdline(line_buf + col, scr_width - col - 1, s->pid, s->comm);
+               fputs(line_buf, stdout);
+               /* printf(" %d/%d %lld/%lld", s->pcpu, total_pcpu,
+                       cur_jif.busy - prev_jif.busy, cur_jif.total - prev_jif.total); */
+               s++;
+       }
+       /* printf(" %d", hist_iterations); */
+       bb_putchar(OPT_BATCH_MODE ? '\n' : '\r');
+       fflush(stdout);
+}
+#undef UPSCALE
+#undef SHOW_STAT
+#undef CALC_STAT
+#undef FMT
+
+static void clearmems(void)
+{
+       clear_username_cache();
+       free(top);
+       top = NULL;
+       ntop = 0;
+}
+
+#if ENABLE_FEATURE_USE_TERMIOS
+
+static void reset_term(void)
+{
+       tcsetattr_stdin_TCSANOW(&initial_settings);
+       if (ENABLE_FEATURE_CLEAN_UP) {
+               clearmems();
+#if ENABLE_FEATURE_TOP_CPU_USAGE_PERCENTAGE
+               free(prev_hist);
+#endif
+       }
+}
+
+static void sig_catcher(int sig UNUSED_PARAM)
+{
+       reset_term();
+       exit(EXIT_FAILURE);
+}
+#endif /* FEATURE_USE_TERMIOS */
+
+/*
+ * TOPMEM support
+ */
+
+typedef unsigned long mem_t;
+
+typedef struct topmem_status_t {
+       unsigned pid;
+       char comm[COMM_LEN];
+       /* vsz doesn't count /dev/xxx mappings except /dev/zero */
+       mem_t vsz     ;
+       mem_t vszrw   ;
+       mem_t rss     ;
+       mem_t rss_sh  ;
+       mem_t dirty   ;
+       mem_t dirty_sh;
+       mem_t stack   ;
+} topmem_status_t;
+
+enum { NUM_SORT_FIELD = 7 };
+
+#define topmem ((topmem_status_t*)top)
+
+#if ENABLE_FEATURE_TOPMEM
+
+static int topmem_sort(char *a, char *b)
+{
+       int n;
+       mem_t l, r;
+
+       n = offsetof(topmem_status_t, vsz) + (sort_field * sizeof(mem_t));
+       l = *(mem_t*)(a + n);
+       r = *(mem_t*)(b + n);
+//     if (l == r) {
+//             l = a->mapped_rw;
+//             r = b->mapped_rw;
+//     }
+       /* We want to avoid unsigned->signed and truncation errors */
+       /* l>r: -1, l=r: 0, l<r: 1 */
+       n = (l > r) ? -1 : (l != r);
+       return inverted ? -n : n;
+}
+
+/* Cut "NNNN " out of "    NNNN kb" */
+static char *grab_number(char *str, const char *match, unsigned sz)
+{
+       if (strncmp(str, match, sz) == 0) {
+               str = skip_whitespace(str + sz);
+               (skip_non_whitespace(str))[1] = '\0';
+               return xstrdup(str);
+       }
+       return NULL;
+}
+
+/* display header info (meminfo / loadavg) */
+static void display_topmem_header(int scr_width, int *lines_rem_p)
+{
+       char linebuf[128];
+       unsigned i;
+       FILE *fp;
+       union {
+               struct {
+                       /*  1 */ char *total;
+                       /*  2 */ char *mfree;
+                       /*  3 */ char *buf;
+                       /*  4 */ char *cache;
+                       /*  5 */ char *swaptotal;
+                       /*  6 */ char *swapfree;
+                       /*  7 */ char *dirty;
+                       /*  8 */ char *mwrite;
+                       /*  9 */ char *anon;
+                       /* 10 */ char *map;
+                       /* 11 */ char *slab;
+               } u;
+               char *str[11];
+       } Z;
+#define total     Z.u.total
+#define mfree     Z.u.mfree
+#define buf       Z.u.buf
+#define cache     Z.u.cache
+#define swaptotal Z.u.swaptotal
+#define swapfree  Z.u.swapfree
+#define dirty     Z.u.dirty
+#define mwrite    Z.u.mwrite
+#define anon      Z.u.anon
+#define map       Z.u.map
+#define slab      Z.u.slab
+#define str       Z.str
+
+       memset(&Z, 0, sizeof(Z));
+
+       /* read memory info */
+       fp = xfopen_for_read("meminfo");
+       while (fgets(linebuf, sizeof(linebuf), fp)) {
+               char *p;
+
+#define SCAN(match, name) \
+               p = grab_number(linebuf, match, sizeof(match)-1); \
+               if (p) { name = p; continue; }
+
+               SCAN("MemTotal:", total);
+               SCAN("MemFree:", mfree);
+               SCAN("Buffers:", buf);
+               SCAN("Cached:", cache);
+               SCAN("SwapTotal:", swaptotal);
+               SCAN("SwapFree:", swapfree);
+               SCAN("Dirty:", dirty);
+               SCAN("Writeback:", mwrite);
+               SCAN("AnonPages:", anon);
+               SCAN("Mapped:", map);
+               SCAN("Slab:", slab);
+#undef SCAN
+       }
+       fclose(fp);
+
+#define S(s) (s ? s : "0 ")
+       snprintf(linebuf, sizeof(linebuf),
+               "Mem %stotal %sanon %smap %sfree",
+               S(total), S(anon), S(map), S(mfree));
+       printf(OPT_BATCH_MODE ? "%.*s\n" : "\e[H\e[J%.*s\n", scr_width, linebuf);
+
+       snprintf(linebuf, sizeof(linebuf),
+               " %sslab %sbuf %scache %sdirty %swrite",
+               S(slab), S(buf), S(cache), S(dirty), S(mwrite));
+       printf("%.*s\n", scr_width, linebuf);
+
+       snprintf(linebuf, sizeof(linebuf),
+               "Swap %stotal %sfree", // TODO: % used?
+               S(swaptotal), S(swapfree));
+       printf("%.*s\n", scr_width, linebuf);
+
+       (*lines_rem_p) -= 3;
+#undef S
+
+       for (i = 0; i < ARRAY_SIZE(str); i++)
+               free(str[i]);
+#undef total
+#undef free
+#undef buf
+#undef cache
+#undef swaptotal
+#undef swapfree
+#undef dirty
+#undef write
+#undef anon
+#undef map
+#undef slab
+#undef str
+}
+
+static void ulltoa6_and_space(unsigned long long ul, char buf[6])
+{
+       /* see http://en.wikipedia.org/wiki/Tera */
+       smart_ulltoa5(ul, buf, " mgtpezy");
+       buf[5] = ' ';
+}
+
+static NOINLINE void display_topmem_process_list(int lines_rem, int scr_width)
+{
+#define HDR_STR "  PID   VSZ VSZRW   RSS (SHR) DIRTY (SHR) STACK"
+#define MIN_WIDTH sizeof(HDR_STR)
+       const topmem_status_t *s = topmem;
+
+       display_topmem_header(scr_width, &lines_rem);
+       strcpy(line_buf, HDR_STR " COMMAND");
+       line_buf[5 + sort_field * 6] = '*';
+       printf(OPT_BATCH_MODE ? "%.*s" : "\e[7m%.*s\e[0m", scr_width, line_buf);
+       lines_rem--;
+
+       if (lines_rem > ntop)
+               lines_rem = ntop;
+       while (--lines_rem >= 0) {
+               /* PID VSZ VSZRW RSS (SHR) DIRTY (SHR) COMMAND */
+               ulltoa6_and_space(s->pid     , &line_buf[0*6]);
+               ulltoa6_and_space(s->vsz     , &line_buf[1*6]);
+               ulltoa6_and_space(s->vszrw   , &line_buf[2*6]);
+               ulltoa6_and_space(s->rss     , &line_buf[3*6]);
+               ulltoa6_and_space(s->rss_sh  , &line_buf[4*6]);
+               ulltoa6_and_space(s->dirty   , &line_buf[5*6]);
+               ulltoa6_and_space(s->dirty_sh, &line_buf[6*6]);
+               ulltoa6_and_space(s->stack   , &line_buf[7*6]);
+               line_buf[8*6] = '\0';
+               if (scr_width > (int)MIN_WIDTH) {
+                       read_cmdline(&line_buf[8*6], scr_width - MIN_WIDTH, s->pid, s->comm);
+               }
+               printf("\n""%.*s", scr_width, line_buf);
+               s++;
+       }
+       bb_putchar(OPT_BATCH_MODE ? '\n' : '\r');
+       fflush(stdout);
+#undef HDR_STR
+#undef MIN_WIDTH
+}
+
+#else
+void display_topmem_process_list(int lines_rem, int scr_width);
+int topmem_sort(char *a, char *b);
+#endif /* TOPMEM */
+
+/*
+ * end TOPMEM support
+ */
+
+enum {
+       TOP_MASK = 0
+               | PSSCAN_PID
+               | PSSCAN_PPID
+               | PSSCAN_VSZ
+               | PSSCAN_STIME
+               | PSSCAN_UTIME
+               | PSSCAN_STATE
+               | PSSCAN_COMM
+#if ENABLE_FEATURE_TOP_SMP_PROCESS
+               | PSSCAN_CPU
+#endif
+               | PSSCAN_UIDGID,
+       TOPMEM_MASK = 0
+               | PSSCAN_PID
+               | PSSCAN_SMAPS
+               | PSSCAN_COMM,
+};
+
+int top_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int top_main(int argc UNUSED_PARAM, char **argv)
+{
+       int iterations;
+       unsigned lines, col;
+       int lines_rem;
+       unsigned interval;
+       char *str_interval, *str_iterations;
+       SKIP_FEATURE_TOPMEM(const) unsigned scan_mask = TOP_MASK;
+#if ENABLE_FEATURE_USE_TERMIOS
+       struct termios new_settings;
+       struct pollfd pfd[1];
+       unsigned char c;
+
+       pfd[0].fd = 0;
+       pfd[0].events = POLLIN;
+#endif /* FEATURE_USE_TERMIOS */
+
+       INIT_G();
+
+       interval = 5; /* default update interval is 5 seconds */
+       iterations = 0; /* infinite */
+#if ENABLE_FEATURE_TOP_SMP_CPU
+       /*num_cpus = 0;*/
+       /*smp_cpu_info = 0;*/  /* to start with show aggregate */
+       cpu_jif = &cur_jif;
+       cpu_prev_jif = &prev_jif;
+#endif
+
+       /* all args are options; -n NUM */
+       opt_complementary = "-";
+       col = getopt32(argv, "d:n:b", &str_interval, &str_iterations);
+       if (col & OPT_d) {
+               /* work around for "-d 1" -> "-d -1" done by getopt32 */
+               if (str_interval[0] == '-')
+                       str_interval++;
+               /* Need to limit it to not overflow poll timeout */
+               interval = xatou16(str_interval);
+       }
+       if (col & OPT_n) {
+               if (str_iterations[0] == '-')
+                       str_iterations++;
+               iterations = xatou(str_iterations);
+       }
+
+       /* change to /proc */
+       xchdir("/proc");
+#if ENABLE_FEATURE_USE_TERMIOS
+       tcgetattr(0, (void *) &initial_settings);
+       memcpy(&new_settings, &initial_settings, sizeof(new_settings));
+       /* unbuffered input, turn off echo */
+       new_settings.c_lflag &= ~(ISIG | ICANON | ECHO | ECHONL);
+
+       bb_signals(BB_FATAL_SIGS, sig_catcher);
+       tcsetattr_stdin_TCSANOW(&new_settings);
+#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[0] = mem_sort;
+#endif /* FEATURE_TOP_CPU_USAGE_PERCENTAGE */
+
+       while (1) {
+               procps_status_t *p = NULL;
+
+               lines = 24; /* default */
+               col = 79;
+#if ENABLE_FEATURE_USE_TERMIOS
+               /* We output to stdout, we need size of stdout (not stdin)! */
+               get_terminal_width_height(STDOUT_FILENO, &col, &lines);
+               if (lines < 5 || col < 10) {
+                       sleep(interval);
+                       continue;
+               }
+#endif /* FEATURE_USE_TERMIOS */
+               if (col > LINE_BUF_SIZE-2) /* +2 bytes for '\n', NUL, */
+                       col = LINE_BUF_SIZE-2;
+
+               /* read process IDs & status for all the processes */
+               while ((p = procps_scan(p, scan_mask)) != NULL) {
+                       int n;
+                       if (scan_mask == TOP_MASK) {
+                               n = ntop;
+                               top = xrealloc_vector(top, 6, ntop++);
+                               top[n].pid = p->pid;
+                               top[n].ppid = p->ppid;
+                               top[n].vsz = p->vsz;
+#if ENABLE_FEATURE_TOP_CPU_USAGE_PERCENTAGE
+                               top[n].ticks = p->stime + p->utime;
+#endif
+                               top[n].uid = p->uid;
+                               strcpy(top[n].state, p->state);
+                               strcpy(top[n].comm, p->comm);
+#if ENABLE_FEATURE_TOP_SMP_PROCESS
+                               top[n].last_seen_on_cpu = p->last_seen_on_cpu;
+#endif
+                       } else { /* TOPMEM */
+#if ENABLE_FEATURE_TOPMEM
+                               if (!(p->mapped_ro | p->mapped_rw))
+                                       continue; /* kernel threads are ignored */
+                               n = ntop;
+                               /* No bug here - top and topmem are the same */
+                               top = xrealloc_vector(topmem, 6, ntop++);
+                               strcpy(topmem[n].comm, p->comm);
+                               topmem[n].pid      = p->pid;
+                               topmem[n].vsz      = p->mapped_rw + p->mapped_ro;
+                               topmem[n].vszrw    = p->mapped_rw;
+                               topmem[n].rss_sh   = p->shared_clean + p->shared_dirty;
+                               topmem[n].rss      = p->private_clean + p->private_dirty + topmem[n].rss_sh;
+                               topmem[n].dirty    = p->private_dirty + p->shared_dirty;
+                               topmem[n].dirty_sh = p->shared_dirty;
+                               topmem[n].stack    = p->stack;
+#endif
+                       }
+               } /* end of "while we read /proc" */
+               if (ntop == 0) {
+                       bb_error_msg("no process info in /proc");
+                       break;
+               }
+
+               if (scan_mask == TOP_MASK) {
+#if ENABLE_FEATURE_TOP_CPU_USAGE_PERCENTAGE
+                       if (!prev_hist_count) {
+                               do_stats();
+                               usleep(100000);
+                               clearmems();
+                               continue;
+                       }
+                       do_stats();
+                       /* TODO: we don't need to sort all 10000 processes, we need to find top 24! */
+                       qsort(top, ntop, sizeof(top_status_t), (void*)mult_lvl_cmp);
+#else
+                       qsort(top, ntop, sizeof(top_status_t), (void*)(sort_function[0]));
+#endif /* FEATURE_TOP_CPU_USAGE_PERCENTAGE */
+               }
+#if ENABLE_FEATURE_TOPMEM
+               else { /* TOPMEM */
+                       qsort(topmem, ntop, sizeof(topmem_status_t), (void*)topmem_sort);
+               }
+#endif
+               lines_rem = lines;
+               if (OPT_BATCH_MODE) {
+                       lines_rem = INT_MAX;
+               }
+               if (scan_mask == TOP_MASK)
+                       display_process_list(lines_rem, col);
+#if ENABLE_FEATURE_TOPMEM
+               else
+                       display_topmem_process_list(lines_rem, col);
+#endif
+               clearmems();
+               if (iterations >= 0 && !--iterations)
+                       break;
+#if !ENABLE_FEATURE_USE_TERMIOS
+               sleep(interval);
+#else
+               if (option_mask32 & (OPT_b|OPT_EOF))
+                        /* batch mode, or EOF on stdin ("top </dev/null") */
+                       sleep(interval);
+               else if (safe_poll(pfd, 1, interval * 1000) > 0) {
+                       if (safe_read(STDIN_FILENO, &c, 1) != 1) { /* error/EOF? */
+                               option_mask32 |= OPT_EOF;
+                               continue;
+                       }
+                       if (c == initial_settings.c_cc[VINTR])
+                               break;
+                       c |= 0x20; /* lowercase */
+                       if (c == 'q')
+                               break;
+                       if (c == 'n') {
+                               USE_FEATURE_TOPMEM(scan_mask = TOP_MASK;)
+                               sort_function[0] = pid_sort;
+                       }
+                       if (c == 'm') {
+                               USE_FEATURE_TOPMEM(scan_mask = TOP_MASK;)
+                               sort_function[0] = mem_sort;
+#if ENABLE_FEATURE_TOP_CPU_USAGE_PERCENTAGE
+                               sort_function[1] = pcpu_sort;
+                               sort_function[2] = time_sort;
+#endif
+                       }
+#if ENABLE_FEATURE_TOP_CPU_USAGE_PERCENTAGE
+                       if (c == 'p') {
+                               USE_FEATURE_TOPMEM(scan_mask = TOP_MASK;)
+                               sort_function[0] = pcpu_sort;
+                               sort_function[1] = mem_sort;
+                               sort_function[2] = time_sort;
+                       }
+                       if (c == 't') {
+                               USE_FEATURE_TOPMEM(scan_mask = TOP_MASK;)
+                               sort_function[0] = time_sort;
+                               sort_function[1] = mem_sort;
+                               sort_function[2] = pcpu_sort;
+                       }
+#if ENABLE_FEATURE_TOPMEM
+                       if (c == 's') {
+                               scan_mask = TOPMEM_MASK;
+                               free(prev_hist);
+                               prev_hist = NULL;
+                               prev_hist_count = 0;
+                               sort_field = (sort_field + 1) % NUM_SORT_FIELD;
+                       }
+                       if (c == 'r')
+                               inverted ^= 1;
+#endif
+#if ENABLE_FEATURE_TOP_SMP_CPU
+                       /* procps-2.0.18 uses 'C', 3.2.7 uses '1' */
+                       if (c == 'c' || c == '1') {
+                               /* User wants to toggle per cpu <> aggregate */
+                               if (smp_cpu_info) {
+                                       free(cpu_prev_jif);
+                                       free(cpu_jif);
+                                       cpu_jif = &cur_jif;
+                                       cpu_prev_jif = &prev_jif;
+                               } else {
+                                       /* Prepare for xrealloc() */
+                                       cpu_jif = cpu_prev_jif = NULL;
+                               }
+                               num_cpus = 0;
+                               smp_cpu_info = !smp_cpu_info;
+                               get_jiffy_counts();
+                       }
+#endif
+#endif
+               }
+#endif /* FEATURE_USE_TERMIOS */
+       } /* end of "while (1)" */
+
+       bb_putchar('\n');
+#if ENABLE_FEATURE_USE_TERMIOS
+       reset_term();
+#endif
+       return EXIT_SUCCESS;
+}
diff --git a/procps/uptime.c b/procps/uptime.c
new file mode 100644 (file)
index 0000000..d9aa1d0
--- /dev/null
@@ -0,0 +1,60 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Mini uptime 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.
+ */
+
+/* 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 "libbb.h"
+
+#ifndef FSHIFT
+# define FSHIFT 16              /* nr of bits of precision */
+#endif
+#define FIXED_1         (1<<FSHIFT)     /* 1.0 as fixed-point */
+#define LOAD_INT(x) ((x) >> FSHIFT)
+#define LOAD_FRAC(x) LOAD_INT(((x) & (FIXED_1-1)) * 100)
+
+
+int uptime_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int uptime_main(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
+{
+       int updays, uphours, upminutes;
+       struct sysinfo info;
+       struct tm *current_time;
+       time_t current_secs;
+
+       time(&current_secs);
+       current_time = localtime(&current_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/procps/watch.c b/procps/watch.c
new file mode 100644 (file)
index 0000000..5fd0510
--- /dev/null
@@ -0,0 +1,75 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Mini watch implementation for busybox
+ *
+ * Copyright (C) 2001 by Michael Habermann <mhabermann@gmx.de>
+ * 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 "libbb.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) MAIN_EXTERNALLY_VISIBLE;
+int watch_main(int argc UNUSED_PARAM, char **argv)
+{
+       unsigned opt;
+       unsigned period = 2;
+       unsigned width, new_width;
+       char *header;
+       char *cmd;
+
+       opt_complementary = "-1:n+"; // at least one param; -n NUM
+       // "+": stop at first non-option (procps 3.x only)
+       opt = getopt32(argv, "+dtn:", &period);
+       argv += optind;
+
+       // watch from both procps 2.x and 3.x does concatenation. Example:
+       // watch ls -l "a /tmp" "2>&1" -- ls won't see "a /tmp" as one param
+       cmd = *argv;
+       while (*++argv)
+               cmd = xasprintf("%s %s", cmd, *argv); // leaks cmd
+
+       width = (unsigned)-1; // make sure first time new_width != width
+       header = NULL;
+       while (1) {
+               printf("\033[H\033[J");
+               if (!(opt & 0x2)) { // no -t
+                       const unsigned time_len = sizeof("1234-67-90 23:56:89");
+                       time_t t;
+
+                       get_terminal_width_height(STDIN_FILENO, &new_width, NULL);
+                       if (new_width != width) {
+                               width = new_width;
+                               free(header);
+                               header = xasprintf("Every %us: %-*s", period, (int)width, cmd);
+                       }
+                       time(&t);
+                       if (time_len < width)
+                               strftime(header + width - time_len, time_len,
+                                       "%Y-%m-%d %H:%M:%S", localtime(&t));
+
+                       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/runit/Config.in b/runit/Config.in
new file mode 100644 (file)
index 0000000..422ca75
--- /dev/null
@@ -0,0 +1,83 @@
+#
+# 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 FEATURE_RUNSVDIR_LOG
+       bool "Enable scrolling argument log"
+       depends on RUNSVDIR
+       default n
+       help
+         Enable feature where second parameter of runsvdir holds last error
+         message (viewable via top/ps). Otherwise (feature is off
+         or no parameter), error messages go to stderr only.
+
+config SV
+       bool "sv"
+       default n
+       help
+         sv reports the current status and controls the state of services
+         monitored by the runsv supervisor.
+
+config SV_DEFAULT_SERVICE_DIR
+       string "Default directory for services"
+       default "/var/service"
+       depends on SV
+       help
+         Default directory for services.
+         Defaults to "/var/service"
+
+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 (file)
index 0000000..ab9eef6
--- /dev/null
@@ -0,0 +1,17 @@
+# 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_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
+
+lib-$(CONFIG_ENVDIR) += chpst.o
+lib-$(CONFIG_ENVUIDGID) += chpst.o
+lib-$(CONFIG_SETUIDGID) += chpst.o
+lib-$(CONFIG_SOFTLIMIT) += chpst.o
diff --git a/runit/chpst.c b/runit/chpst.c
new file mode 100644 (file)
index 0000000..82a81f5
--- /dev/null
@@ -0,0 +1,388 @@
+/*
+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 Denys Vlasenko <vda.linux@googlemail.com> */
+/* Dependencies on runit_lib.c removed */
+
+#include "libbb.h"
+#include <dirent.h>
+
+/*
+Five applets here: chpst, envdir, envuidgid, setuidgid, softlimit.
+
+Only softlimit and chpst are taking options:
+
+# common
+-o N            Limit number of open files per process
+-p N            Limit number of processes per uid
+-m BYTES        Same as -d BYTES -s BYTES -l BYTES [-a BYTES]
+-d BYTES        Limit data segment
+-f BYTES        Limit output file sizes
+-c BYTES        Limit core file size
+# softlimit
+-a BYTES        Limit total size of all segments
+-s BYTES        Limit stack segment
+-l BYTES        Limit locked memory size
+-r BYTES        Limit resident set size
+-t N            Limit CPU time
+# chpst
+-u USER[:GRP]   Set uid and gid
+-U USER[:GRP]   Set $UID and $GID in environment
+-e DIR          Set environment variables as specified by files in DIR
+-/ DIR          Chroot to DIR
+-n NICE         Add NICE to nice value
+-v              Verbose
+-P              Create new process group
+-0 -1 -2        Close fd 0,1,2
+
+Even though we accept all these options for both softlimit and chpst,
+they are not to be advertised on their help texts.
+We have enough problems with feature creep in other people's
+software, don't want to add our own.
+
+envdir, envuidgid, setuidgid take no options, but they reuse code which
+handles -e, -U and -u.
+*/
+
+enum {
+       OPT_a = (1 << 0) * ENABLE_SOFTLIMIT,
+       OPT_c = (1 << 1) * (ENABLE_SOFTLIMIT || ENABLE_CHPST),
+       OPT_d = (1 << 2) * (ENABLE_SOFTLIMIT || ENABLE_CHPST),
+       OPT_f = (1 << 3) * (ENABLE_SOFTLIMIT || ENABLE_CHPST),
+       OPT_l = (1 << 4) * ENABLE_SOFTLIMIT,
+       OPT_m = (1 << 5) * (ENABLE_SOFTLIMIT || ENABLE_CHPST),
+       OPT_o = (1 << 6) * (ENABLE_SOFTLIMIT || ENABLE_CHPST),
+       OPT_p = (1 << 7) * (ENABLE_SOFTLIMIT || ENABLE_CHPST),
+       OPT_r = (1 << 8) * ENABLE_SOFTLIMIT,
+       OPT_s = (1 << 9) * ENABLE_SOFTLIMIT,
+       OPT_t = (1 << 10) * ENABLE_SOFTLIMIT,
+       OPT_u = (1 << 11) * (ENABLE_CHPST || ENABLE_SETUIDGID),
+       OPT_U = (1 << 12) * (ENABLE_CHPST || ENABLE_ENVUIDGID),
+       OPT_e = (1 << 13) * (ENABLE_CHPST || ENABLE_ENVDIR),
+       OPT_root = (1 << 14) * ENABLE_CHPST,
+       OPT_n = (1 << 15) * ENABLE_CHPST,
+       OPT_v = (1 << 16) * ENABLE_CHPST,
+       OPT_P = (1 << 17) * ENABLE_CHPST,
+       OPT_0 = (1 << 18) * ENABLE_CHPST,
+       OPT_1 = (1 << 19) * ENABLE_CHPST,
+       OPT_2 = (1 << 20) * ENABLE_CHPST,
+};
+
+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 (;;) {
+               char buf[256];
+               char *tail;
+               int size;
+
+               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) && directory_name) {
+                               if (option_mask32 & OPT_v)
+                                       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);
+               }
+               size = full_read(fd, buf, sizeof(buf)-1);
+               close(fd);
+               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 = strchr(buf, '\n');
+               /* skip trailing whitespace */
+               while (1) {
+                       *tail = '\0';
+                       tail--;
+                       if (tail < buf || !isspace(*tail))
+                               break;
+               }
+               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;
+
+       /* Never fails under Linux (except if you pass it bad arguments) */
+       getrlimit(what, &r);
+       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");
+}
+
+int chpst_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int chpst_main(int argc UNUSED_PARAM, char **argv)
+{
+       struct bb_uidgid_t ugid;
+       char *set_user = set_user; /* for compiler */
+       char *env_user = env_user;
+       char *env_dir = env_dir;
+       char *root;
+       char *nicestr;
+       unsigned limita;
+       unsigned limitc;
+       unsigned limitd;
+       unsigned limitf;
+       unsigned limitl;
+       unsigned limitm;
+       unsigned limito;
+       unsigned limitp;
+       unsigned limitr;
+       unsigned limits;
+       unsigned limitt;
+       unsigned opt;
+
+       if ((ENABLE_CHPST && applet_name[0] == 'c')
+        || (ENABLE_SOFTLIMIT && applet_name[1] == 'o')
+       ) {
+               // FIXME: can we live with int-sized limits?
+               // can we live with 40000 days?
+               // if yes -> getopt converts strings to numbers for us
+               opt_complementary = "-1:a+:c+:d+:f+:l+:m+:o+:p+:r+:s+:t+";
+               opt = getopt32(argv, "+a:c:d:f:l:m:o:p:r:s:t:u:U:e:"
+                       USE_CHPST("/:n:vP012"),
+                       &limita, &limitc, &limitd, &limitf, &limitl,
+                       &limitm, &limito, &limitp, &limitr, &limits, &limitt,
+                       &set_user, &env_user, &env_dir
+                       USE_CHPST(, &root, &nicestr));
+               argv += optind;
+               if (opt & OPT_m) { // -m means -asld
+                       limita = limits = limitl = limitd = limitm;
+                       opt |= (OPT_s | OPT_l | OPT_a | OPT_d);
+               }
+       } else {
+               option_mask32 = opt = 0;
+               argv++;
+               if (!*argv)
+                       bb_show_usage();
+       }
+
+       // envdir?
+       if (ENABLE_ENVDIR && applet_name[3] == 'd') {
+               env_dir = *argv++;
+               opt |= OPT_e;
+       }
+
+       // setuidgid?
+       if (ENABLE_SETUIDGID && applet_name[1] == 'e') {
+               set_user = *argv++;
+               opt |= OPT_u;
+       }
+
+       // envuidgid?
+       if (ENABLE_ENVUIDGID && applet_name[0] == 'e' && applet_name[3] == 'u') {
+               env_user = *argv++;
+               opt |= OPT_U;
+       }
+
+       // we must have PROG [ARGS]
+       if (!*argv)
+               bb_show_usage();
+
+       // set limits
+       if (opt & OPT_d) {
+#ifdef RLIMIT_DATA
+               limit(RLIMIT_DATA, limitd);
+#else
+               if (opt & OPT_v)
+                       bb_error_msg("system does not support RLIMIT_%s",
+                               "DATA");
+#endif
+       }
+       if (opt & OPT_s) {
+#ifdef RLIMIT_STACK
+               limit(RLIMIT_STACK, limits);
+#else
+               if (opt & OPT_v)
+                       bb_error_msg("system does not support RLIMIT_%s",
+                               "STACK");
+#endif
+       }
+       if (opt & OPT_l) {
+#ifdef RLIMIT_MEMLOCK
+               limit(RLIMIT_MEMLOCK, limitl);
+#else
+               if (opt & OPT_v)
+                       bb_error_msg("system does not support RLIMIT_%s",
+                               "MEMLOCK");
+#endif
+       }
+       if (opt & OPT_a) {
+#ifdef RLIMIT_VMEM
+               limit(RLIMIT_VMEM, limita);
+#else
+#ifdef RLIMIT_AS
+               limit(RLIMIT_AS, limita);
+#else
+               if (opt & OPT_v)
+                       bb_error_msg("system does not support RLIMIT_%s",
+                               "VMEM");
+#endif
+#endif
+       }
+       if (opt & OPT_o) {
+#ifdef RLIMIT_NOFILE
+               limit(RLIMIT_NOFILE, limito);
+#else
+#ifdef RLIMIT_OFILE
+               limit(RLIMIT_OFILE, limito);
+#else
+               if (opt & OPT_v)
+                       bb_error_msg("system does not support RLIMIT_%s",
+                               "NOFILE");
+#endif
+#endif
+       }
+       if (opt & OPT_p) {
+#ifdef RLIMIT_NPROC
+               limit(RLIMIT_NPROC, limitp);
+#else
+               if (opt & OPT_v)
+                       bb_error_msg("system does not support RLIMIT_%s",
+                               "NPROC");
+#endif
+       }
+       if (opt & OPT_f) {
+#ifdef RLIMIT_FSIZE
+               limit(RLIMIT_FSIZE, limitf);
+#else
+               if (opt & OPT_v)
+                       bb_error_msg("system does not support RLIMIT_%s",
+                               "FSIZE");
+#endif
+       }
+       if (opt & OPT_c) {
+#ifdef RLIMIT_CORE
+               limit(RLIMIT_CORE, limitc);
+#else
+               if (opt & OPT_v)
+                       bb_error_msg("system does not support RLIMIT_%s",
+                               "CORE");
+#endif
+       }
+       if (opt & OPT_r) {
+#ifdef RLIMIT_RSS
+               limit(RLIMIT_RSS, limitr);
+#else
+               if (opt & OPT_v)
+                       bb_error_msg("system does not support RLIMIT_%s",
+                               "RSS");
+#endif
+       }
+       if (opt & OPT_t) {
+#ifdef RLIMIT_CPU
+               limit(RLIMIT_CPU, limitt);
+#else
+               if (opt & OPT_v)
+                       bb_error_msg("system does not support RLIMIT_%s",
+                               "CPU");
+#endif
+       }
+
+       if (opt & OPT_P)
+               setsid();
+
+       if (opt & OPT_e)
+               edir(env_dir);
+
+       // FIXME: chrooted jail must have /etc/passwd if we move this after chroot!
+       // OTOH chroot fails for non-roots!
+       // SOLUTION: cache uid/gid before chroot, apply uid/gid after
+       if (opt & OPT_U) {
+               xget_uidgid(&ugid, env_user);
+               xsetenv("GID", utoa(ugid.gid));
+               xsetenv("UID", utoa(ugid.uid));
+       }
+
+       if (opt & OPT_u) {
+               xget_uidgid(&ugid, set_user);
+       }
+
+       if (opt & OPT_root) {
+               xchdir(root);
+               xchroot(".");
+       }
+
+       if (opt & OPT_u) {
+               if (setgroups(1, &ugid.gid) == -1)
+                       bb_perror_msg_and_die("setgroups");
+               xsetgid(ugid.gid);
+               xsetuid(ugid.uid);
+       }
+
+       if (opt & OPT_n) {
+               errno = 0;
+               if (nice(xatoi(nicestr)) == -1)
+                       bb_perror_msg_and_die("nice");
+       }
+
+       if (opt & OPT_0)
+               close(STDIN_FILENO);
+       if (opt & OPT_1)
+               close(STDOUT_FILENO);
+       if (opt & OPT_2)
+               close(STDERR_FILENO);
+
+       BB_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 (file)
index 0000000..f33619d
--- /dev/null
@@ -0,0 +1,273 @@
+/*
+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 Denys Vlasenko <vda.linux@googlemail.com> */
+/* Collected into one file from runit's many tiny files */
+/* TODO: review, eliminate unneeded stuff, move good stuff to libbb */
+
+#include <sys/poll.h>
+#include <sys/file.h>
+#include "libbb.h"
+#include "runit_lib.h"
+
+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;
+       }
+       return t - s;
+}
+
+#ifdef UNUSED
+static /* as it isn't used anywhere else */
+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;
+}
+
+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;
+}
+
+
+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;
+       }
+}
+
+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;
+}
+
+void taia_now(struct taia *t)
+{
+       struct timeval now;
+       gettimeofday(&now, NULL);
+       tai_unix(&t->sec, now.tv_sec);
+       t->nano = 1000 * now.tv_usec + 500;
+       t->atto = 0;
+}
+
+/* UNUSED
+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;
+}
+*/
+
+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;
+       }
+}
+
+/* XXX: breaks tai encapsulation */
+void taia_uint(struct taia *t, unsigned s)
+{
+       t->sec.x = s;
+       t->nano = 0;
+       t->atto = 0;
+}
+
+static
+uint64_t taia2millisec(const struct taia *t)
+{
+       return (t->sec.x * 1000) + (t->nano / 1000000);
+}
+
+void iopause(iopause_fd *x, unsigned len, struct taia *deadline, struct taia *stamp)
+{
+       int millisecs;
+       int i;
+
+       if (taia_less(deadline, stamp))
+               millisecs = 0;
+       else {
+               uint64_t m;
+               struct taia t;
+               t = *stamp;
+               taia_sub(&t, deadline, &t);
+               millisecs = m = taia2millisec(&t);
+               if (m > 1000) millisecs = 1000;
+               millisecs += 20;
+       }
+
+       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? */
+}
+#endif
+
+int lock_ex(int fd)
+{
+       return flock(fd,LOCK_EX);
+}
+
+int lock_exnb(int fd)
+{
+       return flock(fd,LOCK_EX | LOCK_NB);
+}
+
+int open_append(const char *fn)
+{
+       return open(fn, O_WRONLY|O_NDELAY|O_APPEND|O_CREAT, 0600);
+}
+
+int open_read(const char *fn)
+{
+       return open(fn, O_RDONLY|O_NDELAY);
+}
+
+int open_trunc(const char *fn)
+{
+       return open(fn,O_WRONLY | O_NDELAY | O_TRUNC | O_CREAT,0644);
+}
+
+int open_write(const char *fn)
+{
+       return open(fn, O_WRONLY|O_NDELAY);
+}
+
+unsigned pmatch(const char *p, const char *s, unsigned len)
+{
+       for (;;) {
+               char c = *p++;
+               if (!c) return !len;
+               switch (c) {
+               case '*':
+                       c = *p;
+                       if (!c) return 1;
+                       for (;;) {
+                               if (!len) return 0;
+                               if (*s == c) break;
+                               ++s;
+                               --len;
+                       }
+                       continue;
+               case '+':
+                       c = *p++;
+                       if (c != *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;
+}
diff --git a/runit/runit_lib.h b/runit/runit_lib.h
new file mode 100644 (file)
index 0000000..fd94db9
--- /dev/null
@@ -0,0 +1,101 @@
+/*
+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.
+*/
+
+PUSH_AND_SET_FUNCTION_VISIBILITY_TO_HIDDEN
+
+extern unsigned byte_chr(char *s,unsigned n,int c);
+
+#define direntry struct dirent
+
+//struct tai {
+//     uint64_t x;
+//};
+//
+//#define tai_unix(t,u) ((void) ((t)->x = 0x400000000000000aULL + (uint64_t) (u)))
+//
+//#define TAI_PACK 8
+//extern void tai_unpack(const char *,struct tai *);
+//
+//extern void tai_uint(struct tai *,unsigned);
+//
+//struct taia {
+//     struct tai sec;
+//     unsigned long nano; /* 0...999999999 */
+//     unsigned long atto; /* 0...999999999 */
+//};
+//
+//extern void taia_now(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_uint(struct taia *,unsigned);
+//
+//typedef struct pollfd iopause_fd;
+//#define IOPAUSE_READ POLLIN
+//#define IOPAUSE_WRITE POLLOUT
+//
+//extern void iopause(iopause_fd *,unsigned,struct taia *,struct taia *);
+
+extern int lock_ex(int);
+extern int lock_un(int);
+extern int lock_exnb(int);
+
+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 *);
+
+extern unsigned pmatch(const char *, const char *, unsigned);
+
+#define str_diff(s,t) strcmp((s), (t))
+#define str_equal(s,t) (!strcmp((s), (t)))
+
+/*
+ * runsv / supervise / sv stuff
+ */
+typedef struct svstatus_t {
+       uint64_t time_be64 PACKED;
+       uint32_t time_nsec_be32 PACKED;
+       uint32_t pid_le32 PACKED;
+       uint8_t  paused;
+       uint8_t  want;
+       uint8_t  got_term;
+       uint8_t  run_or_finish;
+} svstatus_t;
+struct ERR_svstatus_must_be_20_bytes {
+       char ERR_svstatus_must_be_20_bytes[sizeof(svstatus_t) == 20 ? 1 : -1];
+};
+
+POP_SAVED_FUNCTION_VISIBILITY
diff --git a/runit/runsv.c b/runit/runsv.c
new file mode 100644 (file)
index 0000000..6d34dc1
--- /dev/null
@@ -0,0 +1,650 @@
+/*
+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 Denys Vlasenko <vda.linux@googlemail.com> */
+/* TODO: depends on runit_lib.c - review and reduce/eliminate */
+
+#include <sys/poll.h>
+#include <sys/file.h>
+#include "libbb.h"
+#include "runit_lib.h"
+
+#if ENABLE_MONOTONIC_SYSCALL
+#include <sys/syscall.h>
+
+/* libc has incredibly messy way of doing this,
+ * typically requiring -lrt. We just skip all this mess */
+static void gettimeofday_ns(struct timespec *ts)
+{
+       syscall(__NR_clock_gettime, CLOCK_REALTIME, ts);
+}
+#else
+static void gettimeofday_ns(struct timespec *ts)
+{
+       if (sizeof(struct timeval) == sizeof(struct timespec)
+        && sizeof(((struct timeval*)ts)->tv_usec) == sizeof(ts->tv_nsec)
+       ) {
+               /* Cheat */
+               gettimeofday((void*)ts, NULL);
+               ts->tv_nsec *= 1000;
+       } else {
+               extern void BUG_need_to_implement_gettimeofday_ns(void);
+               BUG_need_to_implement_gettimeofday_ns();
+       }
+}
+#endif
+
+/* Compare possibly overflowing unsigned counters */
+#define LESS(a,b) ((int)((unsigned)(b) - (unsigned)(a)) > 0)
+
+/* 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;
+       smallint state;
+       smallint ctrl;
+       smallint want;
+       smallint islog;
+       struct timespec start;
+       int fdlock;
+       int fdcontrol;
+       int fdcontrolwrite;
+};
+
+struct globals {
+       smallint haslog;
+       smallint sigterm;
+       smallint pidchanged;
+       struct fd_pair selfpipe;
+       struct fd_pair logpipe;
+       char *dir;
+       struct svdir svd[2];
+};
+#define G (*(struct globals*)&bb_common_bufsiz1)
+#define haslog       (G.haslog      )
+#define sigterm      (G.sigterm     )
+#define pidchanged   (G.pidchanged  )
+#define selfpipe     (G.selfpipe    )
+#define logpipe      (G.logpipe     )
+#define dir          (G.dir         )
+#define svd          (G.svd         )
+#define INIT_G() do { \
+       pidchanged = 1; \
+} while (0)
+
+static void fatal2_cannot(const char *m1, const char *m2)
+{
+       bb_perror_msg_and_die("%s: fatal: cannot %s%s", dir, m1, m2);
+       /* was exiting 111 */
+}
+static void fatal_cannot(const char *m)
+{
+       fatal2_cannot(m, "");
+       /* was exiting 111 */
+}
+static void fatal2x_cannot(const char *m1, const char *m2)
+{
+       bb_error_msg_and_die("%s: fatal: cannot %s%s", dir, m1, m2);
+       /* was exiting 111 */
+}
+static void warn_cannot(const char *m)
+{
+       bb_perror_msg("%s: warning: cannot %s", dir, m);
+}
+
+static void s_child(int sig_no UNUSED_PARAM)
+{
+       write(selfpipe.wr, "", 1);
+}
+
+static void s_term(int sig_no UNUSED_PARAM)
+{
+       sigterm = 1;
+       write(selfpipe.wr, "", 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 void update_status(struct svdir *s)
+{
+       ssize_t sz;
+       int fd;
+       svstatus_t status;
+
+       /* pid */
+       if (pidchanged) {
+               fd = open_trunc_or_warn("supervise/pid.new");
+               if (fd < 0)
+                       return;
+               if (s->pid) {
+                       char spid[sizeof(int)*3 + 2];
+                       int size = sprintf(spid, "%u\n", (unsigned)s->pid);
+                       write(fd, spid, size);
+               }
+               close(fd);
+               if (rename_or_warn("supervise/pid.new",
+                   s->islog ? "log/supervise/pid" : "log/supervise/pid"+4))
+                       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);
+       }
+
+       rename_or_warn("supervise/stat.new",
+               s->islog ? "log/supervise/stat" : "log/supervise/stat"+4);
+
+       /* supervise compatibility */
+       memset(&status, 0, sizeof(status));
+       status.time_be64 = SWAP_BE64(s->start.tv_sec + 0x400000000000000aULL);
+       status.time_nsec_be32 = SWAP_BE32(s->start.tv_nsec);
+       status.pid_le32 = SWAP_LE32(s->pid);
+       if (s->ctrl & C_PAUSE)
+               status.paused = 1;
+       if (s->want == W_UP)
+               status.want = 'u';
+       else
+               status.want = 'd';
+       if (s->ctrl & C_TERM)
+               status.got_term = 1;
+       status.run_or_finish = s->state;
+       fd = open_trunc_or_warn("supervise/status.new");
+       if (fd < 0)
+               return;
+       sz = write(fd, &status, sizeof(status));
+       close(fd);
+       if (sz != sizeof(status)) {
+               warn_cannot("write supervise/status.new");
+               unlink("supervise/status.new");
+               return;
+       }
+       rename_or_warn("supervise/status.new",
+               s->islog ? "log/supervise/status" : "log/supervise/status"+4);
+}
+
+static unsigned custom(struct svdir *s, char c)
+{
+       pid_t pid;
+       int w;
+       char a[10];
+       struct stat st;
+
+       if (s->islog) return 0;
+       strcpy(a, "control/?");
+       a[8] = c; /* replace '?' */
+       if (stat(a, &st) == 0) {
+               if (st.st_mode & S_IXUSR) {
+                       pid = vfork();
+                       if (pid == -1) {
+                               warn_cannot("vfork for control/?");
+                               return 0;
+                       }
+                       if (!pid) {
+                               /* child */
+                               if (haslog && dup2(logpipe.wr, 1) == -1)
+                                       warn_cannot("setup stdout for control/?");
+                               execl(a, a, (char *) NULL);
+                               fatal_cannot("run control/?");
+                       }
+                       /* parent */
+                       if (safe_waitpid(pid, &w, 0) == -1) {
+                               warn_cannot("wait for child control/?");
+                               return 0;
+                       }
+                       return !wait_exitcode(w);
+               }
+       } else {
+               if (errno != ENOENT)
+                       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;
+       const char *run;
+
+       if (s->state == S_FINISH)
+               run = "./finish";
+       else {
+               run = "./run";
+               custom(s, 'u');
+       }
+
+       if (s->pid != 0)
+               stopservice(s); /* should never happen */
+       while ((p = vfork()) == -1) {
+               warn_cannot("vfork, sleeping");
+               sleep(5);
+       }
+       if (p == 0) {
+               /* child */
+               if (haslog) {
+                       /* NB: bug alert! right order is close, then dup2 */
+                       if (s->islog) {
+                               xchdir("./log");
+                               close(logpipe.wr);
+                               xdup2(logpipe.rd, 0);
+                       } else {
+                               close(logpipe.rd);
+                               xdup2(logpipe.wr, 1);
+                       }
+               }
+               /* Non-ignored signals revert to SIG_DFL on exec anyway */
+               /*bb_signals(0
+                       + (1 << SIGCHLD)
+                       + (1 << SIGTERM)
+                       , SIG_DFL);*/
+               sig_unblock(SIGCHLD);
+               sig_unblock(SIGTERM);
+               execl(run, run, (char *) NULL);
+               fatal2_cannot(s->islog ? "start log/" : "start ", run);
+       }
+       /* parent */
+       if (s->state != S_FINISH) {
+               gettimeofday_ns(&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)
+{
+       int sig;
+
+       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);
+               /* FALLTHROUGH */
+       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);
+               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 */
+               sig = SIGALRM;
+               goto sendsig;
+       case 'h': /* sig hup */
+               sig = SIGHUP;
+               goto sendsig;
+       case 'i': /* sig int */
+               sig = SIGINT;
+               goto sendsig;
+       case 'q': /* sig quit */
+               sig = SIGQUIT;
+               goto sendsig;
+       case '1': /* sig usr1 */
+               sig = SIGUSR1;
+               goto sendsig;
+       case '2': /* sig usr2 */
+               sig = SIGUSR2;
+               goto sendsig;
+       }
+       return 1;
+ sendsig:
+       if (s->pid && !custom(s, c))
+               kill(s->pid, sig);
+       return 1;
+}
+
+int runsv_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int runsv_main(int argc UNUSED_PARAM, char **argv)
+{
+       struct stat s;
+       int fd;
+       int r;
+       char buf[256];
+
+       INIT_G();
+
+       if (!argv[1] || argv[2])
+               bb_show_usage();
+       dir = argv[1];
+
+       xpiped_pair(selfpipe);
+       close_on_exec_on(selfpipe.rd);
+       close_on_exec_on(selfpipe.wr);
+       ndelay_on(selfpipe.rd);
+       ndelay_on(selfpipe.wr);
+
+       sig_block(SIGCHLD);
+       bb_signals_recursive_norestart(1 << SIGCHLD, s_child);
+       sig_block(SIGTERM);
+       bb_signals_recursive_norestart(1 << SIGTERM, s_term);
+
+       xchdir(dir);
+       /* bss: svd[0].pid = 0; */
+       if (S_DOWN) svd[0].state = S_DOWN; /* otherwise already 0 (bss) */
+       if (C_NOOP) svd[0].ctrl = C_NOOP;
+       if (W_UP) svd[0].want = W_UP;
+       /* bss: svd[0].islog = 0; */
+       /* bss: svd[1].pid = 0; */
+       gettimeofday_ns(&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)) {
+                       errno = 0;
+                       warn_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;
+                       gettimeofday_ns(&svd[1].start);
+                       if (stat("log/down", &s) != -1)
+                               svd[1].want = W_DOWN;
+                       xpiped_pair(logpipe);
+                       close_on_exec_on(logpipe.rd);
+                       close_on_exec_on(logpipe.wr);
+               }
+       }
+
+       if (mkdir("supervise", 0700) == -1) {
+               r = readlink("supervise", buf, sizeof(buf));
+               if (r != -1) {
+                       if (r == sizeof(buf))
+                               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");
+       close_on_exec_on(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");
+               close_on_exec_on(svd[1].fdlock);
+       }
+
+       mkfifo("log/supervise/control"+4, 0600);
+       svd[0].fdcontrol = xopen("log/supervise/control"+4, O_RDONLY|O_NDELAY);
+       close_on_exec_on(svd[0].fdcontrol);
+       svd[0].fdcontrolwrite = xopen("log/supervise/control"+4, O_WRONLY|O_NDELAY);
+       close_on_exec_on(svd[0].fdcontrolwrite);
+       update_status(&svd[0]);
+       if (haslog) {
+               mkfifo("log/supervise/control", 0600);
+               svd[1].fdcontrol = xopen("log/supervise/control", O_RDONLY|O_NDELAY);
+               close_on_exec_on(svd[1].fdcontrol);
+               svd[1].fdcontrolwrite = xopen("log/supervise/control", O_WRONLY|O_NDELAY);
+               close_on_exec_on(svd[1].fdcontrolwrite);
+               update_status(&svd[1]);
+       }
+       mkfifo("log/supervise/ok"+4, 0600);
+       fd = xopen("log/supervise/ok"+4, O_RDONLY|O_NDELAY);
+       close_on_exec_on(fd);
+       if (haslog) {
+               mkfifo("log/supervise/ok", 0600);
+               fd = xopen("log/supervise/ok", O_RDONLY|O_NDELAY);
+               close_on_exec_on(fd);
+       }
+       for (;;) {
+               struct pollfd x[3];
+               unsigned deadline;
+               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.rd;
+               x[0].events = POLLIN;
+               x[1].fd = svd[0].fdcontrol;
+               x[1].events = POLLIN;
+               /* x[2] is used only if haslog == 1 */
+               x[2].fd = svd[1].fdcontrol;
+               x[2].events = POLLIN;
+               sig_unblock(SIGTERM);
+               sig_unblock(SIGCHLD);
+               poll(x, 2 + haslog, 3600*1000);
+               sig_block(SIGTERM);
+               sig_block(SIGCHLD);
+
+               while (read(selfpipe.rd, &ch, 1) == 1)
+                       continue;
+
+               for (;;) {
+                       pid_t child;
+                       int wstat;
+
+                       child = wait_any_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;
+                               deadline = svd[0].start.tv_sec + 1;
+                               gettimeofday_ns(&svd[0].start);
+                               update_status(&svd[0]);
+                               if (LESS(svd[0].start.tv_sec, 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;
+                                       deadline = svd[1].start.tv_sec + 1;
+                                       gettimeofday_ns(&svd[1].start);
+                                       update_status(&svd[1]);
+                                       if (LESS(svd[1].start.tv_sec, deadline))
+                                               sleep(1);
+                               }
+                       }
+               } /* for (;;) */
+               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(EXIT_SUCCESS);
+                       if (svd[1].want != W_EXIT) {
+                               svd[1].want = W_EXIT;
+                               /* stopservice(&svd[1]); */
+                               update_status(&svd[1]);
+                               close(logpipe.wr);
+                               close(logpipe.rd);
+                       }
+               }
+       } /* for (;;) */
+       /* not reached */
+       return 0;
+}
diff --git a/runit/runsvdir.c b/runit/runsvdir.c
new file mode 100644 (file)
index 0000000..a77bc3f
--- /dev/null
@@ -0,0 +1,393 @@
+/*
+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 Denys Vlasenko <vda.linux@googlemail.com> */
+/* TODO: depends on runit_lib.c - review and reduce/eliminate */
+
+#include <sys/poll.h>
+#include <sys/file.h>
+#include "libbb.h"
+#include "runit_lib.h"
+
+#define MAXSERVICES 1000
+
+/* Should be not needed - all dirs are on same FS, right? */
+#define CHECK_DEVNO_TOO 0
+
+struct service {
+#if CHECK_DEVNO_TOO
+       dev_t dev;
+#endif
+       ino_t ino;
+       pid_t pid;
+       smallint isgone;
+};
+
+struct globals {
+       struct service *sv;
+       char *svdir;
+       int svnum;
+#if ENABLE_FEATURE_RUNSVDIR_LOG
+       char *rplog;
+       int rploglen;
+       struct fd_pair logpipe;
+       struct pollfd pfd[1];
+       unsigned stamplog;
+#endif
+};
+#define G (*(struct globals*)&bb_common_bufsiz1)
+#define sv          (G.sv          )
+#define svdir       (G.svdir       )
+#define svnum       (G.svnum       )
+#define rplog       (G.rplog       )
+#define rploglen    (G.rploglen    )
+#define logpipe     (G.logpipe     )
+#define pfd         (G.pfd         )
+#define stamplog    (G.stamplog    )
+#define INIT_G() do { \
+} while (0)
+
+static void fatal2_cannot(const char *m1, const char *m2)
+{
+       bb_perror_msg_and_die("%s: fatal: cannot %s%s", svdir, m1, m2);
+       /* was exiting 100 */
+}
+static void warn3x(const char *m1, const char *m2, const char *m3)
+{
+       bb_error_msg("%s: warning: %s%s%s", svdir, m1, m2, m3);
+}
+static void warn2_cannot(const char *m1, const char *m2)
+{
+       warn3x("cannot ", m1, m2);
+}
+#if ENABLE_FEATURE_RUNSVDIR_LOG
+static void warnx(const char *m1)
+{
+       warn3x(m1, "", "");
+}
+#endif
+
+/* inlining + vfork -> bigger code */
+static NOINLINE pid_t runsv(const char *name)
+{
+       pid_t pid;
+
+       /* If we got signaled, stop spawning children at once! */
+       if (bb_got_signal)
+               return 0;
+
+       pid = vfork();
+       if (pid == -1) {
+               warn2_cannot("vfork", "");
+               return 0;
+       }
+       if (pid == 0) {
+               /* child */
+               if (option_mask32 & 1) /* -P option? */
+                       setsid();
+/* man execv:
+ * "Signals set to be caught by the calling process image
+ *  shall be set to the default action in the new process image."
+ * Therefore, we do not need this: */
+#if 0
+               bb_signals(0
+                       | (1 << SIGHUP)
+                       | (1 << SIGTERM)
+                       , SIG_DFL);
+#endif
+               execlp("runsv", "runsv", name, (char *) NULL);
+               fatal2_cannot("start runsv ", name);
+       }
+       return pid;
+}
+
+/* gcc 4.3.0 does better with NOINLINE */
+static NOINLINE int do_rescan(void)
+{
+       DIR *dir;
+       direntry *d;
+       int i;
+       struct stat s;
+       int need_rescan = 0;
+
+       dir = opendir(".");
+       if (!dir) {
+               warn2_cannot("open directory ", svdir);
+               return 1; /* need to rescan again soon */
+       }
+       for (i = 0; i < svnum; i++)
+               sv[i].isgone = 1;
+
+       while (1) {
+               errno = 0;
+               d = readdir(dir);
+               if (!d)
+                       break;
+               if (d->d_name[0] == '.')
+                       continue;
+               if (stat(d->d_name, &s) == -1) {
+                       warn2_cannot("stat ", d->d_name);
+                       continue;
+               }
+               if (!S_ISDIR(s.st_mode))
+                       continue;
+               /* Do we have this service listed already? */
+               for (i = 0; i < svnum; i++) {
+                       if ((sv[i].ino == s.st_ino)
+#if CHECK_DEVNO_TOO
+                        && (sv[i].dev == s.st_dev)
+#endif
+                       ) {
+                               if (sv[i].pid == 0) /* restart if it has died */
+                                       goto run_ith_sv;
+                               sv[i].isgone = 0; /* "we still see you" */
+                               goto next_dentry;
+                       }
+               }
+               { /* Not found, make new service */
+                       struct service *svnew = realloc(sv, (i+1) * sizeof(*sv));
+                       if (!svnew) {
+                               warn2_cannot("start runsv ", d->d_name);
+                               need_rescan = 1;
+                               continue;
+                       }
+                       sv = svnew;
+                       svnum++;
+#if CHECK_DEVNO_TOO
+                       sv[i].dev = s.st_dev;
+#endif
+                       sv[i].ino = s.st_ino;
+ run_ith_sv:
+                       sv[i].pid = runsv(d->d_name);
+                       sv[i].isgone = 0;
+               }
+ next_dentry: ;
+       }
+       i = errno;
+       closedir(dir);
+       if (i) { /* readdir failed */
+               warn2_cannot("read directory ", svdir);
+               return 1; /* need to rescan again soon */
+       }
+
+       /* Send SIGTERM to runsv whose directories
+        * were no longer found (-> must have been removed) */
+       for (i = 0; i < svnum; i++) {
+               if (!sv[i].isgone)
+                       continue;
+               if (sv[i].pid)
+                       kill(sv[i].pid, SIGTERM);
+               svnum--;
+               sv[i] = sv[svnum];
+               i--; /* so that we don't skip new sv[i] (bug was here!) */
+       }
+       return need_rescan;
+}
+
+int runsvdir_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int runsvdir_main(int argc UNUSED_PARAM, char **argv)
+{
+       struct stat s;
+       dev_t last_dev = last_dev; /* for gcc */
+       ino_t last_ino = last_ino; /* for gcc */
+       time_t last_mtime = 0;
+       int wstat;
+       int curdir;
+       pid_t pid;
+       unsigned deadline;
+       unsigned now;
+       unsigned stampcheck;
+       int i;
+       int need_rescan = 1;
+       char *opt_s_argv[3];
+
+       INIT_G();
+
+       opt_complementary = "-1";
+       opt_s_argv[0] = NULL;
+       opt_s_argv[2] = NULL;
+       getopt32(argv, "Ps:", &opt_s_argv[0]);
+       argv += optind;
+
+       bb_signals(0
+               | (1 << SIGTERM)
+               | (1 << SIGHUP)
+               /* For busybox's init, SIGTERM == reboot,
+                * SIGUSR1 == halt
+                * SIGUSR2 == poweroff
+                * so we need to intercept SIGUSRn too.
+                * Note that we do not implement actual reboot
+                * (killall(TERM) + umount, etc), we just pause
+                * respawing and avoid exiting (-> making kernel oops).
+                * The user is responsible for the rest. */
+               | (getpid() == 1 ? ((1 << SIGUSR1) | (1 << SIGUSR2)) : 0)
+               , record_signo);
+       svdir = *argv++;
+
+#if ENABLE_FEATURE_RUNSVDIR_LOG
+       /* setup log */
+       if (*argv) {
+               rplog = *argv;
+               rploglen = strlen(rplog);
+               if (rploglen < 7) {
+                       warnx("log must have at least seven characters");
+               } else if (piped_pair(logpipe)) {
+                       warnx("cannot create pipe for log");
+               } else {
+                       close_on_exec_on(logpipe.rd);
+                       close_on_exec_on(logpipe.wr);
+                       ndelay_on(logpipe.rd);
+                       ndelay_on(logpipe.wr);
+                       if (dup2(logpipe.wr, 2) == -1) {
+                               warnx("cannot set filedescriptor for log");
+                       } else {
+                               pfd[0].fd = logpipe.rd;
+                               pfd[0].events = POLLIN;
+                               stamplog = monotonic_sec();
+                               goto run;
+                       }
+               }
+               rplog = NULL;
+               warnx("log service disabled");
+       }
+ run:
+#endif
+       curdir = open_read(".");
+       if (curdir == -1)
+               fatal2_cannot("open current directory", "");
+       close_on_exec_on(curdir);
+
+       stampcheck = monotonic_sec();
+
+       for (;;) {
+               /* collect children */
+               for (;;) {
+                       pid = wait_any_nohang(&wstat);
+                       if (pid <= 0)
+                               break;
+                       for (i = 0; i < svnum; i++) {
+                               if (pid == sv[i].pid) {
+                                       /* runsv has died */
+                                       sv[i].pid = 0;
+                                       need_rescan = 1;
+                               }
+                       }
+               }
+
+               now = monotonic_sec();
+               if ((int)(now - stampcheck) >= 0) {
+                       /* wait at least a second */
+                       stampcheck = now + 1;
+
+                       if (stat(svdir, &s) != -1) {
+                               if (need_rescan || s.st_mtime != last_mtime
+                                || s.st_ino != last_ino || s.st_dev != last_dev
+                               ) {
+                                       /* svdir modified */
+                                       if (chdir(svdir) != -1) {
+                                               last_mtime = s.st_mtime;
+                                               last_dev = s.st_dev;
+                                               last_ino = s.st_ino;
+                                               //if (now <= mtime)
+                                               //      sleep(1);
+                                               need_rescan = do_rescan();
+                                               while (fchdir(curdir) == -1) {
+                                                       warn2_cannot("change directory, pausing", "");
+                                                       sleep(5);
+                                               }
+                                       } else {
+                                               warn2_cannot("change directory to ", svdir);
+                                       }
+                               }
+                       } else {
+                               warn2_cannot("stat ", svdir);
+                       }
+               }
+
+#if ENABLE_FEATURE_RUNSVDIR_LOG
+               if (rplog) {
+                       if ((int)(now - stamplog) >= 0) {
+                               write(logpipe.wr, ".", 1);
+                               stamplog = now + 900;
+                       }
+               }
+               pfd[0].revents = 0;
+#endif
+               deadline = (need_rescan ? 1 : 5);
+               sig_block(SIGCHLD);
+#if ENABLE_FEATURE_RUNSVDIR_LOG
+               if (rplog)
+                       poll(pfd, 1, deadline*1000);
+               else
+#endif
+                       sleep(deadline);
+               sig_unblock(SIGCHLD);
+
+#if ENABLE_FEATURE_RUNSVDIR_LOG
+               if (pfd[0].revents & POLLIN) {
+                       char ch;
+                       while (read(logpipe.rd, &ch, 1) > 0) {
+                               if (ch < ' ')
+                                       ch = ' ';
+                               for (i = 6; i < rploglen; i++)
+                                       rplog[i-1] = rplog[i];
+                               rplog[rploglen-1] = ch;
+                       }
+               }
+#endif
+               if (!bb_got_signal)
+                       continue;
+
+               /* -s SCRIPT: useful if we are init.
+                * In this case typically script never returns,
+                * it halts/powers off/reboots the system. */
+               if (opt_s_argv[0]) {
+                       /* Single parameter: signal# */
+                       opt_s_argv[1] = utoa(bb_got_signal);
+                       pid = spawn(opt_s_argv);
+                       if (pid > 0) {
+                               /* Remembering to wait for _any_ children,
+                                * not just pid */
+                               while (wait(NULL) != pid)
+                                       continue;
+                       }
+               }
+
+               if (bb_got_signal == SIGHUP) {
+                       for (i = 0; i < svnum; i++)
+                               if (sv[i].pid)
+                                       kill(sv[i].pid, SIGTERM);
+               }
+               /* SIGHUP or SIGTERM (or SIGUSRn if we are init) */
+               /* Exit unless we are init */
+               if (getpid() != 1)
+                       return (SIGHUP == bb_got_signal) ? 111 : EXIT_SUCCESS;
+
+               /* init continues to monitor services forever */
+               bb_got_signal = 0;
+       } /* for (;;) */
+}
diff --git a/runit/sv.c b/runit/sv.c
new file mode 100644 (file)
index 0000000..20e8619
--- /dev/null
@@ -0,0 +1,598 @@
+/*
+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.
+*/
+
+/* Taken from http://smarden.sunsite.dk/runit/sv.8.html:
+
+sv - control and manage services monitored by runsv
+
+sv [-v] [-w sec] command services
+/etc/init.d/service [-w sec] command
+
+The sv program reports the current status and controls the state of services
+monitored by the runsv(8) supervisor.
+
+services consists of one or more arguments, each argument naming a directory
+service used by runsv(8). If service doesn't start with a dot or slash,
+it is searched in the default services directory /var/service/, otherwise
+relative to the current directory.
+
+command is one of up, down, status, once, pause, cont, hup, alarm, interrupt,
+1, 2, term, kill, or exit, or start, stop, restart, shutdown, force-stop,
+force-reload, force-restart, force-shutdown.
+
+The sv program can be sym-linked to /etc/init.d/ to provide an LSB init
+script interface. The service to be controlled then is specified by the
+base name of the "init script".
+
+status
+    Report the current status of the service, and the appendant log service
+    if available, to standard output.
+up
+    If the service is not running, start it. If the service stops, restart it.
+down
+    If the service is running, send it the TERM signal, and the CONT signal.
+    If ./run exits, start ./finish if it exists. After it stops, do not
+    restart service.
+once
+    If the service is not running, start it. Do not restart it if it stops.
+pause cont hup alarm interrupt quit 1 2 term kill
+    If the service is running, send it the STOP, CONT, HUP, ALRM, INT, QUIT,
+    USR1, USR2, TERM, or KILL signal respectively.
+exit
+    If the service is running, send it the TERM signal, and the CONT signal.
+    Do not restart the service. If the service is down, and no log service
+    exists, runsv(8) exits. If the service is down and a log service exists,
+    send the TERM signal to the log service. If the log service is down,
+    runsv(8) exits. This command is ignored if it is given to an appendant
+    log service.
+
+sv actually looks only at the first character of above commands.
+
+Commands compatible to LSB init script actions:
+
+status
+    Same as status.
+start
+    Same as up, but wait up to 7 seconds for the command to take effect.
+    Then report the status or timeout. If the script ./check exists in
+    the service directory, sv runs this script to check whether the service
+    is up and available; it's considered to be available if ./check exits
+    with 0.
+stop
+    Same as down, but wait up to 7 seconds for the service to become down.
+    Then report the status or timeout.
+restart
+    Send the commands term, cont, and up to the service, and wait up to
+    7 seconds for the service to restart. Then report the status or timeout.
+    If the script ./check exists in the service directory, sv runs this script
+    to check whether the service is up and available again; it's considered
+    to be available if ./check exits with 0.
+shutdown
+    Same as exit, but wait up to 7 seconds for the runsv(8) process
+    to terminate. Then report the status or timeout.
+force-stop
+    Same as down, but wait up to 7 seconds for the service to become down.
+    Then report the status, and on timeout send the service the kill command.
+force-reload
+    Send the service the term and cont commands, and wait up to
+    7 seconds for the service to restart. Then report the status,
+    and on timeout send the service the kill command.
+force-restart
+    Send the service the term, cont and up commands, and wait up to
+    7 seconds for the service to restart. Then report the status, and
+    on timeout send the service the kill command. If the script ./check
+    exists in the service directory, sv runs this script to check whether
+    the service is up and available again; it's considered to be available
+    if ./check exits with 0.
+force-shutdown
+    Same as exit, but wait up to 7 seconds for the runsv(8) process to
+    terminate. Then report the status, and on timeout send the service
+    the kill command.
+
+Additional Commands
+
+check
+    Check for the service to be in the state that's been requested. Wait up to
+    7 seconds for the service to reach the requested state, then report
+    the status or timeout. If the requested state of the service is up,
+    and the script ./check exists in the service directory, sv runs
+    this script to check whether the service is up and running;
+    it's considered to be up if ./check exits with 0.
+
+Options
+
+-v
+    wait up to 7 seconds for the command to take effect.
+    Then report the status or timeout.
+-w sec
+    Override the default timeout of 7 seconds with sec seconds. Implies -v.
+
+Environment
+
+SVDIR
+    The environment variable $SVDIR overrides the default services directory
+    /var/service.
+SVWAIT
+    The environment variable $SVWAIT overrides the default 7 seconds to wait
+    for a command to take effect. It is overridden by the -w option.
+
+Exit Codes
+    sv exits 0, if the command was successfully sent to all services, and,
+    if it was told to wait, the command has taken effect to all services.
+
+    For each service that caused an error (e.g. the directory is not
+    controlled by a runsv(8) process, or sv timed out while waiting),
+    sv increases the exit code by one and exits non zero. The maximum
+    is 99. sv exits 100 on error.
+*/
+
+/* Busyboxed by Denys Vlasenko <vda.linux@googlemail.com> */
+/* TODO: depends on runit_lib.c - review and reduce/eliminate */
+
+#include <sys/poll.h>
+#include <sys/file.h>
+#include "libbb.h"
+#include "runit_lib.h"
+
+struct globals {
+       const char *acts;
+       char **service;
+       unsigned rc;
+/* "Bernstein" time format: unix + 0x400000000000000aULL */
+       uint64_t tstart, tnow;
+       svstatus_t svstatus;
+};
+#define G (*(struct globals*)&bb_common_bufsiz1)
+#define acts         (G.acts        )
+#define service      (G.service     )
+#define rc           (G.rc          )
+#define tstart       (G.tstart      )
+#define tnow         (G.tnow        )
+#define svstatus     (G.svstatus    )
+#define INIT_G() do { } while (0)
+
+
+static void fatal_cannot(const char *m1) NORETURN;
+static void fatal_cannot(const char *m1)
+{
+       bb_perror_msg("fatal: can't %s", m1);
+       _exit(151);
+}
+
+static void out(const char *p, const char *m1)
+{
+       printf("%s%s: %s", p, *service, m1);
+       if (errno) {
+               printf(": %s", strerror(errno));
+       }
+       bb_putchar('\n'); /* will also flush the output */
+}
+
+#define WARN    "warning: "
+#define OK      "ok: "
+
+static void fail(const char *m1)
+{
+       ++rc;
+       out("fail: ", m1);
+}
+static void failx(const char *m1)
+{
+       errno = 0;
+       fail(m1);
+}
+static void warn(const char *m1)
+{
+       ++rc;
+       /* "warning: <service>: <m1>\n" */
+       out("warning: ", m1);
+}
+static void ok(const char *m1)
+{
+       errno = 0;
+       out(OK, m1);
+}
+
+static int svstatus_get(void)
+{
+       int fd, r;
+
+       fd = open_write("supervise/ok");
+       if (fd == -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);
+       fd = open_read("supervise/status");
+       if (fd == -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:
+               errno = 0;
+               warn("cannot read supervise/status: bad format");
+               return -1;
+       }
+       return 1;
+}
+
+static unsigned svstatus_print(const char *m)
+{
+       int diff;
+       int pid;
+       int normallyup = 0;
+       struct stat s;
+       uint64_t timestamp;
+
+       if (stat("down", &s) == -1) {
+               if (errno != ENOENT) {
+                       bb_perror_msg(WARN"cannot stat %s/down", *service);
+                       return 0;
+               }
+               normallyup = 1;
+       }
+       pid = SWAP_LE32(svstatus.pid_le32);
+       timestamp = SWAP_BE64(svstatus.time_be64);
+       if (pid) {
+               switch (svstatus.run_or_finish) {
+               case 1: printf("run: "); break;
+               case 2: printf("finish: "); break;
+               }
+               printf("%s: (pid %d) ", m, pid);
+       } else {
+               printf("down: %s: ", m);
+       }
+       diff = tnow - timestamp;
+       printf("%us", (diff < 0 ? 0 : diff));
+       if (pid) {
+               if (!normallyup) printf(", normally down");
+               if (svstatus.paused) printf(", paused");
+               if (svstatus.want == 'd') printf(", want down");
+               if (svstatus.got_term) printf(", got TERM");
+       } else {
+               if (normallyup) printf(", normally up");
+               if (svstatus.want == 'u') printf(", want up");
+       }
+       return pid ? 1 : 2;
+}
+
+static int status(const char *unused UNUSED_PARAM)
+{
+       int r;
+
+       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");
+       }
+       bb_putchar('\n'); /* 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; */
+       prog[0] = (char*)"./check";
+       prog[1] = NULL;
+       pid = spawn(prog);
+       if (pid <= 0) {
+               bb_perror_msg(WARN"cannot %s child %s/check", "run", *service);
+               return 0;
+       }
+       while (safe_waitpid(pid, &w, 0) == -1) {
+               bb_perror_msg(WARN"cannot %s child %s/check", "wait for", *service);
+               return 0;
+       }
+       return !wait_exitcode(w);
+}
+
+static int check(const char *a)
+{
+       int r;
+       unsigned pid;
+       uint64_t timestamp;
+
+       r = svstatus_get();
+       if (r == -1)
+               return -1;
+       if (r == 0) {
+               if (*a == 'x')
+                       return 1;
+               return -1;
+       }
+       pid = SWAP_LE32(svstatus.pid_le32);
+       switch (*a) {
+       case 'x':
+               return 0;
+       case 'u':
+               if (!pid || svstatus.run_or_finish != 1) return 0;
+               if (!checkscript()) return 0;
+               break;
+       case 'd':
+               if (pid) return 0;
+               break;
+       case 'c':
+               if (pid && !checkscript()) return 0;
+               break;
+       case 't':
+               if (!pid && svstatus.want == 'd') break;
+               timestamp = SWAP_BE64(svstatus.time_be64);
+               if ((tstart > timestamp) || !pid || svstatus.got_term || !checkscript())
+                       return 0;
+               break;
+       case 'o':
+               timestamp = SWAP_BE64(svstatus.time_be64);
+               if ((!pid && tstart > timestamp) || (pid && svstatus.want != 'd'))
+                       return 0;
+       }
+       printf(OK);
+       svstatus_print(*service);
+       bb_putchar('\n'); /* will also flush the output */
+       return 1;
+}
+
+static int control(const char *a)
+{
+       int fd, r;
+
+       if (svstatus_get() <= 0)
+               return -1;
+       if (svstatus.want == *a)
+               return 0;
+       fd = open_write("supervise/control");
+       if (fd == -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) MAIN_EXTERNALLY_VISIBLE;
+int sv_main(int argc, char **argv)
+{
+       unsigned opt;
+       unsigned i, want_exit;
+       char *x;
+       char *action;
+       const char *varservice = CONFIG_SV_DEFAULT_SERVICE_DIR;
+       unsigned services;
+       char **servicex;
+       unsigned waitsec = 7;
+       smallint kll = 0;
+       int verbose = 0;
+       int (*act)(const char*);
+       int (*cbk)(const char*);
+       int curdir;
+
+       INIT_G();
+
+       xfunc_error_retval = 100;
+
+       x = getenv("SVDIR");
+       if (x) varservice = x;
+       x = getenv("SVWAIT");
+       if (x) waitsec = xatou(x);
+
+       opt_complementary = "w+:vv"; /* -w N, -v is a counter */
+       opt = getopt32(argv, "w:v", &waitsec, &verbose);
+       argc -= optind;
+       argv += optind;
+       action = *argv++;
+       if (!action || !*argv) bb_show_usage();
+       service = argv;
+       services = argc - 1;
+
+       tnow = time(NULL) + 0x400000000000000aULL;
+       tstart = tnow;
+       curdir = open_read(".");
+       if (curdir == -1)
+               fatal_cannot("open current directory");
+
+       act = &control;
+       acts = "s";
+       cbk = &check;
+
+       switch (*action) {
+       case 'x':
+       case 'e':
+               acts = "x";
+               if (!verbose) cbk = NULL;
+               break;
+       case 'X':
+       case 'E':
+               acts = "x";
+               kll = 1;
+               break;
+       case 'D':
+               acts = "d";
+               kll = 1;
+               break;
+       case 'T':
+               acts = "tc";
+               kll = 1;
+               break;
+       case 'c':
+               if (str_equal(action, "check")) {
+                       act = NULL;
+                       acts = "c";
+                       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;
+               if (!verbose) cbk = NULL;
+               break;
+       case 's':
+               if (str_equal(action, "shutdown")) {
+                       acts = "x";
+                       break;
+               }
+               if (str_equal(action, "start")) {
+                       acts = "u";
+                       break;
+               }
+               if (str_equal(action, "stop")) {
+                       acts = "d";
+                       break;
+               }
+               /* "status" */
+               act = &status;
+               cbk = NULL;
+               break;
+       case 'r':
+               if (str_equal(action, "restart")) {
+                       acts = "tcu";
+                       break;
+               }
+               bb_show_usage();
+       case 'f':
+               if (str_equal(action, "force-reload")) {
+                       acts = "tc";
+                       kll = 1;
+                       break;
+               }
+               if (str_equal(action, "force-restart")) {
+                       acts = "tcu";
+                       kll = 1;
+                       break;
+               }
+               if (str_equal(action, "force-shutdown")) {
+                       acts = "x";
+                       kll = 1;
+                       break;
+               }
+               if (str_equal(action, "force-stop")) {
+                       acts = "d";
+                       kll = 1;
+                       break;
+               }
+       default:
+               bb_show_usage();
+       }
+
+       servicex = service;
+       for (i = 0; i < services; ++i) {
+               if ((**service != '/') && (**service != '.')) {
+                       if (chdir(varservice) == -1)
+                               goto chdir_failed_0;
+               }
+               if (chdir(*service) == -1) {
+ chdir_failed_0:
+                       fail("cannot change to service directory");
+                       goto nullify_service_0;
+               }
+               if (act && (act(acts) == -1)) {
+ nullify_service_0:
+                       *service = NULL;
+               }
+               if (fchdir(curdir) == -1)
+                       fatal_cannot("change to original directory");
+               service++;
+       }
+
+       if (cbk) while (1) {
+               int diff;
+
+               diff = tnow - tstart;
+               service = servicex;
+               want_exit = 1;
+               for (i = 0; i < services; ++i, ++service) {
+                       if (!*service)
+                               continue;
+                       if ((**service != '/') && (**service != '.')) {
+                               if (chdir(varservice) == -1)
+                                       goto chdir_failed;
+                       }
+                       if (chdir(*service) == -1) {
+ chdir_failed:
+                               fail("cannot change to service directory");
+                               goto nullify_service;
+                       }
+                       if (cbk(acts) != 0)
+                               goto nullify_service;
+                       want_exit = 0;
+                       if (diff >= waitsec) {
+                               printf(kll ? "kill: " : "timeout: ");
+                               if (svstatus_get() > 0) {
+                                       svstatus_print(*service);
+                                       ++rc;
+                               }
+                               bb_putchar('\n'); /* will also flush the output */
+                               if (kll)
+                                       control("k");
+ nullify_service:
+                               *service = NULL;
+                       }
+                       if (fchdir(curdir) == -1)
+                               fatal_cannot("change to original directory");
+               }
+               if (want_exit) break;
+               usleep(420000);
+               tnow = time(NULL) + 0x400000000000000aULL;
+       }
+       return rc > 99 ? 99 : rc;
+}
diff --git a/runit/svlogd.c b/runit/svlogd.c
new file mode 100644 (file)
index 0000000..9609fa3
--- /dev/null
@@ -0,0 +1,1056 @@
+/*
+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 Denys Vlasenko <vda.linux@googlemail.com> */
+/* TODO: depends on runit_lib.c - review and reduce/eliminate */
+
+#include <sys/poll.h>
+#include <sys/file.h>
+#include "libbb.h"
+#include "runit_lib.h"
+
+#define LESS(a,b) ((int)((unsigned)(b) - (unsigned)(a)) > 0)
+
+#define FMT_PTIME 30
+
+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;
+       unsigned rotate_period;
+       int ppid;
+       int fddir;
+       int fdcur;
+       FILE* filecur; ////
+       int fdlock;
+       unsigned next_rotate;
+       char fnsave[FMT_PTIME];
+       char match;
+       char matcherr;
+};
+
+
+struct globals {
+       struct logdir *dir;
+       unsigned verbose;
+       int linemax;
+       ////int buflen;
+       int linelen;
+
+       int fdwdir;
+       char **fndir;
+       int wstat;
+       unsigned nearest_rotate;
+
+       smallint exitasap;
+       smallint rotateasap;
+       smallint reopenasap;
+       smallint linecomplete;
+       smallint tmaxflag;
+
+       char repl;
+       const char *replace;
+       int fl_flag_0;
+       unsigned dirn;
+
+       sigset_t blocked_sigset;
+};
+#define G (*(struct globals*)ptr_to_globals)
+#define dir            (G.dir           )
+#define verbose        (G.verbose       )
+#define linemax        (G.linemax       )
+#define buflen         (G.buflen        )
+#define linelen        (G.linelen       )
+#define fndir          (G.fndir         )
+#define fdwdir         (G.fdwdir        )
+#define wstat          (G.wstat         )
+#define nearest_rotate (G.nearest_rotate)
+#define exitasap       (G.exitasap      )
+#define rotateasap     (G.rotateasap    )
+#define reopenasap     (G.reopenasap    )
+#define linecomplete   (G.linecomplete  )
+#define tmaxflag       (G.tmaxflag      )
+#define repl           (G.repl          )
+#define replace        (G.replace       )
+#define blocked_sigset (G.blocked_sigset)
+#define fl_flag_0      (G.fl_flag_0     )
+#define dirn           (G.dirn          )
+#define INIT_G() do { \
+       SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
+       linemax = 1000; \
+       /*buflen = 1024;*/ \
+       linecomplete = 1; \
+       replace = ""; \
+} while (0)
+
+#define line bb_common_bufsiz1
+
+
+#define FATAL "fatal: "
+#define WARNING "warning: "
+#define PAUSE "pausing: "
+#define INFO "info: "
+
+#define usage() bb_show_usage()
+static void fatalx(const char *m0)
+{
+       bb_error_msg_and_die(FATAL"%s", m0);
+}
+static void warn(const char *m0)
+{
+       bb_perror_msg(WARNING"%s", m0);
+}
+static void warn2(const char *m0, const char *m1)
+{
+       bb_perror_msg(WARNING"%s: %s", m0, m1);
+}
+static void warnx(const char *m0, const 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(const char *m0)
+{
+       bb_perror_msg(PAUSE"can't %s", m0);
+       sleep(3);
+}
+static void pause2cannot(const char *m0, const char *m1)
+{
+       bb_perror_msg(PAUSE"can't %s %s", m0, m1);
+       sleep(3);
+}
+
+static char* wstrdup(const char *str)
+{
+       char *s;
+       while (!(s = strdup(str)))
+               pause_nomem();
+       return s;
+}
+
+/*** ex fmt_ptime.[ch] ***/
+
+/* NUL terminated */
+static void fmt_time_human_30nul(char *s)
+{
+       struct tm *t;
+       struct timeval tv;
+
+       gettimeofday(&tv, NULL);
+       t = gmtime(&(tv.tv_sec));
+       sprintf(s, "%04u-%02u-%02u_%02u:%02u:%02u.%06u000",
+               (unsigned)(1900 + t->tm_year),
+               (unsigned)(t->tm_mon + 1),
+               (unsigned)(t->tm_mday),
+               (unsigned)(t->tm_hour),
+               (unsigned)(t->tm_min),
+               (unsigned)(t->tm_sec),
+               (unsigned)(tv.tv_usec)
+       );
+       /* 4+1 + 2+1 + 2+1 + 2+1 + 2+1 + 2+1 + 9 = */
+       /* 5   + 3   + 3   + 3   + 3   + 3   + 9 = */
+       /* 20 (up to '.' inclusive) + 9 (not including '\0') */
+}
+
+/* NOT terminated! */
+static void fmt_time_bernstein_25(char *s)
+{
+       uint32_t pack[3];
+       struct timeval tv;
+       unsigned sec_hi;
+
+       gettimeofday(&tv, NULL);
+       sec_hi = (0x400000000000000aULL + tv.tv_sec) >> 32;
+       tv.tv_sec = (time_t)(0x400000000000000aULL) + tv.tv_sec;
+       tv.tv_usec *= 1000;
+       /* Network order is big-endian: most significant byte first.
+        * This is exactly what we want here */
+       pack[0] = htonl(sec_hi);
+       pack[1] = htonl(tv.tv_sec);
+       pack[2] = htonl(tv.tv_usec);
+       *s++ = '@';
+       bin2hex(s, (char*)pack, 12);
+}
+
+static void processorstart(struct logdir *ld)
+{
+       char sv_ch;
+       int pid;
+
+       if (!ld->processor) return;
+       if (ld->ppid) {
+               warnx("processor already running", ld->name);
+               return;
+       }
+
+       /* vfork'ed child trashes this byte, save... */
+       sv_ch = ld->fnsave[26];
+
+       while ((pid = vfork()) == -1)
+               pause2cannot("vfork for processor", ld->name);
+       if (!pid) {
+               char *prog[4];
+               int fd;
+
+               /* child */
+               /* Non-ignored signals revert to SIG_DFL on exec anyway */
+               /*bb_signals(0
+                       + (1 << SIGTERM)
+                       + (1 << SIGALRM)
+                       + (1 << SIGHUP)
+                       , SIG_DFL);*/
+               sig_unblock(SIGTERM);
+               sig_unblock(SIGALRM);
+               sig_unblock(SIGHUP);
+
+               if (verbose)
+                       bb_error_msg(INFO"processing: %s/%s", ld->name, ld->fnsave);
+               fd = xopen(ld->fnsave, O_RDONLY|O_NDELAY);
+               xmove_fd(fd, 0);
+               ld->fnsave[26] = 't'; /* <- that's why we need sv_ch! */
+               fd = xopen(ld->fnsave, O_WRONLY|O_NDELAY|O_TRUNC|O_CREAT);
+               xmove_fd(fd, 1);
+               fd = open_read("state");
+               if (fd == -1) {
+                       if (errno != ENOENT)
+                               bb_perror_msg_and_die(FATAL"can't %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);
+               }
+               xmove_fd(fd, 4);
+               fd = xopen("newstate", O_WRONLY|O_NDELAY|O_TRUNC|O_CREAT);
+               xmove_fd(fd, 5);
+
+// getenv("SHELL")?
+               prog[0] = (char*)"sh";
+               prog[1] = (char*)"-c";
+               prog[2] = ld->processor;
+               prog[3] = NULL;
+               execv("/bin/sh", prog);
+               bb_perror_msg_and_die(FATAL"can't %s processor %s", "run", ld->name);
+       }
+       ld->fnsave[26] = sv_ch; /* ...restore */
+       ld->ppid = pid;
+}
+
+static unsigned processorstop(struct logdir *ld)
+{
+       char f[28];
+
+       if (ld->ppid) {
+               sig_unblock(SIGHUP);
+               while (safe_waitpid(ld->ppid, &wstat, 0) == -1)
+                       pause2cannot("wait for processor", ld->name);
+               sig_block(SIGHUP);
+               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"can't 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("can't 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("can't 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("can't unlink oldest logfile", ld->name);
+       }
+}
+
+static unsigned rotate(struct logdir *ld)
+{
+       struct stat st;
+       unsigned now;
+
+       if (ld->fddir == -1) {
+               ld->rotate_period = 0;
+               return 0;
+       }
+       if (ld->ppid)
+               while (!processorstop(ld))
+                       continue;
+
+       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 {
+               fmt_time_bernstein_25(ld->fnsave);
+               errno = 0;
+               stat(ld->fnsave, &st);
+       } while (errno != ENOENT);
+
+       now = monotonic_sec();
+       if (ld->rotate_period && LESS(ld->next_rotate, now)) {
+               ld->next_rotate = now + ld->rotate_period;
+               if (LESS(ld->next_rotate, nearest_rotate))
+                       nearest_rotate = ld->next_rotate;
+       }
+
+       if (ld->size > 0) {
+               while (fflush(ld->filecur) || 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);
+               fclose(ld->filecur);
+
+               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);
+               /* we presume this cannot fail */
+               ld->filecur = fdopen(ld->fdcur, "a"); ////
+               setvbuf(ld->filecur, NULL, _IOFBF, linelen); ////
+               close_on_exec_on(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 (1) {
+               ////i = full_write(ld->fdcur, s, len);
+               ////if (i != -1) break;
+               i = fwrite(s, 1, len, ld->filecur);
+               if (i == len) break;
+
+               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("can't 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("can't 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 (fflush(ld->filecur) || 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);
+       fclose(ld->filecur);
+       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];
+       unsigned now;
+       char *new, *s, *np;
+       int i;
+       struct stat st;
+
+       now = monotonic_sec();
+
+       ld->fddir = open(fn, O_RDONLY|O_NDELAY);
+       if (ld->fddir == -1) {
+               warn2("can't open log directory", (char*)fn);
+               return 0;
+       }
+       close_on_exec_on(ld->fddir);
+       if (fchdir(ld->fddir) == -1) {
+               logdir_close(ld);
+               warn2("can't 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("can't lock directory", (char*)fn);
+               while (fchdir(fdwdir) == -1)
+                       pause1cannot("change to initial working directory");
+               return 0;
+       }
+       close_on_exec_on(ld->fdlock);
+
+       ld->size = 0;
+       ld->sizemax = 1000000;
+       ld->nmax = ld->nmin = 10;
+       ld->rotate_period = 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 && errno != ENOENT)
+               bb_perror_msg(WARNING"%s/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':
+                               /* Add '\n'-terminated line to ld->inst */
+                               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 },
+                                       { }
+                               };
+                               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 },*/
+                                       { }
+                               };
+                               ld->rotate_period = xatou_sfx(&s[1], mh_suffixes);
+                               if (ld->rotate_period) {
+                                       ld->next_rotate = now + ld->rotate_period;
+                                       if (!tmaxflag || LESS(ld->next_rotate, nearest_rotate))
+                                               nearest_rotate = ld->next_rotate;
+                                       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 {
+                               fmt_time_bernstein_25(ld->fnsave);
+                               errno = 0;
+                               stat(ld->fnsave, &st);
+                       } while (errno != ENOENT);
+                       while (rename("current", ld->fnsave) == -1)
+                               pause2cannot("rename current", ld->name);
+                       rmoldest(ld);
+                       i = -1;
+               } else {
+                       /* st.st_size can be not just bigger, but WIDER!
+                        * This code is safe: if st.st_size > 4GB, we select
+                        * ld->sizemax (because it's "unsigned") */
+                       ld->size = (st.st_size > ld->sizemax) ? ld->sizemax : st.st_size;
+               }
+       } else {
+               if (errno != ENOENT) {
+                       logdir_close(ld);
+                       warn2("can't 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);
+       /* we presume this cannot fail */
+       ld->filecur = fdopen(ld->fdcur, "a"); ////
+       setvbuf(ld->filecur, NULL, _IOFBF, linelen); ////
+
+       close_on_exec_on(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)
+{
+       int l;
+       int ok = 0;
+
+       tmaxflag = 0;
+       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");
+}
+
+/* Will look good in libbb one day */
+static ssize_t ndelay_read(int fd, void *buf, size_t count)
+{
+       if (!(fl_flag_0 & O_NONBLOCK))
+               fcntl(fd, F_SETFL, fl_flag_0 | O_NONBLOCK);
+       count = safe_read(fd, buf, count);
+       if (!(fl_flag_0 & O_NONBLOCK))
+               fcntl(fd, F_SETFL, fl_flag_0);
+       return count;
+}
+
+/* Used for reading stdin */
+static int buffer_pread(/*int fd, */char *s, unsigned len)
+{
+       unsigned now;
+       struct pollfd input;
+       int i;
+
+       input.fd = 0;
+       input.events = POLLIN;
+
+       do {
+               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;
+               }
+               now = monotonic_sec();
+               nearest_rotate = now + (45 * 60 + 45);
+               for (i = 0; i < dirn; ++i) {
+                       if (dir[i].rotate_period) {
+                               if (LESS(dir[i].next_rotate, now))
+                                       rotate(dir + i);
+                               if (LESS(dir[i].next_rotate, nearest_rotate))
+                                       nearest_rotate = dir[i].next_rotate;
+                       }
+               }
+
+               sigprocmask(SIG_UNBLOCK, &blocked_sigset, NULL);
+               i = nearest_rotate - now;
+               if (i > 1000000)
+                       i = 1000000;
+               if (i <= 0)
+                       i = 1;
+               poll(&input, 1, i * 1000);
+               sigprocmask(SIG_BLOCK, &blocked_sigset, NULL);
+
+               i = ndelay_read(STDIN_FILENO, s, len);
+               if (i >= 0)
+                       break;
+               if (errno == EINTR)
+                       continue;
+               if (errno != EAGAIN) {
+                       warn("can't read standard input");
+                       break;
+               }
+               /* else: EAGAIN - normal, repeat silently */
+       } while (!exitasap);
+
+       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 UNUSED_PARAM)
+{
+       if (verbose)
+               bb_error_msg(INFO"sig%s received", "term");
+       exitasap = 1;
+}
+
+static void sig_child_handler(int sig_no UNUSED_PARAM)
+{
+       pid_t pid;
+       int l;
+
+       if (verbose)
+               bb_error_msg(INFO"sig%s received", "child");
+       while ((pid = wait_any_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 UNUSED_PARAM)
+{
+       if (verbose)
+               bb_error_msg(INFO"sig%s received", "alarm");
+       rotateasap = 1;
+}
+
+static void sig_hangup_handler(int sig_no UNUSED_PARAM)
+{
+       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) MAIN_EXTERNALLY_VISIBLE;
+int svlogd_main(int argc, char **argv)
+{
+       char *r,*l,*b;
+       ssize_t stdin_cnt = 0;
+       int i;
+       unsigned opt;
+       unsigned timestamp = 0;
+       void* (*memRchr)(const void *, int, size_t) = memchr;
+
+       INIT_G();
+
+       opt_complementary = "tt:vv";
+       opt = getopt32(argv, "r:R:l:b:tv",
+                       &r, &replace, &l, &b, &timestamp, &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, BUFSIZ-26);
+               if (linemax == 0) linemax = BUFSIZ-26;
+               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);
+       close_on_exec_on(fdwdir);
+       dir = xzalloc(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;
+       /* We cannot set NONBLOCK on fd #0 permanently - this setting
+        * _isn't_ per-process! It is shared among all other processes
+        * with the same stdin */
+       fl_flag_0 = fcntl(0, F_GETFL);
+
+       sigemptyset(&blocked_sigset);
+       sigaddset(&blocked_sigset, SIGTERM);
+       sigaddset(&blocked_sigset, SIGCHLD);
+       sigaddset(&blocked_sigset, SIGALRM);
+       sigaddset(&blocked_sigset, SIGHUP);
+       sigprocmask(SIG_BLOCK, &blocked_sigset, NULL);
+       bb_signals_recursive_norestart(1 << SIGTERM, sig_term_handler);
+       bb_signals_recursive_norestart(1 << SIGCHLD, sig_child_handler);
+       bb_signals_recursive_norestart(1 << SIGALRM, sig_alarm_handler);
+       bb_signals_recursive_norestart(1 << SIGHUP, sig_hangup_handler);
+
+       logdirs_reopen();
+
+       /* Without timestamps, we don't have to print each line
+        * separately, so we can look for _last_ newline, not first,
+        * thus batching writes */
+       if (!timestamp)
+               memRchr = memrchr;
+
+       setvbuf(stderr, NULL, _IOFBF, linelen);
+
+       /* Each iteration processes one or more lines */
+       while (1) {
+               char stamp[FMT_PTIME];
+               char *lineptr;
+               char *printptr;
+               char *np;
+               int printlen;
+               char ch;
+
+               lineptr = line;
+               if (timestamp)
+                       lineptr += 26;
+
+               /* lineptr[0..linemax-1] - buffer for stdin */
+               /* (possibly has some unprocessed data from prev loop) */
+
+               /* Refill the buffer if needed */
+               np = memRchr(lineptr, '\n', stdin_cnt);
+               if (!np && !exitasap) {
+                       i = linemax - stdin_cnt; /* avail. bytes at tail */
+                       if (i >= 128) {
+                               i = buffer_pread(/*0, */lineptr + stdin_cnt, i);
+                               if (i <= 0) /* EOF or error on stdin */
+                                       exitasap = 1;
+                               else {
+                                       np = memRchr(lineptr + stdin_cnt, '\n', i);
+                                       stdin_cnt += i;
+                               }
+                       }
+               }
+               if (stdin_cnt <= 0 && exitasap)
+                       break;
+
+               /* Search for '\n' (in fact, np already holds the result) */
+               linelen = stdin_cnt;
+               if (np) {
+ print_to_nl:          /* NB: starting from here lineptr may point
+                        * farther out into line[] */
+                       linelen = np - lineptr + 1;
+               }
+               /* linelen == no of chars incl. '\n' (or == stdin_cnt) */
+               ch = lineptr[linelen-1];
+
+               /* Biggest performance hit was coming from the fact
+                * that we did not buffer writes. We were reading many lines
+                * in one read() above, but wrote one line per write().
+                * We are using stdio to fix that */
+
+               /* write out lineptr[0..linelen-1] to each log destination
+                * (or lineptr[-26..linelen-1] if timestamping) */
+               printlen = linelen;
+               printptr = lineptr;
+               if (timestamp) {
+                       if (timestamp == 1)
+                               fmt_time_bernstein_25(stamp);
+                       else /* 2: */
+                               fmt_time_human_30nul(stamp);
+                       printlen += 26;
+                       printptr -= 26;
+                       memcpy(printptr, stamp, 25);
+                       printptr[25] = ' ';
+               }
+               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') {
+                               /* runit-1.8.0 compat: if timestamping, do it on stderr too */
+                               ////full_write(STDERR_FILENO, printptr, printlen);
+                               fwrite(printptr, 1, printlen, stderr);
+                       }
+                       if (ld->match != '+') continue;
+                       buffer_pwrite(i, printptr, 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 */
+                               exitasap = 1;
+                               lineptr[0] = ch = '\n';
+                               linelen = 1;
+                               stdin_cnt = 1;
+                       } else {
+                               linelen = stdin_cnt;
+                               np = memRchr(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(STDERR_FILENO, lineptr, linelen);
+                                       fwrite(lineptr, 1, linelen, stderr);
+                               }
+                               if (dir[i].match != '+') continue;
+                               buffer_pwrite(i, lineptr, linelen);
+                       }
+               }
+
+               stdin_cnt -= linelen;
+               if (stdin_cnt > 0) {
+                       lineptr += linelen;
+                       /* If we see another '\n', we don't need to read
+                        * next piece of input: can print what we have */
+                       np = memRchr(lineptr, '\n', stdin_cnt);
+                       if (np)
+                               goto print_to_nl;
+                       /* Move unprocessed data to the front of line */
+                       memmove((timestamp ? line+26 : line), lineptr, stdin_cnt);
+               }
+               fflush(NULL);////
+       }
+
+       for (i = 0; i < dirn; ++i) {
+               if (dir[i].ppid)
+                       while (!processorstop(&dir[i]))
+                               /* repeat */;
+               logdir_close(&dir[i]);
+       }
+       return 0;
+}
diff --git a/scripts/Kbuild b/scripts/Kbuild
new file mode 100644 (file)
index 0000000..83b4232
--- /dev/null
@@ -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 (file)
index 0000000..6ec1809
--- /dev/null
@@ -0,0 +1,154 @@
+####
+# 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 ;)
+
+# hostcc-option
+# Usage: hostcflags-y += $(call hostcc-option, -march=winchip-c6, -march=i586)
+
+hostcc-option = $(shell if $(HOSTCC) $(HOSTCFLAGS) $(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 PATH="$(PATH)" $(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.IMA b/scripts/Makefile.IMA
new file mode 100644 (file)
index 0000000..a34db50
--- /dev/null
@@ -0,0 +1,207 @@
+# This is completely unsupported.
+#
+# Uasge: make -f scripts/Makefile.IMA
+#
+# Fix COMBINED_COMPILE upstream (in the Kbuild) and propagate
+# the changes back
+srctree                := $(if $(KBUILD_SRC),$(KBUILD_SRC),$(CURDIR))
+objtree                := $(CURDIR)
+src            := $(srctree)
+obj            := $(objtree)
+
+# Look for make include files relative to root of kernel src
+MAKEFLAGS += --include-dir=$(srctree)
+
+default: busybox
+
+include .config
+
+# Cross compiling and selecting different set of gcc/bin-utils
+ifeq ($(CROSS_COMPILE),)
+CROSS_COMPILE := $(subst ",,$(CONFIG_CROSS_COMPILER_PREFIX))
+endif
+
+ifneq ($(CROSS_COMPILE),)
+SUBARCH := $(shell echo $(CROSS_COMPILE) | cut -d- -f1)
+else
+SUBARCH := $(shell uname -m)
+endif
+SUBARCH := $(shell echo $(SUBARCH) | 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/ )
+ARCH ?= $(SUBARCH)
+
+ifndef HOSTCC
+HOSTCC = cc
+endif
+AS              = $(CROSS_COMPILE)as
+CC              = $(CROSS_COMPILE)gcc
+LD              = $(CC) -nostdlib
+CPP             = $(CC) -E
+AR              = $(CROSS_COMPILE)ar
+NM              = $(CROSS_COMPILE)nm
+STRIP           = $(CROSS_COMPILE)strip
+OBJCOPY         = $(CROSS_COMPILE)objcopy
+OBJDUMP         = $(CROSS_COMPILE)objdump
+
+CFLAGS   := $(CFLAGS)
+CPPFLAGS += -D"KBUILD_STR(s)=\#s" #-Q
+
+# We need some generic definitions
+include $(srctree)/scripts/Kbuild.include
+
+include Makefile.flags
+
+-include $(srctree)/arch/$(ARCH)/Makefile
+ifdef CONFIG_FEATURE_COMPRESS_USAGE
+usage_stuff = include/usage_compressed.h
+endif
+
+ifndef BB_VER
+BB_VER:=""
+endif
+
+WHOLE_PROGRAM:=$(call cc-option,-fwhole-program,)
+
+# pull in the config stuff
+lib-all-y := applets/applets.o
+lib-y:=
+include procps/Kbuild
+lib-all-y += $(patsubst %,procps/%,$(sort $(lib-y)))
+lib-y:=
+include networking/Kbuild
+lib-all-y += $(patsubst %,networking/%,$(sort $(lib-y)))
+lib-y:=
+include networking/udhcp/Kbuild
+lib-all-y += $(patsubst %,networking/udhcp/%,$(sort $(lib-y)))
+lib-y:=
+include networking/libiproute/Kbuild
+lib-all-y += $(patsubst %,networking/libiproute/%,$(sort $(lib-y)))
+lib-y:=
+include loginutils/Kbuild
+lib-all-y += $(patsubst %,loginutils/%,$(sort $(lib-y)))
+lib-y:=
+include archival/Kbuild
+lib-all-y += $(patsubst %,archival/%,$(sort $(lib-y)))
+lib-y:=
+include archival/libunarchive/Kbuild
+lib-all-y += $(patsubst %,archival/libunarchive/%,$(sort $(lib-y)))
+lib-y:=
+include applets/Kbuild
+lib-all-y += $(patsubst %,applets/%,$(sort $(lib-y)))
+lib-y:=
+include e2fsprogs/Kbuild
+lib-all-y += $(patsubst %,e2fsprogs/%,$(sort $(lib-y)))
+lib-y:=
+#include e2fsprogs/old_e2fsprogs/Kbuild
+#lib-all-y += $(patsubst %,e2fsprogs/old_e2fsprogs/%,$(sort $(lib-y)))
+#lib-y:=
+#include e2fsprogs/old_e2fsprogs/ext2fs/Kbuild
+#lib-all-y += $(patsubst %,e2fsprogs/old_e2fsprogs/ext2fs/%,$(sort $(lib-y)))
+#lib-y:=
+#include e2fsprogs/old_e2fsprogs/blkid/Kbuild
+#lib-all-y += $(patsubst %,e2fsprogs/old_e2fsprogs/blkid/%,$(sort $(lib-y)))
+#lib-y:=
+#include e2fsprogs/old_e2fsprogs/uuid/Kbuild
+#lib-all-y += $(patsubst %,e2fsprogs/old_e2fsprogs/uuid/%,$(sort $(lib-y)))
+#lib-y:=
+#include e2fsprogs/old_e2fsprogs/e2p/Kbuild
+#lib-all-y += $(patsubst %,e2fsprogs/old_e2fsprogs/e2p/%,$(sort $(lib-y)))
+#lib-y:=
+include debianutils/Kbuild
+lib-all-y += $(patsubst %,debianutils/%,$(sort $(lib-y)))
+lib-y:=
+include runit/Kbuild
+lib-all-y += $(patsubst %,runit/%,$(sort $(lib-y)))
+lib-y:=
+include modutils/Kbuild
+lib-all-y += $(patsubst %,modutils/%,$(sort $(lib-y)))
+lib-y:=
+include miscutils/Kbuild
+lib-all-y += $(patsubst %,miscutils/%,$(sort $(lib-y)))
+lib-y:=
+include coreutils/libcoreutils/Kbuild
+lib-all-y += $(patsubst %,coreutils/libcoreutils/%,$(sort $(lib-y)))
+lib-y:=
+include coreutils/Kbuild
+lib-all-y += $(patsubst %,coreutils/%,$(sort $(lib-y)))
+lib-y:=
+include sysklogd/Kbuild
+lib-all-y += $(patsubst %,sysklogd/%,$(sort $(lib-y)))
+lib-y:=
+include shell/Kbuild
+lib-all-y += $(patsubst %,shell/%,$(sort $(lib-y)))
+lib-y:=
+include console-tools/Kbuild
+lib-all-y += $(patsubst %,console-tools/%,$(sort $(lib-y)))
+lib-y:=
+include findutils/Kbuild
+lib-all-y += $(patsubst %,findutils/%,$(sort $(lib-y)))
+lib-y:=
+include util-linux/Kbuild
+lib-all-y += $(patsubst %,util-linux/%,$(sort $(lib-y)))
+lib-y:=
+include util-linux/volume_id/Kbuild
+lib-all-y += $(patsubst %,util-linux/volume_id/%,$(sort $(lib-y)))
+lib-y:=
+include init/Kbuild
+lib-all-y += $(patsubst %,init/%,$(sort $(lib-y)))
+lib-y:=
+include libpwdgrp/Kbuild
+lib-all-y += $(patsubst %,libpwdgrp/%,$(sort $(lib-y)))
+lib-y:=
+include editors/Kbuild
+lib-all-y += $(patsubst %,editors/%,$(sort $(lib-y)))
+lib-y:=
+include printutils/Kbuild
+lib-all-y += $(patsubst %,printutils/%,$(sort $(lib-y)))
+lib-y:=
+include selinux/Kbuild
+lib-all-y += $(patsubst %,selinux/%,$(sort $(lib-y)))
+lib-y:=
+include scripts/Kbuild
+lib-all-y += $(patsubst %,scripts/%,$(sort $(lib-y)))
+lib-y:=
+include libbb/Kbuild
+lib-all-y += $(patsubst %,libbb/%,$(sort $(lib-y)))
+lib-y:=
+
+comma:=,
+busybox_unstripped.o: $(usage_stuff) include/applet_tables.h include/autoconf.h
+       $(CC) $(CPPFLAGS) $(CFLAGS) $(EXTRA_CFLAGS) \
+               $(patsubst %,-Wl$(comma)%,$(LDFLAGS) $(EXTRA_LDFLAGS)) \
+               -DGCC_COMBINE=1 \
+               --combine $(WHOLE_PROGRAM) \
+               -funit-at-a-time -Wno-error -std=gnu99  \
+               -c -o busybox_unstripped.o \
+               $(lib-all-y:.o=.c)
+
+busybox: busybox_unstripped.o
+       $(srctree)/scripts/trylink \
+               busybox_unstripped \
+               "$(CC) $(CFLAGS_busybox)" \
+               "$(CFLAGS)" \
+               "$(LDFLAGS)" \
+               "busybox_unstripped.o" \
+               "" \
+               "crypt m"
+       cp -f $(@)_unstripped $@
+       -$(STRIP) -s -R .note -R .comment -R .version $@
+
+# If .config is newer than include/autoconf.h, someone tinkered
+# with it and forgot to run make oldconfig.
+include/autoconf.h: .config
+       $(MAKE) -f $(srctree)/Makefile silentoldconfig
+
+applets/usage: include/autoconf.h
+       $(HOSTCC) -Wall -Wstrict-prototypes -O2 -fomit-frame-pointer -I$(srctree)/include -o applets/usage applets/usage.c
+
+applets/applet_tables: include/autoconf.h
+       $(HOSTCC) -Wall -Wstrict-prototypes -O2 -fomit-frame-pointer -I$(srctree)/include -o applets/applet_tables applets/applet_tables.c
+
+include/usage_compressed.h: $(srctree)/include/usage.h applets/usage
+       $(srctree)/applets/usage_compressed include/usage_compressed.h applets
+
+include/applet_tables.h: $(srctree)/include/applets.h
+       applets/applet_tables include/applet_tables.h
diff --git a/scripts/Makefile.build b/scripts/Makefile.build
new file mode 100644 (file)
index 0000000..f343818
--- /dev/null
@@ -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_<file>.o from <file>.c
+# o if .tmp_<file>.o doesn't contain a __ksymtab version, i.e. does
+#   not export symbols, we just rename .tmp_<file>.o to <file>.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 <file>.o from .tmp_<file>.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:
+#    <composite-object>-objs := <list of .o files>
+#  or
+#    <composite-object>-y    := <list of .o files>
+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 (file)
index 0000000..cff3349
--- /dev/null
@@ -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 (file)
index 0000000..23bd9ff
--- /dev/null
@@ -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 (file)
index 0000000..be679b6
--- /dev/null
@@ -0,0 +1,172 @@
+# 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)
+
+# Seems to be a wrong thing to do. LDFLAGS contains gcc's flags,
+# yet ld_flags is fed to ld.
+#ld_flags       = $(LDFLAGS) $(EXTRA_LDFLAGS)
+# Remove the -Wl, prefix from linker options normally passed through gcc
+ld_flags       = $(filter-out -Wl$(comma)%,$(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
+# ---------------------------------------------------------------------------
+
+# TODO: LDFLAGS usually is supposed to contain gcc's flags, not ld's.
+# but here we feed them to ld!
+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 (file)
index 0000000..119f079
--- /dev/null
@@ -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 (file)
index 0000000..e178d72
--- /dev/null
@@ -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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <unistd.h>
+#include <limits.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+/* 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("<!-- %s -->\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 (file)
index 0000000..811d48b
--- /dev/null
@@ -0,0 +1,404 @@
+/*
+ * "Optimize" a list of dependencies as spit out by gcc -MD
+ * for the kernel build
+ * ===========================================================================
+ *
+ * Author       Kai Germaschewski
+ * Copyright    2002 by Kai Germaschewski  <kai.germaschewski@gmx.de>
+ *
+ * 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 <linux/config.h> which then again
+ * includes <linux/autoconf.h>
+ *
+ * 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, <mailto:mec@shout.net>.
+ *   This is a C version of syncdep.pl by Werner Almesberger.
+ *
+ *
+ * It is invoked as
+ *
+ *   fixdep <depfile> <target> <cmdline>
+ *
+ * and will read the dependency file <depfile>
+ *
+ * The transformed dependency snipped is written to stdout.
+ *
+ * It first generates a line
+ *
+ *   cmd_<target> = <cmdline>
+ *
+ * and then basically copies the .<target>.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 <sys/types.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <limits.h>
+#include <ctype.h>
+#include <arpa/inet.h>
+
+/* 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 <depfile> <target> <cmdline>\n");
+       exit(1);
+}
+
+/*
+ * Print out the commandline prefixed with cmd_<target filename> :=
+ */
+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_4 = map + len - 4; /* 4 == length of "USE_" */
+       char *end_7 = map + len - 7;
+       char *p = map;
+       char *q;
+       int off;
+
+       for (; p < end_4; p++) {
+               if (p < end_7 && p[6] == '_') {
+                       if (!memcmp(p, "CONFIG", 6)) goto conf7;
+                       if (!memcmp(p, "ENABLE", 6)) goto conf7;
+               }
+               /* We have at least 5 chars: for() has
+                * "p < end-4", not "p <= end-4"
+                * therefore we don't need to check p <= end-5 here */
+               if (p[4] == '_')
+                       if (!memcmp(p, "SKIP", 4)) goto conf5;
+               /* Ehhh, gcc is too stupid to just compare it as 32bit int */
+               if (p[0] == 'U')
+                       if (!memcmp(p, "USE_", 4)) goto conf4;
+               continue;
+
+       conf4:  off = 4;
+       conf5:  off = 5;
+       conf7:  off = 7;
+               p += off;
+               for (q = p; q < end_4+4; q++) {
+                       if (!(isalnum(*q) || *q == '_'))
+                               break;
+               }
+               use_config(p, q-p);
+       }
+}
+
+/* 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 = memchr(m, ':', len);
+       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 (file)
index 0000000..60934b5
--- /dev/null
@@ -0,0 +1,226 @@
+/*
+ * split-include.c
+ *
+ * Copyright abandoned, Michael Chastain, <mailto:mec@shout.net>.
+ * 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 <sys/stat.h>
+#include <sys/types.h>
+
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#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/bb_release b/scripts/bb_release
new file mode 100755 (executable)
index 0000000..8aa3804
--- /dev/null
@@ -0,0 +1,34 @@
+#!/bin/sh
+
+# Create signed release tarballs and signature files from current svn.
+# Since you don't have my gpg key, this doesn't do you much good,
+# but if I get hit by a bus the next maintainer might find this useful.
+# Run this in an empty directory.  The VERSION= line can get confused
+# otherwise.
+
+#svn co svn://busybox.net/trunk/busybox
+cd busybox || { echo "cd busybox failed"; exit 1; }
+make release || { echo "make release failed"; exit 1; }
+cd ..
+
+VERSION=`ls busybox-*.tar.gz | sed 's/busybox-\(.*\)\.tar\.gz/\1/'`
+
+zcat busybox-$VERSION.tar.gz | bzip2 > busybox-$VERSION.tar.bz2
+
+test -f busybox-$VERSION.tar.gz || { echo "no busybox-$VERSION.tar.gz"; exit 1; }
+test -f busybox-$VERSION.tar.bz2 || { echo "no busybox-$VERSION.tar.bz2"; exit 1; }
+
+signit()
+{
+echo "$1 released `date -r $1 -R`
+
+MD5:  `md5sum $1`
+SHA1: `sha1sum $1`
+
+To verify this signature, you can obtain my public key
+from http://busybox.net/~vda/vda_pubkey.gpg
+" | gpg --clearsign > "$1.sign"
+}
+
+signit busybox-$VERSION.tar.gz
+signit busybox-$VERSION.tar.bz2
diff --git a/scripts/bloat-o-meter b/scripts/bloat-o-meter
new file mode 100755 (executable)
index 0000000..02e8c8a
--- /dev/null
@@ -0,0 +1,80 @@
+#!/usr/bin/python
+#
+# Copyright 2004 Matt Mackall <mpm@selenic.com>
+#
+# 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
+
+def usage():
+    sys.stderr.write("usage: %s file1 file2\n" % sys.argv[0])
+    sys.exit(-1)
+
+if len(sys.argv) < 3:
+    usage()
+
+for f in sys.argv[1], sys.argv[2]:
+    if not os.path.exists(f):
+        sys.stderr.write("Error: file '%s' does not exist\n" % f)
+        usage()
+
+nm_args = " ".join([x for x in sys.argv[3:]])
+def getsizes(file):
+    sym = {}
+    for l in os.popen("nm --size-sort %s %s" % (nm_args, file)).readlines():
+       l = l.strip()
+       # Skip empty lines
+        if not len(l): continue
+       # Skip archive members
+        if len(l.split()) == 1 and l.endswith(':'):
+          continue
+        size, type, name = l.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 (executable)
index 0000000..2468db2
--- /dev/null
@@ -0,0 +1,40 @@
+#!/usr/bin/awk -f
+# AWK script to check for missing help entries for config options
+#
+# Copyright (C) 2006 Bernhard Reutner-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/checkstack.pl b/scripts/checkstack.pl
new file mode 100755 (executable)
index 0000000..55cdd78
--- /dev/null
@@ -0,0 +1,141 @@
+#!/usr/bin/perl
+
+# Stolen from Linux kernel :)
+
+#      Check the stack usage of functions
+#
+#      Copyright Joern Engel <joern@wh.fh-wedel.de>
+#      Inspired by Linus Torvalds
+#      Original idea maybe from Keith Owens
+#      s390 port and big speedup by Arnd Bergmann <arnd@bergmann-dalldorf.de>
+#      Mips port by Juan Quintela <quintela@mandrakesoft.com>
+#      IA64 port via Andreas Dilger
+#      Arm port by Holger Schurig
+#      sh64 port by Paul Mundt
+#      Random bits by Matt Mackall <mpm@selenic.com>
+#      M68k port by Geert Uytterhoeven and Andreas Schwab
+#
+#      Usage:
+#      objdump -d vmlinux | checkstack.pl [arch]
+#
+#      TODO :  Port to all architectures (one regex per arch)
+
+# check for arch
+#
+# $re is used for two matches:
+# $& (whole re) matches the complete objdump line with the stack growth
+# $1 (first bracket) matches the size of the stack growth
+#
+# use anything else and feel the pain ;)
+my (@stack, $re, $x, $xs);
+{
+       my $arch = shift;
+       if ($arch eq "") {
+               $arch = `uname -m`;
+       }
+
+       $x      = "[0-9a-f]";   # hex character
+       $xs     = "[0-9a-f ]";  # hex character or space
+       if ($arch eq 'arm') {
+               #c0008ffc:      e24dd064        sub     sp, sp, #100    ; 0x64
+               $re = qr/.*sub.*sp, sp, #(([0-9]{2}|[3-9])[0-9]{2})/o;
+       } elsif ($arch eq 'blackfin') {
+               #      52:       00 e8 03 00     LINK 0xc;
+               $re = qr/.*LINK (0x$x{1,5});$/o;
+       } elsif ($arch =~ /^i[3456]86$/) {
+               #c0105234:       81 ec ac 05 00 00       sub    $0x5ac,%esp
+               $re = qr/^.*[as][du][db]    \$(0x$x{1,8}),\%esp$/o;
+       } elsif ($arch eq 'x86_64') {
+               #    2f60:      48 81 ec e8 05 00 00    sub    $0x5e8,%rsp
+               $re = qr/^.*[as][du][db]    \$(0x$x{1,8}),\%rsp$/o;
+       } elsif ($arch eq 'ia64') {
+               #e0000000044011fc:       01 0f fc 8c     adds r12=-384,r12
+               $re = qr/.*adds.*r12=-(([0-9]{2}|[3-9])[0-9]{2}),r12/o;
+       } elsif ($arch eq 'm68k') {
+               #    2b6c:       4e56 fb70       linkw %fp,#-1168
+               #  1df770:       defc ffe4       addaw #-28,%sp
+               $re = qr/.*(?:linkw %fp,|addaw )#-([0-9]{1,4})(?:,%sp)?$/o;
+       } elsif ($arch eq 'mips64') {
+               #8800402c:       67bdfff0        daddiu  sp,sp,-16
+               $re = qr/.*daddiu.*sp,sp,-(([0-9]{2}|[3-9])[0-9]{2})/o;
+       } elsif ($arch eq 'mips') {
+               #88003254:       27bdffe0        addiu   sp,sp,-32
+               $re = qr/.*addiu.*sp,sp,-(([0-9]{2}|[3-9])[0-9]{2})/o;
+       } elsif ($arch eq 'ppc') {
+               #c00029f4:       94 21 ff 30     stwu    r1,-208(r1)
+               $re = qr/.*stwu.*r1,-($x{1,8})\(r1\)/o;
+       } elsif ($arch eq 'ppc64') {
+               #XXX
+               $re = qr/.*stdu.*r1,-($x{1,8})\(r1\)/o;
+       } elsif ($arch eq 'powerpc') {
+               $re = qr/.*st[dw]u.*r1,-($x{1,8})\(r1\)/o;
+       } elsif ($arch =~ /^s390x?$/) {
+               #   11160:       a7 fb ff 60             aghi   %r15,-160
+               $re = qr/.*ag?hi.*\%r15,-(([0-9]{2}|[3-9])[0-9]{2})/o;
+       } elsif ($arch =~ /^sh64$/) {
+               #XXX: we only check for the immediate case presently,
+               #     though we will want to check for the movi/sub
+               #     pair for larger users. -- PFM.
+               #a00048e0:       d4fc40f0        addi.l  r15,-240,r15
+               $re = qr/.*addi\.l.*r15,-(([0-9]{2}|[3-9])[0-9]{2}),r15/o;
+       } else {
+               print("wrong or unknown architecture\n");
+               exit
+       }
+}
+
+sub bysize($) {
+       my ($asize, $bsize);
+       ($asize = $a) =~ s/.*:  *(.*)$/$1/;
+       ($bsize = $b) =~ s/.*:  *(.*)$/$1/;
+       $bsize <=> $asize
+}
+
+#
+# main()
+#
+my $funcre = qr/^$x* <(.*)>:$/;
+my $func;
+my $file, $lastslash;
+
+while (my $line = <STDIN>) {
+       if ($line =~ m/$funcre/) {
+               $func = $1;
+       }
+       elsif ($line =~ m/(.*):\s*file format/) {
+               $file = $1;
+               $file =~ s/\.ko//;
+               $lastslash = rindex($file, "/");
+               if ($lastslash != -1) {
+                       $file = substr($file, $lastslash + 1);
+               }
+       }
+       elsif ($line =~ m/$re/) {
+               my $size = $1;
+               $size = hex($size) if ($size =~ /^0x/);
+
+               if ($size > 0xf0000000) {
+                       $size = - $size;
+                       $size += 0x80000000;
+                       $size += 0x80000000;
+               }
+               next if ($size > 0x10000000);
+
+               next if $line !~ m/^($xs*)/;
+               my $addr = $1;
+               $addr =~ s/ /0/g;
+               $addr = "0x$addr";
+
+               # bbox: was: my $intro = "$addr $func [$file]:";
+               my $intro = "$func [$file]:";
+               my $padlen = 56 - length($intro);
+               while ($padlen > 0) {
+                       $intro .= '     ';
+                       $padlen -= 8;
+               }
+               next if ($size < 100);
+               push @stack, "$intro$size\n";
+       }
+}
+
+print sort bysize @stack;
diff --git a/scripts/cleanup_printf2puts b/scripts/cleanup_printf2puts
new file mode 100755 (executable)
index 0000000..446152e
--- /dev/null
@@ -0,0 +1,9 @@
+#!/bin/sh
+
+# Processes current directory recursively:
+# printf("abc\n") -> puts("abc"). Beware of fprintf etc...
+
+# BTW, gcc 4.1.2 already does tha same! Can't believe it...
+
+grep -lr 'printf\([^%%]*\\n"\)' . | grep '.[ch]$' | xargs -n1 \
+    sed -e 's/\([^A-Za-z0-9_]\)printf(\( *"[^%]*\)\\n")/\1puts(\2")/' -i
diff --git a/scripts/defconfig b/scripts/defconfig
new file mode 100644 (file)
index 0000000..f991363
--- /dev/null
@@ -0,0 +1,888 @@
+#
+# Automatically generated make config: don't edit
+# Busybox version: 1.14.1
+# Wed May 27 18:05:31 2009
+#
+CONFIG_HAVE_DOT_CONFIG=y
+
+#
+# Busybox Settings
+#
+
+#
+# General Configuration
+#
+# CONFIG_DESKTOP is not set
+# CONFIG_EXTRA_COMPAT is not set
+# CONFIG_FEATURE_ASSUME_UNICODE is not set
+CONFIG_FEATURE_BUFFERS_USE_MALLOC=y
+# 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_PIDFILE=y
+CONFIG_FEATURE_SUID=y
+CONFIG_FEATURE_SUID_CONFIG=y
+CONFIG_FEATURE_SUID_CONFIG_QUIET=y
+# CONFIG_SELINUX is not set
+# CONFIG_FEATURE_PREFER_APPLETS is not set
+CONFIG_BUSYBOX_EXEC_PATH="/proc/self/exe"
+CONFIG_FEATURE_SYSLOG=y
+CONFIG_FEATURE_HAVE_RPC=y
+
+#
+# Build Options
+#
+# CONFIG_STATIC is not set
+# CONFIG_PIE is not set
+# CONFIG_NOMMU is not set
+# CONFIG_BUILD_LIBBUSYBOX is not set
+# CONFIG_FEATURE_INDIVIDUAL is not set
+# CONFIG_FEATURE_SHARED_BUSYBOX is not set
+CONFIG_LFS=y
+CONFIG_CROSS_COMPILER_PREFIX=""
+CONFIG_EXTRA_CFLAGS=""
+
+#
+# Debugging Options
+#
+# CONFIG_DEBUG is not set
+# CONFIG_DEBUG_PESSIMIZE is not set
+# CONFIG_WERROR is not set
+CONFIG_NO_DEBUG_LIB=y
+# CONFIG_DMALLOC is not set
+# CONFIG_EFENCE is not set
+CONFIG_INCLUDE_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_SCRIPT_WRAPPERS is not set
+# CONFIG_INSTALL_APPLET_DONT is not set
+# CONFIG_INSTALL_SH_APPLET_SYMLINK is not set
+# CONFIG_INSTALL_SH_APPLET_HARDLINK is not set
+# CONFIG_INSTALL_SH_APPLET_SCRIPT_WRAPPER is not set
+CONFIG_PREFIX="./_install"
+
+#
+# Busybox Library Tuning
+#
+CONFIG_PASSWORD_MINLEN=6
+CONFIG_MD5_SIZE_VS_SPEED=2
+CONFIG_FEATURE_FAST_TOP=y
+# CONFIG_FEATURE_ETC_NETWORKS is not set
+CONFIG_FEATURE_EDITING=y
+CONFIG_FEATURE_EDITING_MAX_LEN=1024
+# CONFIG_FEATURE_EDITING_VI is not set
+CONFIG_FEATURE_EDITING_HISTORY=15
+CONFIG_FEATURE_EDITING_SAVEHISTORY=y
+CONFIG_FEATURE_TAB_COMPLETION=y
+# CONFIG_FEATURE_USERNAME_COMPLETION is not set
+# CONFIG_FEATURE_EDITING_FANCY_PROMPT is not set
+# CONFIG_FEATURE_VERBOSE_CP_MESSAGE is not set
+CONFIG_FEATURE_COPYBUF_KB=4
+# CONFIG_MONOTONIC_SYSCALL is not set
+CONFIG_IOCTL_HEX2STR_ERROR=y
+CONFIG_FEATURE_HWIB=y
+
+#
+# Applets
+#
+
+#
+# Archival Utilities
+#
+CONFIG_FEATURE_SEAMLESS_LZMA=y
+CONFIG_FEATURE_SEAMLESS_BZ2=y
+CONFIG_FEATURE_SEAMLESS_GZ=y
+CONFIG_FEATURE_SEAMLESS_Z=y
+CONFIG_AR=y
+CONFIG_FEATURE_AR_LONG_FILENAMES=y
+CONFIG_BUNZIP2=y
+CONFIG_BZIP2=y
+CONFIG_CPIO=y
+CONFIG_FEATURE_CPIO_O=y
+CONFIG_FEATURE_CPIO_P=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_GZIP=y
+# CONFIG_RPM2CPIO is not set
+# CONFIG_RPM is not set
+CONFIG_TAR=y
+CONFIG_FEATURE_TAR_CREATE=y
+CONFIG_FEATURE_TAR_AUTODETECT=y
+CONFIG_FEATURE_TAR_FROM=y
+CONFIG_FEATURE_TAR_OLDGNU_COMPATIBILITY=y
+CONFIG_FEATURE_TAR_OLDSUN_COMPATIBILITY=y
+CONFIG_FEATURE_TAR_GNU_EXTENSIONS=y
+CONFIG_FEATURE_TAR_LONG_OPTIONS=y
+CONFIG_FEATURE_TAR_UNAME_GNAME=y
+CONFIG_UNCOMPRESS=y
+CONFIG_UNLZMA=y
+CONFIG_FEATURE_LZMA_FAST=y
+CONFIG_UNZIP=y
+
+#
+# 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_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_FEATURE_DF_FANCY=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_EXPAND=y
+CONFIG_FEATURE_EXPAND_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_READLINK=y
+CONFIG_FEATURE_READLINK_FOLLOW=y
+CONFIG_REALPATH=y
+CONFIG_RM=y
+CONFIG_RMDIR=y
+CONFIG_FEATURE_RMDIR_LONG_OPTIONS=y
+CONFIG_SEQ=y
+CONFIG_SHA1SUM=y
+CONFIG_SHA256SUM=y
+CONFIG_SHA512SUM=y
+CONFIG_SLEEP=y
+CONFIG_FEATURE_FANCY_SLEEP=y
+CONFIG_FEATURE_FLOAT_SLEEP=y
+CONFIG_SORT=y
+CONFIG_FEATURE_SORT_BIG=y
+CONFIG_SPLIT=y
+CONFIG_FEATURE_SPLIT_FANCY=y
+CONFIG_STAT=y
+CONFIG_FEATURE_STAT_FORMAT=y
+CONFIG_STTY=y
+CONFIG_SUM=y
+CONFIG_SYNC=y
+CONFIG_TAC=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_UNEXPAND=y
+CONFIG_FEATURE_UNEXPAND_LONG_OPTIONS=y
+CONFIG_UNIQ=y
+CONFIG_USLEEP=y
+CONFIG_UUDECODE=y
+CONFIG_UUENCODE=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_KBD_MODE=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=y
+CONFIG_SETFONT=y
+CONFIG_FEATURE_SETFONT_TEXTUAL_MAP=y
+CONFIG_DEFAULT_SETFONT_DIR=""
+CONFIG_SETKEYCODES=y
+CONFIG_SETLOGCONS=y
+CONFIG_SHOWKEY=y
+
+#
+# Debian Utilities
+#
+CONFIG_MKTEMP=y
+CONFIG_PIPE_PROGRESS=y
+CONFIG_RUN_PARTS=y
+CONFIG_FEATURE_RUN_PARTS_LONG_OPTIONS=y
+CONFIG_FEATURE_RUN_PARTS_FANCY=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_LIBM=y
+CONFIG_CMP=y
+CONFIG_DIFF=y
+CONFIG_FEATURE_DIFF_BINARY=y
+CONFIG_FEATURE_DIFF_DIR=y
+CONFIG_FEATURE_DIFF_MINIMAL=y
+CONFIG_ED=y
+CONFIG_PATCH=y
+CONFIG_SED=y
+CONFIG_VI=y
+CONFIG_FEATURE_VI_MAX_LEN=4096
+# CONFIG_FEATURE_VI_8BIT is not set
+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
+CONFIG_FEATURE_ALLOW_EXEC=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_MAXDEPTH=y
+CONFIG_FEATURE_FIND_NEWER=y
+CONFIG_FEATURE_FIND_INUM=y
+CONFIG_FEATURE_FIND_EXEC=y
+CONFIG_FEATURE_FIND_USER=y
+CONFIG_FEATURE_FIND_GROUP=y
+CONFIG_FEATURE_FIND_NOT=y
+CONFIG_FEATURE_FIND_DEPTH=y
+CONFIG_FEATURE_FIND_PAREN=y
+CONFIG_FEATURE_FIND_SIZE=y
+CONFIG_FEATURE_FIND_PRUNE=y
+CONFIG_FEATURE_FIND_DELETE=y
+CONFIG_FEATURE_FIND_PATH=y
+CONFIG_FEATURE_FIND_REGEX=y
+# CONFIG_FEATURE_FIND_CONTEXT is not set
+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_FEATURE_USE_INITTAB=y
+# CONFIG_FEATURE_KILL_REMOVED is not set
+CONFIG_FEATURE_KILL_DELAY=0
+CONFIG_FEATURE_INIT_SCTTY=y
+CONFIG_FEATURE_INIT_SYSLOG=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_PWD_GRP=y
+CONFIG_USE_BB_SHADOW=y
+CONFIG_USE_BB_CRYPT=y
+CONFIG_USE_BB_CRYPT_SHA=y
+CONFIG_ADDGROUP=y
+CONFIG_FEATURE_ADDUSER_TO_GROUP=y
+CONFIG_DELGROUP=y
+CONFIG_FEATURE_DEL_USER_FROM_GROUP=y
+# CONFIG_FEATURE_CHECK_NAMES is not set
+CONFIG_ADDUSER=y
+CONFIG_FEATURE_ADDUSER_LONG_OPTIONS=y
+CONFIG_DELUSER=y
+CONFIG_GETTY=y
+CONFIG_FEATURE_UTMP=y
+CONFIG_FEATURE_WTMP=y
+CONFIG_LOGIN=y
+# CONFIG_PAM is not set
+CONFIG_LOGIN_SCRIPTS=y
+CONFIG_FEATURE_NOLOGIN=y
+CONFIG_FEATURE_SECURETTY=y
+CONFIG_PASSWD=y
+CONFIG_FEATURE_PASSWD_WEAK_CHECK=y
+CONFIG_CRYPTPW=y
+CONFIG_CHPASSWD=y
+CONFIG_SU=y
+CONFIG_FEATURE_SU_SYSLOG=y
+CONFIG_FEATURE_SU_CHECKS_SHELLS=y
+CONFIG_SULOGIN=y
+CONFIG_VLOCK=y
+
+#
+# Linux Ext2 FS Progs
+#
+CONFIG_CHATTR=y
+CONFIG_FSCK=y
+CONFIG_LSATTR=y
+
+#
+# Linux Module Utilities
+#
+CONFIG_MODPROBE_SMALL=y
+CONFIG_FEATURE_MODPROBE_SMALL_OPTIONS_ON_CMDLINE=y
+CONFIG_FEATURE_MODPROBE_SMALL_CHECK_ALREADY_LOADED=y
+# CONFIG_INSMOD is not set
+# CONFIG_RMMOD is not set
+# CONFIG_LSMOD is not set
+# CONFIG_FEATURE_LSMOD_PRETTY_2_6_OUTPUT is not set
+# CONFIG_MODPROBE is not set
+# CONFIG_FEATURE_MODPROBE_BLACKLIST is not set
+# CONFIG_DEPMOD is not set
+
+#
+# Options common to multiple modutils
+#
+# CONFIG_FEATURE_2_4_MODULES is not set
+# CONFIG_FEATURE_INSMOD_VERSION_CHECKING is not set
+# CONFIG_FEATURE_INSMOD_KSYMOOPS_SYMBOLS is not set
+# CONFIG_FEATURE_INSMOD_LOADINKMEM is not set
+# CONFIG_FEATURE_INSMOD_LOAD_MAP is not set
+# CONFIG_FEATURE_INSMOD_LOAD_MAP_FULL is not set
+# CONFIG_FEATURE_CHECK_TAINTED_MODULE is not set
+# CONFIG_FEATURE_MODUTILS_ALIAS is not set
+# CONFIG_FEATURE_MODUTILS_SYMBOLS is not set
+CONFIG_DEFAULT_MODULES_DIR="/lib/modules"
+CONFIG_DEFAULT_DEPMOD_FILE="modules.dep"
+
+#
+# Linux System Utilities
+#
+CONFIG_ACPID=y
+CONFIG_FEATURE_ACPID_COMPAT=y
+CONFIG_BLKID=y
+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_FINDFS=y
+CONFIG_FREERAMDISK=y
+CONFIG_FSCK_MINIX=y
+CONFIG_MKFS_MINIX=y
+
+#
+# Minix filesystem support
+#
+CONFIG_FEATURE_MINIX2=y
+CONFIG_MKFS_VFAT=y
+CONFIG_GETOPT=y
+CONFIG_HEXDUMP=y
+CONFIG_FEATURE_HEXDUMP_REVERSE=y
+CONFIG_HD=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_RENAME=y
+CONFIG_FEATURE_MDEV_RENAME_REGEXP=y
+CONFIG_FEATURE_MDEV_EXEC=y
+CONFIG_FEATURE_MDEV_LOAD_FIRMWARE=y
+CONFIG_MKSWAP=y
+CONFIG_FEATURE_MKSWAP_V0=y
+CONFIG_MORE=y
+CONFIG_FEATURE_USE_TERMIOS=y
+CONFIG_VOLUMEID=y
+CONFIG_FEATURE_VOLUMEID_EXT=y
+CONFIG_FEATURE_VOLUMEID_REISERFS=y
+CONFIG_FEATURE_VOLUMEID_FAT=y
+CONFIG_FEATURE_VOLUMEID_HFS=y
+CONFIG_FEATURE_VOLUMEID_JFS=y
+CONFIG_FEATURE_VOLUMEID_XFS=y
+CONFIG_FEATURE_VOLUMEID_NTFS=y
+CONFIG_FEATURE_VOLUMEID_ISO9660=y
+CONFIG_FEATURE_VOLUMEID_UDF=y
+CONFIG_FEATURE_VOLUMEID_LUKS=y
+CONFIG_FEATURE_VOLUMEID_LINUXSWAP=y
+CONFIG_FEATURE_VOLUMEID_CRAMFS=y
+CONFIG_FEATURE_VOLUMEID_ROMFS=y
+CONFIG_FEATURE_VOLUMEID_SYSV=y
+CONFIG_FEATURE_VOLUMEID_OCFS2=y
+CONFIG_FEATURE_VOLUMEID_LINUXRAID=y
+CONFIG_MOUNT=y
+CONFIG_FEATURE_MOUNT_FAKE=y
+CONFIG_FEATURE_MOUNT_VERBOSE=y
+# CONFIG_FEATURE_MOUNT_HELPERS is not set
+CONFIG_FEATURE_MOUNT_LABEL=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_RDEV=y
+CONFIG_READPROFILE=y
+CONFIG_RTCWAKE=y
+CONFIG_SCRIPT=y
+CONFIG_SETARCH=y
+CONFIG_SWAPONOFF=y
+CONFIG_FEATURE_SWAPON_PRI=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 is not set
+CONFIG_CHAT=y
+CONFIG_FEATURE_CHAT_NOFAIL=y
+# CONFIG_FEATURE_CHAT_TTY_HIFI is not set
+CONFIG_FEATURE_CHAT_IMPLICIT_CR=y
+CONFIG_FEATURE_CHAT_SWALLOW_OPTS=y
+CONFIG_FEATURE_CHAT_SEND_ESCAPES=y
+CONFIG_FEATURE_CHAT_VAR_ABORT_LEN=y
+CONFIG_FEATURE_CHAT_CLR_ABORT=y
+CONFIG_CHRT=y
+CONFIG_CROND=y
+CONFIG_FEATURE_CROND_D=y
+CONFIG_FEATURE_CROND_CALL_SENDMAIL=y
+CONFIG_FEATURE_CROND_DIR="/var/spool/cron"
+CONFIG_CRONTAB=y
+CONFIG_DC=y
+CONFIG_FEATURE_DC_LIBM=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_DEVMEM=y
+CONFIG_EJECT=y
+CONFIG_FEATURE_EJECT_SCSI=y
+CONFIG_FBSPLASH=y
+# CONFIG_FLASH_ERASEALL is not set
+CONFIG_IONICE=y
+# CONFIG_INOTIFYD is not set
+CONFIG_LAST=y
+# CONFIG_FEATURE_LAST_SMALL is not set
+CONFIG_FEATURE_LAST_FANCY=y
+CONFIG_LESS=y
+CONFIG_FEATURE_LESS_MAXLINES=9999999
+CONFIG_FEATURE_LESS_BRACKETS=y
+CONFIG_FEATURE_LESS_FLAGS=y
+CONFIG_FEATURE_LESS_MARKS=y
+CONFIG_FEATURE_LESS_REGEXP=y
+CONFIG_FEATURE_LESS_WINCH=y
+CONFIG_FEATURE_LESS_DASHCMD=y
+CONFIG_FEATURE_LESS_LINENUMS=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_MAN=y
+CONFIG_MICROCOM=y
+CONFIG_MOUNTPOINT=y
+CONFIG_MT=y
+CONFIG_RAIDAUTORUN=y
+CONFIG_READAHEAD=y
+CONFIG_RUNLEVEL=y
+CONFIG_RX=y
+CONFIG_SETSID=y
+CONFIG_STRINGS=y
+# CONFIG_TASKSET is not set
+# CONFIG_FEATURE_TASKSET_FANCY is not set
+CONFIG_TIME=y
+CONFIG_TIMEOUT=y
+CONFIG_TTYSIZE=y
+CONFIG_WATCHDOG=y
+
+#
+# Networking Utilities
+#
+CONFIG_FEATURE_IPV6=y
+CONFIG_FEATURE_PREFER_IPV4_ADDRESS=y
+# CONFIG_VERBOSE_RESOLUTION_ERRORS is not set
+CONFIG_ARP=y
+CONFIG_ARPING=y
+CONFIG_BRCTL=y
+CONFIG_FEATURE_BRCTL_FANCY=y
+CONFIG_FEATURE_BRCTL_SHOW=y
+CONFIG_DNSD=y
+CONFIG_ETHER_WAKE=y
+CONFIG_FAKEIDENTD=y
+CONFIG_FTPD=y
+CONFIG_FEATURE_FTP_WRITE=y
+CONFIG_FTPGET=y
+CONFIG_FTPPUT=y
+CONFIG_FEATURE_FTPGETPUT_LONG_OPTIONS=y
+CONFIG_HOSTNAME=y
+CONFIG_HTTPD=y
+CONFIG_FEATURE_HTTPD_RANGES=y
+CONFIG_FEATURE_HTTPD_USE_SENDFILE=y
+CONFIG_FEATURE_HTTPD_SETUID=y
+CONFIG_FEATURE_HTTPD_BASIC_AUTH=y
+CONFIG_FEATURE_HTTPD_AUTH_MD5=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_FEATURE_HTTPD_ERROR_PAGES=y
+CONFIG_FEATURE_HTTPD_PROXY=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_IFENSLAVE=y
+CONFIG_IFUPDOWN=y
+CONFIG_IFUPDOWN_IFSTATE_PATH="/var/run/ifstate"
+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_MAPPING=y
+# CONFIG_FEATURE_IFUPDOWN_EXTERNAL_DHCP is not set
+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_FEATURE_IP_RARE_PROTOCOLS is not set
+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_FEATURE_NAMEIF_EXTENDED=y
+CONFIG_NC=y
+CONFIG_NC_SERVER=y
+CONFIG_NC_EXTRA=y
+CONFIG_NETSTAT=y
+CONFIG_FEATURE_NETSTAT_WIDE=y
+CONFIG_FEATURE_NETSTAT_PRG=y
+CONFIG_NSLOOKUP=y
+CONFIG_PING=y
+CONFIG_PING6=y
+CONFIG_FEATURE_FANCY_PING=y
+CONFIG_PSCAN=y
+CONFIG_ROUTE=y
+CONFIG_SLATTACH=y
+CONFIG_TELNET=y
+CONFIG_FEATURE_TELNET_TTYPE=y
+CONFIG_FEATURE_TELNET_AUTOLOGIN=y
+CONFIG_TELNETD=y
+CONFIG_FEATURE_TELNETD_STANDALONE=y
+CONFIG_TFTP=y
+CONFIG_TFTPD=y
+CONFIG_FEATURE_TFTP_GET=y
+CONFIG_FEATURE_TFTP_PUT=y
+CONFIG_FEATURE_TFTP_BLOCKSIZE=y
+# CONFIG_TFTP_DEBUG is not set
+CONFIG_TRACEROUTE=y
+CONFIG_FEATURE_TRACEROUTE_VERBOSE=y
+# 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_FEATURE_UDHCPD_WRITE_LEASES_EARLY=y
+CONFIG_DHCPD_LEASES_FILE="/var/lib/misc/udhcpd.leases"
+CONFIG_APP_UDHCPC=y
+CONFIG_FEATURE_UDHCPC_ARPING=y
+CONFIG_FEATURE_UDHCP_PORT=y
+# CONFIG_UDHCP_DEBUG is not set
+CONFIG_FEATURE_UDHCP_RFC3397=y
+CONFIG_UDHCPC_DEFAULT_SCRIPT="/usr/share/udhcpc/default.script"
+CONFIG_UDHCPC_SLACK_FOR_BUGGY_SERVERS=80
+CONFIG_IFUPDOWN_UDHCPC_CMD_OPTIONS="-R -n"
+CONFIG_VCONFIG=y
+CONFIG_WGET=y
+CONFIG_FEATURE_WGET_STATUSBAR=y
+CONFIG_FEATURE_WGET_AUTHENTICATION=y
+CONFIG_FEATURE_WGET_LONG_OPTIONS=y
+CONFIG_ZCIP=y
+CONFIG_TCPSVD=y
+CONFIG_TUNCTL=y
+CONFIG_FEATURE_TUNCTL_UG=y
+CONFIG_UDPSVD=y
+
+#
+# Print Utilities
+#
+CONFIG_LPD=y
+CONFIG_LPR=y
+CONFIG_LPQ=y
+
+#
+# Mail Utilities
+#
+CONFIG_MAKEMIME=y
+CONFIG_FEATURE_MIME_CHARSET="us-ascii"
+CONFIG_POPMAILDIR=y
+CONFIG_FEATURE_POPMAILDIR_DELIVERY=y
+CONFIG_REFORMIME=y
+CONFIG_FEATURE_REFORMIME_COMPAT=y
+CONFIG_SENDMAIL=y
+
+#
+# Process Utilities
+#
+CONFIG_FREE=y
+CONFIG_FUSER=y
+CONFIG_KILL=y
+CONFIG_KILLALL=y
+CONFIG_KILLALL5=y
+CONFIG_NMETER=y
+CONFIG_PGREP=y
+CONFIG_PIDOF=y
+CONFIG_FEATURE_PIDOF_SINGLE=y
+CONFIG_FEATURE_PIDOF_OMIT=y
+CONFIG_PKILL=y
+CONFIG_PS=y
+CONFIG_FEATURE_PS_WIDE=y
+# CONFIG_FEATURE_PS_TIME is not set
+# CONFIG_FEATURE_PS_UNUSUAL_SYSTEMS is not set
+CONFIG_RENICE=y
+CONFIG_BB_SYSCTL=y
+CONFIG_TOP=y
+CONFIG_FEATURE_TOP_CPU_USAGE_PERCENTAGE=y
+CONFIG_FEATURE_TOP_CPU_GLOBAL_PERCENTS=y
+CONFIG_FEATURE_TOP_SMP_CPU=y
+CONFIG_FEATURE_TOP_DECIMALS=y
+CONFIG_FEATURE_TOP_SMP_PROCESS=y
+CONFIG_FEATURE_TOPMEM=y
+CONFIG_UPTIME=y
+CONFIG_WATCH=y
+
+#
+# Runit Utilities
+#
+CONFIG_RUNSV=y
+CONFIG_RUNSVDIR=y
+# CONFIG_FEATURE_RUNSVDIR_LOG is not set
+CONFIG_SV=y
+CONFIG_SV_DEFAULT_SERVICE_DIR="/var/service"
+CONFIG_SVLOGD=y
+CONFIG_CHPST=y
+CONFIG_SETUIDGID=y
+CONFIG_ENVUIDGID=y
+CONFIG_ENVDIR=y
+CONFIG_SOFTLIMIT=y
+# CONFIG_CHCON is not set
+# CONFIG_FEATURE_CHCON_LONG_OPTIONS is not set
+# CONFIG_GETENFORCE is not set
+# CONFIG_GETSEBOOL is not set
+# CONFIG_LOAD_POLICY is not set
+# CONFIG_MATCHPATHCON is not set
+# CONFIG_RESTORECON is not set
+# CONFIG_RUNCON is not set
+# CONFIG_FEATURE_RUNCON_LONG_OPTIONS is not set
+# CONFIG_SELINUXENABLED is not set
+# CONFIG_SETENFORCE is not set
+# CONFIG_SETFILES is not set
+# CONFIG_FEATURE_SETFILES_CHECK_OPTION is not set
+# CONFIG_SETSEBOOL is not set
+# CONFIG_SESTATUS is not set
+
+#
+# Shells
+#
+CONFIG_FEATURE_SH_IS_ASH=y
+# CONFIG_FEATURE_SH_IS_HUSH is not set
+# CONFIG_FEATURE_SH_IS_MSH is not set
+# CONFIG_FEATURE_SH_IS_NONE is not set
+CONFIG_ASH=y
+
+#
+# Ash Shell Options
+#
+CONFIG_ASH_BASH_COMPAT=y
+CONFIG_ASH_JOB_CONTROL=y
+CONFIG_ASH_READ_NCHARS=y
+CONFIG_ASH_READ_TIMEOUT=y
+CONFIG_ASH_ALIAS=y
+CONFIG_ASH_GETOPTS=y
+CONFIG_ASH_BUILTIN_ECHO=y
+CONFIG_ASH_BUILTIN_PRINTF=y
+CONFIG_ASH_BUILTIN_TEST=y
+CONFIG_ASH_CMDCMD=y
+# CONFIG_ASH_MAIL is not set
+CONFIG_ASH_OPTIMIZE_FOR_SIZE=y
+CONFIG_ASH_RANDOM_SUPPORT=y
+CONFIG_ASH_EXPAND_PRMT=y
+CONFIG_HUSH=y
+CONFIG_HUSH_HELP=y
+CONFIG_HUSH_INTERACTIVE=y
+CONFIG_HUSH_JOB=y
+CONFIG_HUSH_TICK=y
+CONFIG_HUSH_IF=y
+CONFIG_HUSH_LOOPS=y
+CONFIG_HUSH_CASE=y
+CONFIG_HUSH_FUNCTIONS=y
+CONFIG_HUSH_EXPORT_N=y
+# CONFIG_LASH is not set
+CONFIG_MSH=y
+
+#
+# Bourne Shell Options
+#
+CONFIG_SH_MATH_SUPPORT=y
+CONFIG_SH_MATH_SUPPORT_64=y
+CONFIG_FEATURE_SH_EXTRA_QUIET=y
+# CONFIG_FEATURE_SH_STANDALONE is not set
+# CONFIG_FEATURE_SH_NOFORK is not set
+CONFIG_CTTYHACK=y
+
+#
+# System Logging Utilities
+#
+CONFIG_SYSLOGD=y
+CONFIG_FEATURE_ROTATE_LOGFILE=y
+CONFIG_FEATURE_REMOTE_LOG=y
+CONFIG_FEATURE_SYSLOGD_DUP=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
diff --git a/scripts/echo.c b/scripts/echo.c
new file mode 100644 (file)
index 0000000..9e591c4
--- /dev/null
@@ -0,0 +1,230 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * echo implementation for busybox - used as a helper for testsuite/*
+ * on systems lacking "echo -en"
+ *
+ * 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 did not allow 4-digit octals.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <limits.h>
+
+#define WANT_HEX_ESCAPES 1
+
+/* Usual "this only works for ascii compatible encodings" disclaimer. */
+#undef _tolower
+#define _tolower(X) ((X)|((char) 0x20))
+
+static 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 char)(*q) - '0';
+#ifdef WANT_HEX_ESCAPES
+               if (d >= 10) {
+                       d = (unsigned char)(_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;
+}
+
+
+int main(int argc, char **argv)
+{
+       const char *arg;
+       const char *p;
+       char nflag = 1;
+       char eflag = 0;
+
+       /* We must check that stdout is not closed. */
+       if (dup2(1, 1) != 1)
+               return -1;
+
+       while (1) {
+               arg = *++argv;
+               if (!arg)
+                       goto newline_ret;
+               if (*arg != '-')
+                       break;
+
+               /* 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.
+                */
+               p = arg + 1;
+               if (!*p)        /* A single '-', so echo it. */
+                       goto just_echo;
+
+               do {
+                       if (!strrchr("neE", *p))
+                               goto just_echo;
+               } while (*++p);
+
+               /* All of the options in this arg are valid, so handle them. */
+               p = arg + 1;
+               do {
+                       if (*p == 'n')
+                               nflag = 0;
+                       if (*p == 'e')
+                               eflag = '\\';
+               } while (*++p);
+       }
+ just_echo:
+       while (1) {
+               /* arg is already == *argv and isn't NULL */
+               int c;
+
+               if (!eflag) {
+                       /* optimization for very common case */
+                       fputs(arg, stdout);
+               } else while ((c = *arg++)) {
+                       if (c == eflag) {       /* Check for escape seq. */
+                               if (*arg == 'c') {
+                                       /* '\c' means cancel newline and
+                                        * ignore all subsequent chars. */
+                                       goto ret;
+                               }
+                               {
+                                       /* Since SUSv3 mandates a first digit of 0, 4-digit octals
+                                       * of the form \0### are accepted. */
+                                       if (*arg == '0') {
+                                               /* NB: don't turn "...\0" into "...\" */
+                                               if (arg[1] && ((unsigned char)(arg[1]) - '0') < 8) {
+                                                       arg++;
+                                               }
+                                       }
+                                       /* bb_process_escape_sequence handles NUL correctly
+                                        * ("...\" case. */
+                                       c = bb_process_escape_sequence(&arg);
+                               }
+                       }
+                       putchar(c);
+               }
+
+               arg = *++argv;
+               if (!arg)
+                       break;
+               putchar(' ');
+       }
+
+ newline_ret:
+       if (nflag) {
+               putchar('\n');
+       }
+ ret:
+       return fflush(stdout);
+}
+
+/*-
+ * 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. <BSD Advertising Clause omitted per the July 22, 1999 licensing change
+ *              ftp://ftp.cs.berkeley.edu/pub/4bsd/README.Impt.License.Change>
+ *
+ *      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/scripts/find_bad_common_bufsiz b/scripts/find_bad_common_bufsiz
new file mode 100755 (executable)
index 0000000..e80cf62
--- /dev/null
@@ -0,0 +1,13 @@
+#!/bin/sh
+
+# This script finds applets with multiple uses of bb_common_bufsiz1
+# (== possible bugs).
+# Currently (2007-06-04) reports 3 false positives:
+# ./coreutils/diff.c:7
+# ./loginutils/getty.c:2
+# ./util-linux/mount.c:5
+
+find -name '*.c' \
+| while read name; do
+    grep -Hc bb_common_bufsiz1 "$name"
+done | grep -v ':[01]$'
diff --git a/scripts/find_stray_common_vars b/scripts/find_stray_common_vars
new file mode 100755 (executable)
index 0000000..3a25d7a
--- /dev/null
@@ -0,0 +1,10 @@
+#!/bin/sh
+
+# Common variables are elusive, they don't show up in size output!
+# This script will show all commons in *.o, sorted by size
+
+find ! -path './scripts/*' -a ! -name built-in.o -a -name '*.o' \
+| while read name; do
+    b=`basename "$name"`
+    nm "$name" | sed "s/^/$b: /"
+done | grep -i ' c ' | sort -k2
diff --git a/scripts/fix_ws.sh b/scripts/fix_ws.sh
new file mode 100755 (executable)
index 0000000..e7cf529
--- /dev/null
@@ -0,0 +1,71 @@
+#!/bin/bash
+# Whitespace fixer
+# Usage: fix_ws [dir]...
+
+temp="/tmp/fix_ws.$$.$RANDOM"
+
+# Using $'xxx' bashism
+begin8sp_tab=$'s/^        /\t/'
+beginchar7sp_chartab=$'s/^\\([^ \t]\\)       /\\1\t/'
+tab8sp_tabtab=$'s/\t        /\t\t/g'
+tab8sptab_tabtabtab=$'s/\t        \t/\t\t\t/g'
+begin17sptab_tab=$'s/^ \\{1,7\\}\t/\t/'
+tab17sptab_tabtab=$'s/\t \\{1,7\\}\t/\t\t/g'
+trailingws_=$'s/[ \t]*$//'
+
+#>fix_ws.diff
+
+find "$@" -type f \
+| while read name; do
+    test "YES" = "${name/*.bz2/YES}" && continue
+    test "YES" = "${name/*.gz/YES}" && continue
+    test "YES" = "${name/*.png/YES}" && continue
+    test "YES" = "${name/*.gif/YES}" && continue
+    test "YES" = "${name/*.jpg/YES}" && continue
+    test "YES" = "${name/*.diff/YES}" && continue
+    test "YES" = "${name/*.patch/YES}" && continue
+    # shell testsuite entries are not to be touched too
+    test "YES" = "${name/*.right/YES}" && continue
+
+    if test "YES" = "${name/*.[chsS]/YES}" \
+       -o "YES" = "${name/*.sh/YES}" \
+       -o "YES" = "${name/*.txt/YES}" \
+       -o "YES" = "${name/*.html/YES}" \
+       -o "YES" = "${name/*.htm/YES}" \
+       -o "YES" = "${name/*Config.in*/YES}" \
+    ; then
+    # More aggressive whitespace fixes for known file types
+       echo "Formatting: $name" >&2
+       cat "$name" \
+       | sed -e "$tab8sptab_tabtabtab" -e "$tab8sptab_tabtabtab" \
+             -e "$tab8sptab_tabtabtab" -e "$tab8sptab_tabtabtab" \
+       | sed "$begin17sptab_tab" \
+       | sed -e "$tab17sptab_tabtab" -e "$tab17sptab_tabtab" \
+             -e "$tab17sptab_tabtab" -e "$tab17sptab_tabtab" \
+             -e "$tab17sptab_tabtab" -e "$tab17sptab_tabtab" \
+       | sed "$trailingws_"
+    elif test "YES" = "${name/*Makefile*/YES}" \
+       -o "YES" = "${name/*Kbuild*/YES}" \
+    ; then
+    # For Makefiles, never convert "1-7spaces+tab" into "tabtab"
+       echo "Makefile: $name" >&2
+       cat "$name" \
+       | sed -e "$tab8sptab_tabtabtab" -e "$tab8sptab_tabtabtab" \
+             -e "$tab8sptab_tabtabtab" -e "$tab8sptab_tabtabtab" \
+       | sed -e "$tab17sptab_tabtab" -e "$tab17sptab_tabtab" \
+             -e "$tab17sptab_tabtab" -e "$tab17sptab_tabtab" \
+             -e "$tab17sptab_tabtab" -e "$tab17sptab_tabtab" \
+       | sed "$trailingws_"
+    else
+    # Only remove trailing WS for the rest
+       echo "Removing trailing whitespace: $name" >&2
+       cat "$name" \
+       | sed "$trailingws_"
+    fi >"$temp"
+
+#    diff -u "$temp" "$name" >>fix_ws.diff
+
+    # Conserve mode/symlink:
+    cat "$temp" >"$name"
+done
+rm "$temp" 2>/dev/null
diff --git a/scripts/gcc-version.sh b/scripts/gcc-version.sh
new file mode 100755 (executable)
index 0000000..3451080
--- /dev/null
@@ -0,0 +1,12 @@
+#!/bin/sh
+#
+# gcc-version gcc-command
+#
+# Prints the gcc version of `gcc-command' in a canonical 4-digit form
+# such as `0295' for gcc-2.95, `0303' for gcc-3.3, etc.
+#
+
+compiler="$*"
+
+MAJ_MIN=$(echo __GNUC__ __GNUC_MINOR__ | $compiler -E -xc - | tail -n 1)
+printf '%02d%02d\n' $MAJ_MIN
diff --git a/scripts/individual b/scripts/individual
new file mode 100755 (executable)
index 0000000..e93ca55
--- /dev/null
@@ -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_and_exit(...)' '-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 (file)
index 0000000..f56863f
--- /dev/null
@@ -0,0 +1,248 @@
+# ===========================================================================
+# 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
+
+# Add environment specific flags
+HOST_EXTRACFLAGS += $(shell $(CONFIG_SHELL) $(srctree)/$(src)/check.sh $(HOSTCC) $(HOSTCFLAGS))
+
+# 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 (file)
index 0000000..cc94e46
--- /dev/null
@@ -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/check.sh b/scripts/kconfig/check.sh
new file mode 100755 (executable)
index 0000000..fa59cbf
--- /dev/null
@@ -0,0 +1,14 @@
+#!/bin/sh
+# Needed for systems without gettext
+$* -xc -o /dev/null - > /dev/null 2>&1 << EOF
+#include <libintl.h>
+int main()
+{
+       gettext("");
+       return 0;
+}
+EOF
+if [ ! "$?" -eq "0"  ]; then
+       echo -DKBUILD_NO_NLS;
+fi
+
diff --git a/scripts/kconfig/conf.c b/scripts/kconfig/conf.c
new file mode 100644 (file)
index 0000000..9befa2b
--- /dev/null
@@ -0,0 +1,612 @@
+/*
+ * 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 <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <time.h>
+#include <sys/stat.h>
+
+#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 (strlen(line) > 0 && 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); //bbox: && 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 busybox!\n"
+                               "***\n"
+                               "*** Please run some configurator (e.g. \"make oldconfig\" or\n"
+                               "*** \"make menuconfig\" or \"make defconfig\").\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 configuration.\n\n"));
+               return 1;
+       }
+       return 0;
+}
diff --git a/scripts/kconfig/confdata.c b/scripts/kconfig/confdata.c
new file mode 100644 (file)
index 0000000..58ea96d
--- /dev/null
@@ -0,0 +1,573 @@
+/*
+ * Copyright (C) 2002 Roman Zippel <zippel@linux-m68k.org>
+ * Released under the terms of the GNU GPL v2.0.
+ */
+
+#include <sys/stat.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#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[] = {
+       conf_def_filename,
+       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 {
+                                       if (p[0]) /* bbox */
+                                               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"
+                      "# Busybox 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) {
+                       size_t ret = \
+                               strftime(buf, sizeof(buf), "#define AUTOCONF_TIMESTAMP "
+                                       "\"%Y-%m-%d %H:%M:%S %Z\"\n", localtime(&now));
+                       /* if user has Factory timezone or some other odd install, the
+                        * %Z above will overflow the string leaving us with undefined
+                        * results ... so let's try again without the timezone.
+                        */
+                       if (ret == 0)
+                               strftime(buf, sizeof(buf), "#define AUTOCONF_TIMESTAMP "
+                                       "\"%Y-%m-%d %H:%M:%S\"\n", localtime(&now));
+               } else { /* bbox */
+                       strcpy(buf, "#define AUTOCONF_TIMESTAMP \"\"\n");
+               }
+               fprintf(out_h, "/*\n"
+                              " * Automatically generated C config: don't edit\n"
+                              " * Busybox version: %s\n"
+                              " */\n"
+                              "%s"
+                              "\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 see 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);
+                               if (!str[0])
+                                       str = "0";
+                               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 (file)
index 0000000..6f39e7a
--- /dev/null
@@ -0,0 +1,1099 @@
+/*
+ * Copyright (C) 2002 Roman Zippel <zippel@linux-m68k.org>
+ * Released under the terms of the GNU GPL v2.0.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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, "<choice>");
+               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, "<unknown type %d>", 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 (file)
index 0000000..1b36ef1
--- /dev/null
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2002 Roman Zippel <zippel@linux-m68k.org>
+ * Released under the terms of the GNU GPL v2.0.
+ */
+
+#ifndef EXPR_H
+#define EXPR_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdio.h>
+#ifndef __cplusplus
+#include <stdbool.h>
+#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 (file)
index 0000000..9bab17d
--- /dev/null
@@ -0,0 +1,1644 @@
+/* Hey EMACS -*- linux-c -*- */
+/*
+ *
+ * Copyright (C) 2002-2003 Romain Lievin <roms@tilp.info>
+ * Released under the terms of the GNU GPL v2.0.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#  include <config.h>
+#endif
+
+#include "lkc.h"
+#include "images.c"
+
+#include <glade/glade.h>
+#include <gtk/gtk.h>
+#include <glib.h>
+#include <gdk/gdkkeysyms.h>
+
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <time.h>
+#include <stdlib.h>
+
+//#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, _("BusyBox %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 configuration tool.\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 <roms@lpg.ticalc.org>.\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 <config>\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 (file)
index 0000000..f8744ed
--- /dev/null
@@ -0,0 +1,648 @@
+<?xml version="1.0" standalone="no"?> <!--*- mode: xml -*-->
+<!DOCTYPE glade-interface SYSTEM "http://glade.gnome.org/glade-2.0.dtd">
+
+<glade-interface>
+
+<widget class="GtkWindow" id="window1">
+  <property name="visible">True</property>
+  <property name="title" translatable="yes">Gtk Kernel Configurator</property>
+  <property name="type">GTK_WINDOW_TOPLEVEL</property>
+  <property name="window_position">GTK_WIN_POS_NONE</property>
+  <property name="modal">False</property>
+  <property name="default_width">640</property>
+  <property name="default_height">480</property>
+  <property name="resizable">True</property>
+  <property name="destroy_with_parent">False</property>
+  <property name="decorated">True</property>
+  <property name="skip_taskbar_hint">False</property>
+  <property name="skip_pager_hint">False</property>
+  <property name="type_hint">GDK_WINDOW_TYPE_HINT_NORMAL</property>
+  <property name="gravity">GDK_GRAVITY_NORTH_WEST</property>
+  <signal name="destroy" handler="on_window1_destroy" object="window1"/>
+  <signal name="size_request" handler="on_window1_size_request" object="vpaned1" last_modification_time="Fri, 11 Jan 2002 16:17:11 GMT"/>
+  <signal name="delete_event" handler="on_window1_delete_event" object="window1" last_modification_time="Sun, 09 Mar 2003 19:42:46 GMT"/>
+
+  <child>
+    <widget class="GtkVBox" id="vbox1">
+      <property name="visible">True</property>
+      <property name="homogeneous">False</property>
+      <property name="spacing">0</property>
+
+      <child>
+       <widget class="GtkMenuBar" id="menubar1">
+         <property name="visible">True</property>
+
+         <child>
+           <widget class="GtkMenuItem" id="file1">
+             <property name="visible">True</property>
+             <property name="label" translatable="yes">_File</property>
+             <property name="use_underline">True</property>
+
+             <child>
+               <widget class="GtkMenu" id="file1_menu">
+
+                 <child>
+                   <widget class="GtkImageMenuItem" id="load1">
+                     <property name="visible">True</property>
+                     <property name="tooltip" translatable="yes">Load a config file</property>
+                     <property name="label" translatable="yes">_Load</property>
+                     <property name="use_underline">True</property>
+                     <signal name="activate" handler="on_load1_activate"/>
+                     <accelerator key="L" modifiers="GDK_CONTROL_MASK" signal="activate"/>
+
+                     <child internal-child="image">
+                       <widget class="GtkImage" id="image39">
+                         <property name="visible">True</property>
+                         <property name="stock">gtk-open</property>
+                         <property name="icon_size">1</property>
+                         <property name="xalign">0.5</property>
+                         <property name="yalign">0.5</property>
+                         <property name="xpad">0</property>
+                         <property name="ypad">0</property>
+                       </widget>
+                     </child>
+                   </widget>
+                 </child>
+
+                 <child>
+                   <widget class="GtkImageMenuItem" id="save1">
+                     <property name="visible">True</property>
+                     <property name="tooltip" translatable="yes">Save the config in .config</property>
+                     <property name="label" translatable="yes">_Save</property>
+                     <property name="use_underline">True</property>
+                     <signal name="activate" handler="on_save1_activate"/>
+                     <accelerator key="S" modifiers="GDK_CONTROL_MASK" signal="activate"/>
+
+                     <child internal-child="image">
+                       <widget class="GtkImage" id="image40">
+                         <property name="visible">True</property>
+                         <property name="stock">gtk-save</property>
+                         <property name="icon_size">1</property>
+                         <property name="xalign">0.5</property>
+                         <property name="yalign">0.5</property>
+                         <property name="xpad">0</property>
+                         <property name="ypad">0</property>
+                       </widget>
+                     </child>
+                   </widget>
+                 </child>
+
+                 <child>
+                   <widget class="GtkImageMenuItem" id="save_as1">
+                     <property name="visible">True</property>
+                     <property name="tooltip" translatable="yes">Save the config in a file</property>
+                     <property name="label" translatable="yes">Save _as</property>
+                     <property name="use_underline">True</property>
+                     <signal name="activate" handler="on_save_as1_activate"/>
+
+                     <child internal-child="image">
+                       <widget class="GtkImage" id="image41">
+                         <property name="visible">True</property>
+                         <property name="stock">gtk-save-as</property>
+                         <property name="icon_size">1</property>
+                         <property name="xalign">0.5</property>
+                         <property name="yalign">0.5</property>
+                         <property name="xpad">0</property>
+                         <property name="ypad">0</property>
+                       </widget>
+                     </child>
+                   </widget>
+                 </child>
+
+                 <child>
+                   <widget class="GtkSeparatorMenuItem" id="separator1">
+                     <property name="visible">True</property>
+                   </widget>
+                 </child>
+
+                 <child>
+                   <widget class="GtkImageMenuItem" id="quit1">
+                     <property name="visible">True</property>
+                     <property name="label" translatable="yes">_Quit</property>
+                     <property name="use_underline">True</property>
+                     <signal name="activate" handler="on_quit1_activate"/>
+                     <accelerator key="Q" modifiers="GDK_CONTROL_MASK" signal="activate"/>
+
+                     <child internal-child="image">
+                       <widget class="GtkImage" id="image42">
+                         <property name="visible">True</property>
+                         <property name="stock">gtk-quit</property>
+                         <property name="icon_size">1</property>
+                         <property name="xalign">0.5</property>
+                         <property name="yalign">0.5</property>
+                         <property name="xpad">0</property>
+                         <property name="ypad">0</property>
+                       </widget>
+                     </child>
+                   </widget>
+                 </child>
+               </widget>
+             </child>
+           </widget>
+         </child>
+
+         <child>
+           <widget class="GtkMenuItem" id="options1">
+             <property name="visible">True</property>
+             <property name="label" translatable="yes">_Options</property>
+             <property name="use_underline">True</property>
+
+             <child>
+               <widget class="GtkMenu" id="options1_menu">
+
+                 <child>
+                   <widget class="GtkCheckMenuItem" id="show_name1">
+                     <property name="visible">True</property>
+                     <property name="tooltip" translatable="yes">Show name</property>
+                     <property name="label" translatable="yes">Show _name</property>
+                     <property name="use_underline">True</property>
+                     <property name="active">False</property>
+                     <signal name="activate" handler="on_show_name1_activate"/>
+                   </widget>
+                 </child>
+
+                 <child>
+                   <widget class="GtkCheckMenuItem" id="show_range1">
+                     <property name="visible">True</property>
+                     <property name="tooltip" translatable="yes">Show range (Y/M/N)</property>
+                     <property name="label" translatable="yes">Show _range</property>
+                     <property name="use_underline">True</property>
+                     <property name="active">False</property>
+                     <signal name="activate" handler="on_show_range1_activate"/>
+                   </widget>
+                 </child>
+
+                 <child>
+                   <widget class="GtkCheckMenuItem" id="show_data1">
+                     <property name="visible">True</property>
+                     <property name="tooltip" translatable="yes">Show value of the option</property>
+                     <property name="label" translatable="yes">Show _data</property>
+                     <property name="use_underline">True</property>
+                     <property name="active">False</property>
+                     <signal name="activate" handler="on_show_data1_activate"/>
+                   </widget>
+                 </child>
+
+                 <child>
+                   <widget class="GtkSeparatorMenuItem" id="separator2">
+                     <property name="visible">True</property>
+                   </widget>
+                 </child>
+
+                 <child>
+                   <widget class="GtkCheckMenuItem" id="show_all_options1">
+                     <property name="visible">True</property>
+                     <property name="tooltip" translatable="yes">Show all options</property>
+                     <property name="label" translatable="yes">Show all _options</property>
+                     <property name="use_underline">True</property>
+                     <property name="active">False</property>
+                     <signal name="activate" handler="on_show_all_options1_activate"/>
+                   </widget>
+                 </child>
+
+                 <child>
+                   <widget class="GtkCheckMenuItem" id="show_debug_info1">
+                     <property name="visible">True</property>
+                     <property name="tooltip" translatable="yes">Show masked options</property>
+                     <property name="label" translatable="yes">Show _debug info</property>
+                     <property name="use_underline">True</property>
+                     <property name="active">False</property>
+                     <signal name="activate" handler="on_show_debug_info1_activate"/>
+                   </widget>
+                 </child>
+               </widget>
+             </child>
+           </widget>
+         </child>
+
+         <child>
+           <widget class="GtkMenuItem" id="help1">
+             <property name="visible">True</property>
+             <property name="label" translatable="yes">_Help</property>
+             <property name="use_underline">True</property>
+
+             <child>
+               <widget class="GtkMenu" id="help1_menu">
+
+                 <child>
+                   <widget class="GtkImageMenuItem" id="introduction1">
+                     <property name="visible">True</property>
+                     <property name="label" translatable="yes">_Introduction</property>
+                     <property name="use_underline">True</property>
+                     <signal name="activate" handler="on_introduction1_activate" last_modification_time="Fri, 15 Nov 2002 20:26:30 GMT"/>
+                     <accelerator key="I" modifiers="GDK_CONTROL_MASK" signal="activate"/>
+
+                     <child internal-child="image">
+                       <widget class="GtkImage" id="image43">
+                         <property name="visible">True</property>
+                         <property name="stock">gtk-dialog-question</property>
+                         <property name="icon_size">1</property>
+                         <property name="xalign">0.5</property>
+                         <property name="yalign">0.5</property>
+                         <property name="xpad">0</property>
+                         <property name="ypad">0</property>
+                       </widget>
+                     </child>
+                   </widget>
+                 </child>
+
+                 <child>
+                   <widget class="GtkImageMenuItem" id="about1">
+                     <property name="visible">True</property>
+                     <property name="label" translatable="yes">_About</property>
+                     <property name="use_underline">True</property>
+                     <signal name="activate" handler="on_about1_activate" last_modification_time="Fri, 15 Nov 2002 20:26:30 GMT"/>
+                     <accelerator key="A" modifiers="GDK_CONTROL_MASK" signal="activate"/>
+
+                     <child internal-child="image">
+                       <widget class="GtkImage" id="image44">
+                         <property name="visible">True</property>
+                         <property name="stock">gtk-properties</property>
+                         <property name="icon_size">1</property>
+                         <property name="xalign">0.5</property>
+                         <property name="yalign">0.5</property>
+                         <property name="xpad">0</property>
+                         <property name="ypad">0</property>
+                       </widget>
+                     </child>
+                   </widget>
+                 </child>
+
+                 <child>
+                   <widget class="GtkImageMenuItem" id="license1">
+                     <property name="visible">True</property>
+                     <property name="label" translatable="yes">_License</property>
+                     <property name="use_underline">True</property>
+                     <signal name="activate" handler="on_license1_activate" last_modification_time="Fri, 15 Nov 2002 20:26:30 GMT"/>
+
+                     <child internal-child="image">
+                       <widget class="GtkImage" id="image45">
+                         <property name="visible">True</property>
+                         <property name="stock">gtk-justify-fill</property>
+                         <property name="icon_size">1</property>
+                         <property name="xalign">0.5</property>
+                         <property name="yalign">0.5</property>
+                         <property name="xpad">0</property>
+                         <property name="ypad">0</property>
+                       </widget>
+                     </child>
+                   </widget>
+                 </child>
+               </widget>
+             </child>
+           </widget>
+         </child>
+       </widget>
+       <packing>
+         <property name="padding">0</property>
+         <property name="expand">False</property>
+         <property name="fill">False</property>
+       </packing>
+      </child>
+
+      <child>
+       <widget class="GtkHandleBox" id="handlebox1">
+         <property name="visible">True</property>
+         <property name="shadow_type">GTK_SHADOW_OUT</property>
+         <property name="handle_position">GTK_POS_LEFT</property>
+         <property name="snap_edge">GTK_POS_TOP</property>
+
+         <child>
+           <widget class="GtkToolbar" id="toolbar1">
+             <property name="visible">True</property>
+             <property name="orientation">GTK_ORIENTATION_HORIZONTAL</property>
+             <property name="toolbar_style">GTK_TOOLBAR_BOTH</property>
+             <property name="tooltips">True</property>
+             <property name="show_arrow">True</property>
+
+             <child>
+               <widget class="GtkToolButton" id="button1">
+                 <property name="visible">True</property>
+                 <property name="tooltip" translatable="yes">Goes up of one level (single view)</property>
+                 <property name="label" translatable="yes">Back</property>
+                 <property name="use_underline">True</property>
+                 <property name="stock_id">gtk-undo</property>
+                 <property name="visible_horizontal">True</property>
+                 <property name="visible_vertical">True</property>
+                 <property name="is_important">False</property>
+                 <signal name="clicked" handler="on_back_clicked"/>
+               </widget>
+               <packing>
+                 <property name="expand">False</property>
+                 <property name="homogeneous">True</property>
+               </packing>
+             </child>
+
+             <child>
+               <widget class="GtkToolItem" id="toolitem1">
+                 <property name="visible">True</property>
+                 <property name="visible_horizontal">True</property>
+                 <property name="visible_vertical">True</property>
+                 <property name="is_important">False</property>
+
+                 <child>
+                   <widget class="GtkVSeparator" id="vseparator1">
+                     <property name="visible">True</property>
+                   </widget>
+                 </child>
+               </widget>
+               <packing>
+                 <property name="expand">False</property>
+                 <property name="homogeneous">False</property>
+               </packing>
+             </child>
+
+             <child>
+               <widget class="GtkToolButton" id="button2">
+                 <property name="visible">True</property>
+                 <property name="tooltip" translatable="yes">Load a config file</property>
+                 <property name="label" translatable="yes">Load</property>
+                 <property name="use_underline">True</property>
+                 <property name="stock_id">gtk-open</property>
+                 <property name="visible_horizontal">True</property>
+                 <property name="visible_vertical">True</property>
+                 <property name="is_important">False</property>
+                 <signal name="clicked" handler="on_load_clicked"/>
+               </widget>
+               <packing>
+                 <property name="expand">False</property>
+                 <property name="homogeneous">True</property>
+               </packing>
+             </child>
+
+             <child>
+               <widget class="GtkToolButton" id="button3">
+                 <property name="visible">True</property>
+                 <property name="tooltip" translatable="yes">Save a config file</property>
+                 <property name="label" translatable="yes">Save</property>
+                 <property name="use_underline">True</property>
+                 <property name="stock_id">gtk-save</property>
+                 <property name="visible_horizontal">True</property>
+                 <property name="visible_vertical">True</property>
+                 <property name="is_important">False</property>
+                 <signal name="clicked" handler="on_save_clicked"/>
+               </widget>
+               <packing>
+                 <property name="expand">False</property>
+                 <property name="homogeneous">True</property>
+               </packing>
+             </child>
+
+             <child>
+               <widget class="GtkToolItem" id="toolitem2">
+                 <property name="visible">True</property>
+                 <property name="visible_horizontal">True</property>
+                 <property name="visible_vertical">True</property>
+                 <property name="is_important">False</property>
+
+                 <child>
+                   <widget class="GtkVSeparator" id="vseparator2">
+                     <property name="visible">True</property>
+                   </widget>
+                 </child>
+               </widget>
+               <packing>
+                 <property name="expand">False</property>
+                 <property name="homogeneous">False</property>
+               </packing>
+             </child>
+
+             <child>
+               <widget class="GtkToolButton" id="button4">
+                 <property name="visible">True</property>
+                 <property name="tooltip" translatable="yes">Single view</property>
+                 <property name="label" translatable="yes">Single</property>
+                 <property name="use_underline">True</property>
+                 <property name="stock_id">gtk-missing-image</property>
+                 <property name="visible_horizontal">True</property>
+                 <property name="visible_vertical">True</property>
+                 <property name="is_important">False</property>
+                 <signal name="clicked" handler="on_single_clicked" last_modification_time="Sun, 12 Jan 2003 14:28:39 GMT"/>
+               </widget>
+               <packing>
+                 <property name="expand">False</property>
+                 <property name="homogeneous">True</property>
+               </packing>
+             </child>
+
+             <child>
+               <widget class="GtkToolButton" id="button5">
+                 <property name="visible">True</property>
+                 <property name="tooltip" translatable="yes">Split view</property>
+                 <property name="label" translatable="yes">Split</property>
+                 <property name="use_underline">True</property>
+                 <property name="stock_id">gtk-missing-image</property>
+                 <property name="visible_horizontal">True</property>
+                 <property name="visible_vertical">True</property>
+                 <property name="is_important">False</property>
+                 <signal name="clicked" handler="on_split_clicked" last_modification_time="Sun, 12 Jan 2003 14:28:45 GMT"/>
+               </widget>
+               <packing>
+                 <property name="expand">False</property>
+                 <property name="homogeneous">True</property>
+               </packing>
+             </child>
+
+             <child>
+               <widget class="GtkToolButton" id="button6">
+                 <property name="visible">True</property>
+                 <property name="tooltip" translatable="yes">Full view</property>
+                 <property name="label" translatable="yes">Full</property>
+                 <property name="use_underline">True</property>
+                 <property name="stock_id">gtk-missing-image</property>
+                 <property name="visible_horizontal">True</property>
+                 <property name="visible_vertical">True</property>
+                 <property name="is_important">False</property>
+                 <signal name="clicked" handler="on_full_clicked" last_modification_time="Sun, 12 Jan 2003 14:28:50 GMT"/>
+               </widget>
+               <packing>
+                 <property name="expand">False</property>
+                 <property name="homogeneous">True</property>
+               </packing>
+             </child>
+
+             <child>
+               <widget class="GtkToolItem" id="toolitem3">
+                 <property name="visible">True</property>
+                 <property name="visible_horizontal">True</property>
+                 <property name="visible_vertical">True</property>
+                 <property name="is_important">False</property>
+
+                 <child>
+                   <widget class="GtkVSeparator" id="vseparator3">
+                     <property name="visible">True</property>
+                   </widget>
+                 </child>
+               </widget>
+               <packing>
+                 <property name="expand">False</property>
+                 <property name="homogeneous">False</property>
+               </packing>
+             </child>
+
+             <child>
+               <widget class="GtkToolButton" id="button7">
+                 <property name="visible">True</property>
+                 <property name="tooltip" translatable="yes">Collapse the whole tree in the right frame</property>
+                 <property name="label" translatable="yes">Collapse</property>
+                 <property name="use_underline">True</property>
+                 <property name="stock_id">gtk-remove</property>
+                 <property name="visible_horizontal">True</property>
+                 <property name="visible_vertical">True</property>
+                 <property name="is_important">False</property>
+                 <signal name="clicked" handler="on_collapse_clicked"/>
+               </widget>
+               <packing>
+                 <property name="expand">False</property>
+                 <property name="homogeneous">True</property>
+               </packing>
+             </child>
+
+             <child>
+               <widget class="GtkToolButton" id="button8">
+                 <property name="visible">True</property>
+                 <property name="tooltip" translatable="yes">Expand the whole tree in the right frame</property>
+                 <property name="label" translatable="yes">Expand</property>
+                 <property name="use_underline">True</property>
+                 <property name="stock_id">gtk-add</property>
+                 <property name="visible_horizontal">True</property>
+                 <property name="visible_vertical">True</property>
+                 <property name="is_important">False</property>
+                 <signal name="clicked" handler="on_expand_clicked"/>
+               </widget>
+               <packing>
+                 <property name="expand">False</property>
+                 <property name="homogeneous">True</property>
+               </packing>
+             </child>
+           </widget>
+         </child>
+       </widget>
+       <packing>
+         <property name="padding">0</property>
+         <property name="expand">False</property>
+         <property name="fill">False</property>
+       </packing>
+      </child>
+
+      <child>
+       <widget class="GtkHPaned" id="hpaned1">
+         <property name="width_request">1</property>
+         <property name="visible">True</property>
+         <property name="can_focus">True</property>
+         <property name="position">0</property>
+
+         <child>
+           <widget class="GtkScrolledWindow" id="scrolledwindow1">
+             <property name="visible">True</property>
+             <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+             <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+             <property name="shadow_type">GTK_SHADOW_IN</property>
+             <property name="window_placement">GTK_CORNER_TOP_LEFT</property>
+
+             <child>
+               <widget class="GtkTreeView" id="treeview1">
+                 <property name="visible">True</property>
+                 <property name="can_focus">True</property>
+                 <property name="headers_visible">True</property>
+                 <property name="rules_hint">False</property>
+                 <property name="reorderable">False</property>
+                 <property name="enable_search">True</property>
+                 <signal name="cursor_changed" handler="on_treeview2_cursor_changed" last_modification_time="Sun, 12 Jan 2003 15:58:22 GMT"/>
+                 <signal name="button_press_event" handler="on_treeview1_button_press_event" last_modification_time="Sun, 12 Jan 2003 16:03:52 GMT"/>
+                 <signal name="key_press_event" handler="on_treeview2_key_press_event" last_modification_time="Sun, 12 Jan 2003 16:11:44 GMT"/>
+               </widget>
+             </child>
+           </widget>
+           <packing>
+             <property name="shrink">True</property>
+             <property name="resize">False</property>
+           </packing>
+         </child>
+
+         <child>
+           <widget class="GtkVPaned" id="vpaned1">
+             <property name="visible">True</property>
+             <property name="can_focus">True</property>
+             <property name="position">0</property>
+
+             <child>
+               <widget class="GtkScrolledWindow" id="scrolledwindow2">
+                 <property name="visible">True</property>
+                 <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+                 <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+                 <property name="shadow_type">GTK_SHADOW_IN</property>
+                 <property name="window_placement">GTK_CORNER_TOP_LEFT</property>
+
+                 <child>
+                   <widget class="GtkTreeView" id="treeview2">
+                     <property name="visible">True</property>
+                     <property name="can_focus">True</property>
+                     <property name="has_focus">True</property>
+                     <property name="headers_visible">True</property>
+                     <property name="rules_hint">False</property>
+                     <property name="reorderable">False</property>
+                     <property name="enable_search">True</property>
+                     <signal name="cursor_changed" handler="on_treeview2_cursor_changed" last_modification_time="Sun, 12 Jan 2003 15:57:55 GMT"/>
+                     <signal name="button_press_event" handler="on_treeview2_button_press_event" last_modification_time="Sun, 12 Jan 2003 15:57:58 GMT"/>
+                     <signal name="key_press_event" handler="on_treeview2_key_press_event" last_modification_time="Sun, 12 Jan 2003 15:58:01 GMT"/>
+                   </widget>
+                 </child>
+               </widget>
+               <packing>
+                 <property name="shrink">True</property>
+                 <property name="resize">False</property>
+               </packing>
+             </child>
+
+             <child>
+               <widget class="GtkScrolledWindow" id="scrolledwindow3">
+                 <property name="visible">True</property>
+                 <property name="hscrollbar_policy">GTK_POLICY_NEVER</property>
+                 <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+                 <property name="shadow_type">GTK_SHADOW_IN</property>
+                 <property name="window_placement">GTK_CORNER_TOP_LEFT</property>
+
+                 <child>
+                   <widget class="GtkTextView" id="textview3">
+                     <property name="visible">True</property>
+                     <property name="can_focus">True</property>
+                     <property name="editable">False</property>
+                     <property name="overwrite">False</property>
+                     <property name="accepts_tab">True</property>
+                     <property name="justification">GTK_JUSTIFY_LEFT</property>
+                     <property name="wrap_mode">GTK_WRAP_WORD</property>
+                     <property name="cursor_visible">True</property>
+                     <property name="pixels_above_lines">0</property>
+                     <property name="pixels_below_lines">0</property>
+                     <property name="pixels_inside_wrap">0</property>
+                     <property name="left_margin">0</property>
+                     <property name="right_margin">0</property>
+                     <property name="indent">0</property>
+                     <property name="text" translatable="yes">Sorry, no help available for this option yet.</property>
+                   </widget>
+                 </child>
+               </widget>
+               <packing>
+                 <property name="shrink">True</property>
+                 <property name="resize">True</property>
+               </packing>
+             </child>
+           </widget>
+           <packing>
+             <property name="shrink">True</property>
+             <property name="resize">True</property>
+           </packing>
+         </child>
+       </widget>
+       <packing>
+         <property name="padding">0</property>
+         <property name="expand">True</property>
+         <property name="fill">True</property>
+       </packing>
+      </child>
+    </widget>
+  </child>
+</widget>
+
+</glade-interface>
diff --git a/scripts/kconfig/images.c b/scripts/kconfig/images.c
new file mode 100644 (file)
index 0000000..d4f84bd
--- /dev/null
@@ -0,0 +1,326 @@
+/*
+ * Copyright (C) 2002 Roman Zippel <zippel@linux-m68k.org>
+ * 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 (file)
index 0000000..5e87dd5
--- /dev/null
@@ -0,0 +1,35 @@
+#include <dlfcn.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#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 (file)
index 0000000..abee55c
--- /dev/null
@@ -0,0 +1,227 @@
+/*
+ * Arnaldo Carvalho de Melo <acme@conectiva.com.br>, 2005
+ *
+ * Released under the terms of the GNU GPL v2.0
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+#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 (file)
index 0000000..5fc323d
--- /dev/null
@@ -0,0 +1,2325 @@
+
+#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 <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <stdlib.h>
+
+/* end standard C headers. */
+
+/* flex integer type definitions */
+
+#ifndef FLEXINT_H
+#define FLEXINT_H
+
+/* C99 systems have <inttypes.h>. Non-C99 systems may or may not. */
+
+#if defined __STDC_VERSION__ && __STDC_VERSION__ >= 199901L
+#include <inttypes.h>
+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 <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 (size > 70) {
+               fprintf (stderr, "%s:%d error: Overlong line\n",
+               current_file->name, current_file->lineno);
+       }
+
+       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 <unistd.h>
+#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
+
+//bbox: suppressing "defined but not used" warning
+#define YY_NO_INPUT 1
+
+#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 : "<none>";
+}
+
diff --git a/scripts/kconfig/lkc.h b/scripts/kconfig/lkc.h
new file mode 100644 (file)
index 0000000..527f60c
--- /dev/null
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2002 Roman Zippel <zippel@linux-m68k.org>
+ * Released under the terms of the GNU GPL v2.0.
+ */
+
+#ifndef LKC_H
+#define LKC_H
+
+#include "expr.h"
+
+#ifndef KBUILD_NO_NLS
+# include <libintl.h>
+#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 (file)
index 0000000..b6a389c
--- /dev/null
@@ -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 (file)
index 0000000..4cadb80
--- /dev/null
@@ -0,0 +1,4 @@
+This is NOT the official version of dialog.  This version has been
+significantly modified from the original.  It was used by the Linux
+kernel configuration script, and subsequently adapted for busybox.
+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 (file)
index 0000000..2c9dc48
--- /dev/null
@@ -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_EXTRACFLAGS) $(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 (file)
index 0000000..5552154
--- /dev/null
@@ -0,0 +1,82 @@
+#!/bin/sh
+# Check ncurses compatibility
+
+# What library to link
+ldflags()
+{
+       for ext in so a dylib ; do
+               for lib in ncursesw ncurses curses ; do
+                       $cc -print-file-name=lib${lib}.${ext} | grep -q /
+                       if [ $? -eq 0 ]; then
+                               echo "-l${lib}"
+                               exit
+                       fi
+               done
+       done
+       exit 1
+}
+
+# Where is ncurses.h?
+ccflags()
+{
+       if [ -f /usr/include/ncurses/ncurses.h ]; then
+               echo '-I/usr/include/ncurses -DCURSES_LOC="<ncurses.h>"'
+       elif [ -f /usr/include/ncurses/curses.h ]; then
+               echo '-I/usr/include/ncurses -DCURSES_LOC="<ncurses/curses.h>"'
+       elif [ -f /usr/include/ncurses.h ]; then
+               echo '-DCURSES_LOC="<ncurses.h>"'
+       else
+               echo '-DCURSES_LOC="<curses.h>"'
+       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() {
+        $cc -xc - -o $tmp 2>/dev/null <<'EOF'
+#include CURSES_LOC
+main() {}
+EOF
+       if [ $? != 0 ]; then
+           echo " *** Unable to find the ncurses libraries or the"       1>&2
+           echo " *** required header files."                            1>&2
+           echo " *** 'make menuconfig' requires 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 [ $# -eq 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 (file)
index 0000000..be0200e
--- /dev/null
@@ -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 (file)
index 0000000..db071df
--- /dev/null
@@ -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 (file)
index 0000000..af3cf71
--- /dev/null
@@ -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 <sys/types.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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 (file)
index 0000000..7795037
--- /dev/null
@@ -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 (file)
index 0000000..79f6c5f
--- /dev/null
@@ -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 <locale.h>
+#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 <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 (file)
index 0000000..1fd501b
--- /dev/null
@@ -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 (file)
index 0000000..7323f54
--- /dev/null
@@ -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 (file)
index 0000000..77848bb
--- /dev/null
@@ -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 (file)
index 0000000..072d3ee
--- /dev/null
@@ -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 = strchr(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 = strchr(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 (file)
index 0000000..cb2568a
--- /dev/null
@@ -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 (file)
index 0000000..647ec09
--- /dev/null
@@ -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 features may be built directly into busybox.\n"
+"Some may be made into standalone applets.  Some features\n"
+"may be completely removed altogether.  There are also certain\n"
+"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 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 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 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\n"
+       "configurations available on a single machine.\n"
+       "\n"
+       "If you have saved a previous configuration in a file other than\n"
+       "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\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, _("BusyBox %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 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 configuration.\n"
+                               "Your configuration changes were NOT saved."
+                               "\n\n"));
+                       return 1;
+               }
+               printf(_("\n\n"
+                       "*** End of configuration.\n"
+                       "*** Execute 'make' to build the project or try 'make help'."
+                       "\n\n"));
+       } else {
+               fprintf(stderr, _("\n\n"
+                       "Your 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 (file)
index 0000000..0fce20c
--- /dev/null
@@ -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 = &current_entry->list;
+       return current_menu = current_entry;
+}
+
+void menu_end_menu(void)
+{
+       last_entry_ptr = &current_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 (file)
index 0000000..2a189c1
--- /dev/null
@@ -0,0 +1,1425 @@
+/*
+ * 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, "&lt;");
+                       i += 4;
+                       break;
+               case '>':
+                       res.replace(i, 1, "&gt;");
+                       i += 4;
+                       break;
+               case '&':
+                       res.replace(i, 1, "&amp;");
+                       i += 5;
+                       break;
+               case '"':
+                       res.replace(i, 1, "&quot;");
+                       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 += "&nbsp;&nbsp;&nbsp;&nbsp;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 += "&nbsp;&nbsp;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 configuration tool.\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";
+
+       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 (file)
index 0000000..e52f3e9
--- /dev/null
@@ -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 (file)
index 0000000..3d7877a
--- /dev/null
@@ -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 (file)
index 0000000..0461a5f
--- /dev/null
@@ -0,0 +1,115 @@
+/*
+ * 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"
+               "include/autoconf.h: .config\n" /* bbox */
+               "\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 (file)
index 0000000..b032206
--- /dev/null
@@ -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 (file)
index 0000000..345f0fc
--- /dev/null
@@ -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 (file)
index 0000000..d839577
--- /dev/null
@@ -0,0 +1,354 @@
+%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 (size > 70) {
+               fprintf (stderr, "%s:%d error: Overlong line\n",
+                        current_file->name, current_file->lineno);
+       }
+       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 (file)
index 0000000..b62724d
--- /dev/null
@@ -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
+
+\f
+
+#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 */
+
+\f
+
+#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;
+    }
+}
+\f
+
+/* 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 through 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) 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.  */
+
+\f
+  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 (file)
index 0000000..0a7a796
--- /dev/null
@@ -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/memusage b/scripts/memusage
new file mode 100755 (executable)
index 0000000..4ef5608
--- /dev/null
@@ -0,0 +1,16 @@
+#!/bin/sh
+
+busybox=../busybox
+
+i=4000
+echo "Before we started $i copies of '$busybox sleep 10':"
+$busybox nmeter '%t %[pn] %m' | head -3
+
+while test $i != 0; do
+    $busybox sleep 10 &
+    i=$((i-1))
+done
+sleep 1
+
+echo "After:"
+$busybox nmeter '%t %[pn] %m' | head -3
diff --git a/scripts/mkconfigs b/scripts/mkconfigs
new file mode 100755 (executable)
index 0000000..0d1771a
--- /dev/null
@@ -0,0 +1,52 @@
+#!/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 bbconfigopts.h from .config:
+#      - Retain lines that begin with "CONFIG_"
+#      - Retain lines that begin with "# CONFIG_"
+#      - lines that use double-quotes must \\-escape-quote them
+
+config="$1"
+if [ $# -lt 1 ]
+then
+       config=.config
+fi
+
+echo "\
+#ifndef _BBCONFIGOPTS_H
+#define _BBCONFIGOPTS_H
+/*
+ * busybox configuration settings.
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ *
+ * This file is generated automatically by scripts/mkconfigs.
+ * Do not edit.
+ *
+ */
+static const char *const bbconfig_config ="
+
+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 (executable)
index 0000000..7f9d544
--- /dev/null
@@ -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 (executable)
index 0000000..6df0c4f
--- /dev/null
@@ -0,0 +1,19 @@
+#!/bin/sh
+
+t_text=0
+t_data=0
+t_bss=0
+
+printf "%9s %11s %9s %9s %s\n" "text+data" "text+rodata" rwdata bss filename
+
+find -name '*.o' | grep -v '^\./scripts/' | grep -vF built-in.o \
+| sed 's:^\./::' | xargs "${CROSS_COMPILE}size" | grep '^ *[0-9]' \
+| {
+while read text data bss dec hex filename; do
+    t_text=$((t_text+text))
+    t_data=$((t_data+data))
+    t_bss=$((t_bss+bss))
+    printf "%9d %11d %9d %9d %s\n" $((text+data)) $text $data $bss "$filename"
+done
+printf "%9d %11d %9d %9d %s\n" $((t_text+t_data)) $t_text $t_data $t_bss "TOTAL"
+} | env -uLANG -uLC_ALL sort -r
diff --git a/scripts/randomtest b/scripts/randomtest
new file mode 100755 (executable)
index 0000000..eebf1c5
--- /dev/null
@@ -0,0 +1,92 @@
+#!/bin/sh
+
+# Select which libc to build against
+libc="glibc" # assumed native
+# static, cross-compilation
+libc="uclibc"
+# x86 32-bit:
+uclibc_cross="i486-linux-uclibc-"
+# My system has strange prefix for x86 64-bit uclibc:
+#uclibc_cross="x86_64-pc-linux-gnu-"
+
+test -d tree || exit 1
+
+dir=test.$$
+while test -e "$dir" -o -e failed."$dir"; do
+    dir=test."$RANDOM"
+done
+
+cp -dpr tree "$dir" || exit 1
+cd "$dir" || exit 1
+
+echo "Running randconfig test in $dir..." >&2
+
+make randconfig >/dev/null || exit 1
+
+cat .config \
+| grep -v ^CONFIG_DEBUG_PESSIMIZE= \
+| grep -v CONFIG_WERROR \
+| cat >.config.new
+mv .config.new .config
+echo CONFIG_WERROR=y >>.config
+
+test "$libc" = glibc && {
+cat .config \
+| grep -v ^CONFIG_SELINUX= \
+| grep -v ^CONFIG_EFENCE= \
+| grep -v ^CONFIG_DMALLOC= \
+| cat >.config.new
+mv .config.new .config
+}
+
+test "$libc" = uclibc && {
+cat .config \
+| grep -v ^CONFIG_SELINUX= \
+| grep -v ^CONFIG_EFENCE= \
+| grep -v ^CONFIG_DMALLOC= \
+| grep -v ^CONFIG_BUILD_LIBBUSYBOX= \
+| grep -v ^CONFIG_PAM= \
+| grep -v ^CONFIG_TASKSET= \
+| grep -v ^CONFIG_FEATURE_ASSUME_UNICODE= \
+| grep -v ^CONFIG_PIE= \
+| grep -v CONFIG_STATIC \
+| grep -v CONFIG_CROSS_COMPILER_PREFIX \
+| cat >.config.new
+mv .config.new .config
+echo 'CONFIG_CROSS_COMPILER_PREFIX="'"$uclibc_cross"'"' >>.config
+echo 'CONFIG_STATIC=y' >>.config
+}
+
+# If NOMMU, remove some things
+grep -q ^CONFIG_NOMMU= .config && {
+cat .config \
+| grep -v ^CONFIG_ASH= \
+| grep -v ^CONFIG_FEATURE_SH_IS_ASH= \
+| cat >.config.new
+mv .config.new .config
+}
+
+# If STATIC, remove some things
+# PAM with static linking is probably pointless
+# (but I need to try - now I don't have libpam.a on my system, only libpam.so)
+grep -q ^CONFIG_STATIC= .config && {
+cat .config \
+| grep -v ^CONFIG_PAM= \
+| cat >.config.new
+mv .config.new .config
+}
+
+# Regenerate .config with default answers for yanked-off options
+{ yes "" | make oldconfig >/dev/null; } || exit 1
+
+nice -n 10 make 2>&1 | tee -a make.log
+
+test -x busybox && {
+    cd ..
+    rm -rf "$dir"
+    exit 0
+}
+
+cd ..
+mv "$dir" "failed.$dir"
+exit 1
diff --git a/scripts/randomtest.loop b/scripts/randomtest.loop
new file mode 100755 (executable)
index 0000000..28edb67
--- /dev/null
@@ -0,0 +1,10 @@
+#!/bin/sh
+
+cnt=0
+fail=0
+
+while sleep 1; do
+    echo "Passes: $cnt Failures: $fail"
+    ./randomtest >/dev/null || exit #let fail++
+    let cnt++
+done
diff --git a/scripts/sample_pmap b/scripts/sample_pmap
new file mode 100755 (executable)
index 0000000..e7fb457
--- /dev/null
@@ -0,0 +1,11 @@
+#!/bin/sh
+
+busybox=../busybox
+
+$busybox sleep 10 &
+pid=$!
+sleep 1
+
+echo "Memory map of '$busybox sleep 10':"
+size $busybox
+pmap $pid | env - grep "^[0-9a-f][0-9a-f]* " | sort -r -t " " -k2,999
diff --git a/scripts/showasm b/scripts/showasm
new file mode 100755 (executable)
index 0000000..0464426
--- /dev/null
@@ -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 (executable)
index 0000000..7ea1d5c
--- /dev/null
@@ -0,0 +1,305 @@
+#!/bin/sh
+
+debug=false
+
+# Linker flags used:
+#
+# Informational:
+# --warn-common
+# -Map $EXE.map
+# --verbose
+#
+# Optimizations:
+# --sort-common                 reduces padding
+# --sort-section alignment      reduces padding
+# --gc-sections                 throws out unused sections,
+#                               does not work for shared libs
+# -On                           Not used, maybe useful?
+#
+# List of files to link:
+# $l_list                       == --start-group -llib1 -llib2 --end-group
+# --start-group $O_FILES $A_FILES --end-group
+#
+# Shared library link:
+# -shared                       self-explanatory
+# -fPIC                         position-independent code
+# --enable-new-dtags            ?
+# -z,combreloc                  ?
+# -soname="libbusybox.so.$BB_VER"
+# --undefined=lbb_main          Seed name to start pulling from
+#                               (otherwise we'll need --whole-archive)
+# -static                       Not used, but may be useful! manpage:
+#                               "... This option can be used with -shared.
+#                               Doing so means that a shared library
+#                               is being created but that all of the library's
+#                               external references must be resolved by pulling
+#                               in entries from static libraries."
+
+
+try() {
+    printf "%s\n" "Output of:" >$EXE.out
+    printf "%s\n" "$*" >>$EXE.out
+    printf "%s\n" "==========" >>$EXE.out
+    $debug && echo "Trying: $*"
+    $@ >>$EXE.out 2>&1
+    return $?
+}
+
+check_cc() {
+    local tempname="/tmp/temp.$$.$RANDOM"
+    # Can use "-o /dev/null", but older gcc tend to *unlink it* on failure! :(
+    # "-xc": C language. "/dev/null" is an empty source file.
+    if $CC $1 -shared -xc /dev/null -o "$tempname".o >/dev/null 2>&1; then
+       echo "$1";
+    else
+       echo "$2";
+    fi
+    rm "$tempname".o 2>/dev/null
+}
+
+check_libc_is_glibc() {
+    local tempname="/tmp/temp.$$.$RANDOM"
+    echo "\
+       #include <stdlib.h>
+       /* Apparently uclibc defines __GLIBC__ (compat trick?). Oh well. */
+       #if defined(__GLIBC__) && !defined(__UCLIBC__)
+       syntax error here
+       #endif
+       " >"$tempname".c
+    if $CC "$tempname".c -c -o "$tempname".o >/dev/null 2>&1; then
+       echo "$2";
+    else
+       echo "$1";
+    fi
+    rm "$tempname".c "$tempname".o 2>/dev/null
+}
+
+EXE="$1"
+CC="$2"
+CFLAGS="$3"
+LDFLAGS="$4"
+O_FILES="$5"
+A_FILES="$6"
+LDLIBS="$7"
+
+# The --sort-section option is not supported by older versions of ld
+SORT_SECTION=`check_cc "-Wl,--sort-section,alignment" ""`
+
+# Static linking against glibc produces buggy executables
+# (glibc does not cope well with ld --gc-sections).
+# See sources.redhat.com/bugzilla/show_bug.cgi?id=3400
+# Note that glibc is unsuitable for static linking anyway.
+# We are removing -Wl,--gc-sections from link command line.
+GC_SECTIONS=`(
+. ./.config
+if test x"$CONFIG_STATIC" = x"y"; then
+    check_libc_is_glibc "" "-Wl,--gc-sections"
+else
+    echo "-Wl,--gc-sections"
+fi
+)`
+
+# Sanitize lib list (dups, extra spaces etc)
+LDLIBS=`echo "$LDLIBS" | xargs -n1 | sort | uniq | xargs`
+
+# First link with all libs. If it fails, bail out
+echo "Trying libraries: $LDLIBS"
+# "lib1 lib2 lib3" -> "-llib1 -llib2 -llib3"
+l_list=`echo "$LDLIBS" | sed -e 's/ / -l/g' -e 's/^/-l/' -e 's/^-l$//'`
+test "x$l_list" != "x" && l_list="-Wl,--start-group $l_list -Wl,--end-group"
+try $CC $CFLAGS $LDFLAGS \
+       -o $EXE \
+       -Wl,--sort-common \
+       $SORT_SECTION \
+       $GC_SECTIONS \
+       -Wl,--start-group $O_FILES $A_FILES -Wl,--end-group \
+       $l_list \
+|| {
+    echo "Failed: $l_list"
+    cat $EXE.out
+    exit 1
+}
+
+# Now try to remove each lib and build without it.
+# Stop when no lib can be removed.
+while test "$LDLIBS"; do
+    $debug && echo "Trying libraries: $LDLIBS"
+    all_needed=true
+    last_needed=false
+    for one in $LDLIBS; do
+       without_one=`echo " $LDLIBS " | sed "s/ $one / /g" | xargs`
+       # "lib1 lib2 lib3" -> "-llib1 -llib2 -llib3"
+       l_list=`echo "$without_one" | sed -e 's/ / -l/g' -e 's/^/-l/' -e 's/^-l$//'`
+       test x"$l_list" != x"" && l_list="-Wl,--start-group $l_list -Wl,--end-group"
+       $debug && echo "Trying -l options: '$l_list'"
+       try $CC $CFLAGS $LDFLAGS \
+               -o $EXE \
+               -Wl,--sort-common \
+               $SORT_SECTION \
+               $GC_SECTIONS \
+               -Wl,--start-group $O_FILES $A_FILES -Wl,--end-group \
+               $l_list
+       if test $? = 0; then
+           echo " Library $one is not needed, excluding it"
+           LDLIBS="$without_one"
+           all_needed=false
+           last_needed=false
+       else
+           echo " Library $one is needed, can't exclude it (yet)"
+           last_needed=true
+       fi
+    done
+    # All libs were needed, can't remove any
+    $all_needed && break
+    # Optimization: was the last tried lib needed?
+    if $last_needed; then
+       # Was it the only one lib left? Don't test again then.
+       { echo "$LDLIBS" | grep -q ' '; } || break
+    fi
+done
+
+# Make the binary with final, minimal list of libs
+echo "Final link with: ${LDLIBS:-<none>}"
+l_list=`echo "$LDLIBS" | sed -e 's/ / -l/g' -e 's/^/-l/' -e 's/^-l$//'`
+test "x$l_list" != "x" && l_list="-Wl,--start-group $l_list -Wl,--end-group"
+# --verbose gives us gobs of info to stdout (e.g. linker script used)
+if ! test -f busybox_ldscript; then
+    try $CC $CFLAGS $LDFLAGS \
+           -o $EXE \
+           -Wl,--sort-common \
+           $SORT_SECTION \
+           $GC_SECTIONS \
+           -Wl,--start-group $O_FILES $A_FILES -Wl,--end-group \
+           $l_list \
+           -Wl,--warn-common \
+           -Wl,-Map,$EXE.map \
+           -Wl,--verbose \
+    || {
+       cat $EXE.out
+       exit 1
+    }
+else
+    echo "Custom linker script 'busybox_ldscript' found, using it"
+    # Add SORT_BY_ALIGNMENT to linker script (found in $EXE.out):
+    #  .rodata         : { *(.rodata SORT_BY_ALIGNMENT(.rodata.*) .gnu.linkonce.r.*) }
+    #  *(.data SORT_BY_ALIGNMENT(.data.*) .gnu.linkonce.d.*)
+    #  *(.bss SORT_BY_ALIGNMENT(.bss.*) .gnu.linkonce.b.*)
+    # This will eliminate most of the padding (~3kb).
+    # Hmm, "ld --sort-section alignment" should do it too.
+    try $CC $CFLAGS $LDFLAGS \
+           -o $EXE \
+           -Wl,--sort-common \
+           $SORT_SECTION \
+           $GC_SECTIONS \
+           -Wl,-T,busybox_ldscript \
+           -Wl,--start-group $O_FILES $A_FILES -Wl,--end-group \
+           $l_list \
+           -Wl,--warn-common \
+           -Wl,-Map,$EXE.map \
+           -Wl,--verbose \
+    || {
+       cat $EXE.out
+       exit 1
+    }
+fi
+
+. ./.config
+
+sharedlib_dir="0_lib"
+
+if test "$CONFIG_BUILD_LIBBUSYBOX" = y; then
+    mkdir "$sharedlib_dir" 2>/dev/null
+    test -d "$sharedlib_dir" || {
+       echo "Cannot make directory $sharedlib_dir"
+       exit 1
+    }
+    ln -s "libbusybox.so.$BB_VER" "$sharedlib_dir"/libbusybox.so 2>/dev/null
+
+    EXE="$sharedlib_dir/libbusybox.so.${BB_VER}_unstripped"
+    try $CC $CFLAGS $LDFLAGS \
+           -o $EXE \
+           -shared -fPIC \
+           -Wl,--enable-new-dtags \
+           -Wl,-z,combreloc \
+           -Wl,-soname="libbusybox.so.$BB_VER" \
+           -Wl,--undefined=lbb_main \
+           -Wl,--sort-common \
+           $SORT_SECTION \
+           -Wl,--start-group $A_FILES -Wl,--end-group \
+           $l_list \
+           -Wl,--warn-common \
+           -Wl,-Map,$EXE.map \
+           -Wl,--verbose \
+    || {
+       echo "Linking $EXE failed"
+       cat $EXE.out
+       exit 1
+    }
+    $STRIP -s --remove-section=.note --remove-section=.comment $EXE -o "$sharedlib_dir/libbusybox.so.$BB_VER"
+    chmod a+x "$sharedlib_dir/libbusybox.so.$BB_VER"
+    echo "libbusybox: $sharedlib_dir/libbusybox.so.$BB_VER"
+fi
+
+if test "$CONFIG_FEATURE_SHARED_BUSYBOX" = y; then
+    EXE="$sharedlib_dir/busybox_unstripped"
+    try $CC $CFLAGS $LDFLAGS \
+           -o $EXE \
+           -Wl,--sort-common \
+           $SORT_SECTION \
+           $GC_SECTIONS \
+           -Wl,--start-group $O_FILES -Wl,--end-group \
+           -L"$sharedlib_dir" -lbusybox \
+           -Wl,--warn-common \
+           -Wl,-Map,$EXE.map \
+           -Wl,--verbose \
+    || {
+       echo "Linking $EXE failed"
+       cat $EXE.out
+       exit 1
+    }
+    $STRIP -s --remove-section=.note --remove-section=.comment $EXE -o "$sharedlib_dir/busybox"
+    echo "busybox linked against libbusybox: $sharedlib_dir/busybox"
+fi
+
+if test "$CONFIG_FEATURE_INDIVIDUAL" = y; then
+    echo "Linking individual applets against libbusybox (see $sharedlib_dir/*)"
+    gcc -DNAME_MAIN_CNAME -E -include include/autoconf.h include/applets.h \
+    | grep -v "^#" \
+    | grep -v "^$" \
+    > applet_lst.tmp
+    while read name main junk; do
+
+       echo "\
+void lbb_prepare(const char *applet, char **argv);
+int $main(int argc, char **argv);
+
+int main(int argc, char **argv)
+{
+       lbb_prepare(\"$name\", argv);
+       return $main(argc, argv);
+}
+" >"$sharedlib_dir/applet.c"
+
+       EXE="$sharedlib_dir/$name"
+       try $CC $CFLAGS $LDFLAGS "$sharedlib_dir/applet.c" \
+               -o $EXE \
+               -Wl,--sort-common \
+               $SORT_SECTION \
+               $GC_SECTIONS \
+               -L"$sharedlib_dir" -lbusybox \
+               -Wl,--warn-common \
+       || {
+           echo "Linking $EXE failed"
+           cat $EXE.out
+           exit 1
+       }
+       rm -- "$sharedlib_dir/applet.c" $EXE.out
+       $STRIP -s --remove-section=.note --remove-section=.comment $EXE
+
+    done <applet_lst.tmp
+fi
+
+# libbusybox.so is needed only for -lbusybox at link time,
+# it is not needed at runtime. Deleting to reduce confusion.
+rm "$sharedlib_dir"/libbusybox.so 2>/dev/null
+exit 0 # or else we may confuse make
diff --git a/selinux/Config.in b/selinux/Config.in
new file mode 100644 (file)
index 0000000..e795e73
--- /dev/null
@@ -0,0 +1,123 @@
+#
+# For a description of the syntax of this configuration file,
+# see scripts/kbuild/config-language.txt.
+#
+
+menu "SELinux Utilities"
+       depends on SELINUX
+
+config CHCON
+       bool "chcon"
+       default n
+       depends on SELINUX
+       help
+         Enable support to change the security context of file.
+
+config FEATURE_CHCON_LONG_OPTIONS
+       bool "Enable long options"
+       default y
+       depends on CHCON && GETOPT_LONG
+       help
+         Support long options for the chcon applet.
+
+config GETENFORCE
+       bool "getenforce"
+       default n
+       depends on SELINUX
+       help
+         Enable support to get the current mode of SELinux.
+
+config GETSEBOOL
+       bool "getsebool"
+       default n
+       depends on SELINUX
+       help
+         Enable support to get SELinux boolean values.
+
+config LOAD_POLICY
+       bool "load_policy"
+       default n
+       depends on SELINUX
+       help
+         Enable support to load SELinux policy.
+
+config MATCHPATHCON
+       bool "matchpathcon"
+       default n
+       depends on SELINUX
+       help
+         Enable support to get default security context of the
+         specified path from the file contexts configuration.
+
+config RESTORECON
+       bool "restorecon"
+       default n
+       depends on SELINUX
+       help
+         Enable support to relabel files. The feature is almost
+         the same as setfiles, but usage is a little different.
+
+config RUNCON
+       bool "runcon"
+       default n
+       depends on SELINUX
+       help
+         Enable support to run command in speficied security context.
+
+config FEATURE_RUNCON_LONG_OPTIONS
+       bool "Enable long options"
+       default y
+       depends on RUNCON && GETOPT_LONG
+       help
+         Support long options for the runcon applet.
+
+config SELINUXENABLED
+       bool "selinuxenabled"
+       default n
+       depends on SELINUX
+       help
+         Enable support for this command to be used within shell scripts
+         to determine if selinux is enabled.
+
+config SETENFORCE
+       bool "setenforce"
+       default n
+       depends on SELINUX
+       help
+         Enable support to modify the mode SELinux is running in.
+
+config SETFILES
+       bool "setfiles"
+       default n
+       depends on SELINUX
+       help
+         Enable support to modify to relabel files.
+         Notice: If you built libselinux with -D_FILE_OFFSET_BITS=64,
+         (It is default in libselinux's Makefile), you _must_ enable
+         CONFIG_LFS.
+
+config FEATURE_SETFILES_CHECK_OPTION
+       bool "Enable check option"
+       default n
+       depends on SETFILES
+       help
+         Support "-c" option (check the validity of the contexts against
+         the specified binary policy) for setfiles. Requires libsepol.
+
+config SETSEBOOL
+       bool "setsebool"
+       default n
+       depends on SELINUX
+       help
+         Enable support for change boolean.
+         semanage and -P option is not supported yet.
+
+config SESTATUS
+       bool "sestatus"
+       default n
+       depends on SELINUX
+       help
+         Displays the status of SELinux.
+
+endmenu
+
diff --git a/selinux/Kbuild b/selinux/Kbuild
new file mode 100644 (file)
index 0000000..d0c190c
--- /dev/null
@@ -0,0 +1,20 @@
+# Makefile for busybox
+#
+# Copyright (C) 1999-2005 by Erik Andersen <andersen@codepoet.org>
+# Copyright (C) 2007 by KaiGai Kohei <kaigai@kaigai.gr.jp>
+#
+# Licensed under the GPL v2, see the file LICENSE in this tarball.
+
+lib-y:=
+lib-$(CONFIG_CHCON)            += chcon.o
+lib-$(CONFIG_GETENFORCE)       += getenforce.o
+lib-$(CONFIG_GETSEBOOL)                += getsebool.o
+lib-$(CONFIG_LOAD_POLICY)      += load_policy.o
+lib-$(CONFIG_MATCHPATHCON)     += matchpathcon.o
+lib-$(CONFIG_RUNCON)           += runcon.o
+lib-$(CONFIG_SELINUXENABLED)   += selinuxenabled.o
+lib-$(CONFIG_SETENFORCE)       += setenforce.o
+lib-$(CONFIG_SETFILES)         += setfiles.o
+lib-$(CONFIG_RESTORECON)       += setfiles.o
+lib-$(CONFIG_SETSEBOOL)                += setsebool.o
+lib-$(CONFIG_SESTATUS)         += sestatus.o
diff --git a/selinux/chcon.c b/selinux/chcon.c
new file mode 100644 (file)
index 0000000..55c2522
--- /dev/null
@@ -0,0 +1,179 @@
+/*
+ * chcon -- change security context, based on coreutils-5.97-13
+ *
+ * Port to busybox: KaiGai Kohei <kaigai@kaigai.gr.jp>
+ *
+ * Copyright (C) 2006 - 2007 KaiGai Kohei <kaigai@kaigai.gr.jp>
+ *
+ * Licensed under GPLv2, see file LICENSE in this tarball for details.
+ */
+#include <getopt.h>
+#include <selinux/context.h>
+
+#include "libbb.h"
+
+#define OPT_RECURSIVE          (1<<0)  /* 'R' */
+#define OPT_CHANHES            (1<<1)  /* 'c' */
+#define OPT_NODEREFERENCE      (1<<2)  /* 'h' */
+#define OPT_QUIET              (1<<3)  /* 'f' */
+#define OPT_USER               (1<<4)  /* 'u' */
+#define OPT_ROLE               (1<<5)  /* 'r' */
+#define OPT_TYPE               (1<<6)  /* 't' */
+#define OPT_RANGE              (1<<7)  /* 'l' */
+#define OPT_VERBOSE            (1<<8)  /* 'v' */
+#define OPT_REFERENCE          ((1<<9) * ENABLE_FEATURE_CHCON_LONG_OPTIONS)
+#define OPT_COMPONENT_SPECIFIED        (OPT_USER | OPT_ROLE | OPT_TYPE | OPT_RANGE)
+
+static char *user = NULL;
+static char *role = NULL;
+static char *type = NULL;
+static char *range = NULL;
+static char *specified_context = NULL;
+
+static int FAST_FUNC change_filedir_context(
+               const char *fname,
+               struct stat *stbuf UNUSED_PARAM,
+               void *userData UNUSED_PARAM,
+               int depth UNUSED_PARAM)
+{
+       context_t context = NULL;
+       security_context_t file_context = NULL;
+       security_context_t context_string;
+       int rc = FALSE;
+       int status = 0;
+
+       if (option_mask32 & OPT_NODEREFERENCE) {
+               status = lgetfilecon(fname, &file_context);
+       } else {
+               status = getfilecon(fname, &file_context);
+       }
+       if (status < 0 && errno != ENODATA) {
+               if ((option_mask32 & OPT_QUIET) == 0)
+                       bb_error_msg("cannot obtain security context: %s", fname);
+               goto skip;
+       }
+
+       if (file_context == NULL && specified_context == NULL) {
+               bb_error_msg("cannot apply partial context to unlabeled file %s", fname);
+               goto skip;
+       }
+
+       if (specified_context == NULL) {
+               context = set_security_context_component(file_context,
+                                                        user, role, type, range);
+               if (!context) {
+                       bb_error_msg("cannot compute security context from %s", file_context);
+                       goto skip;
+               }
+       } else {
+               context = context_new(specified_context);
+               if (!context) {
+                       bb_error_msg("invalid context: %s", specified_context);
+                       goto skip;
+               }
+       }
+
+       context_string = context_str(context);
+       if (!context_string) {
+               bb_error_msg("cannot obtain security context in text expression");
+               goto skip;
+       }
+
+       if (file_context == NULL || strcmp(context_string, file_context) != 0) {
+               int fail;
+
+               if (option_mask32 & OPT_NODEREFERENCE) {
+                       fail = lsetfilecon(fname, context_string);
+               } else {
+                       fail = setfilecon(fname, context_string);
+               }
+               if ((option_mask32 & OPT_VERBOSE) || ((option_mask32 & OPT_CHANHES) && !fail)) {
+                       printf(!fail
+                              ? "context of %s changed to %s\n"
+                              : "failed to change context of %s to %s\n",
+                              fname, context_string);
+               }
+               if (!fail) {
+                       rc = TRUE;
+               } else if ((option_mask32 & OPT_QUIET) == 0) {
+                       bb_error_msg("failed to change context of %s to %s",
+                                    fname, context_string);
+               }
+       } else if (option_mask32 & OPT_VERBOSE) {
+               printf("context of %s retained as %s\n", fname, context_string);
+               rc = TRUE;
+       }
+skip:
+       context_free(context);
+       freecon(file_context);
+
+       return rc;
+}
+
+#if ENABLE_FEATURE_CHCON_LONG_OPTIONS
+static const char chcon_longopts[] ALIGN1 =
+       "recursive\0"      No_argument       "R"
+       "changes\0"        No_argument       "c"
+       "no-dereference\0" No_argument       "h"
+       "silent\0"         No_argument       "f"
+       "quiet\0"          No_argument       "f"
+       "user\0"           Required_argument "u"
+       "role\0"           Required_argument "r"
+       "type\0"           Required_argument "t"
+       "range\0"          Required_argument "l"
+       "verbose\0"        No_argument       "v"
+       "reference\0"      Required_argument "\xff" /* no short option */
+       ;
+#endif
+
+int chcon_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int chcon_main(int argc UNUSED_PARAM, char **argv)
+{
+       char *reference_file;
+       char *fname;
+       int i, errors = 0;
+
+#if ENABLE_FEATURE_CHCON_LONG_OPTIONS
+       applet_long_options = chcon_longopts;
+#endif
+       opt_complementary = "-1"  /* at least 1 param */
+               ":?"  /* error if exclusivity constraints are violated */
+#if ENABLE_FEATURE_CHCON_LONG_OPTIONS
+               ":\xff--urtl:u--\xff:r--\xff:t--\xff:l--\xff"
+#endif
+               ":f--v:v--f";  /* 'verbose' and 'quiet' are exclusive */
+       getopt32(argv, "Rchfu:r:t:l:v",
+               &user, &role, &type, &range, &reference_file);
+       argv += optind;
+
+#if ENABLE_FEATURE_CHCON_LONG_OPTIONS
+       if (option_mask32 & OPT_REFERENCE) {
+               /* FIXME: lgetfilecon() should be used when '-h' is specified.
+                  But current implementation follows the original one. */
+               if (getfilecon(reference_file, &specified_context) < 0)
+                       bb_perror_msg_and_die("getfilecon('%s') failed", reference_file);
+       } else
+#endif
+       if ((option_mask32 & OPT_COMPONENT_SPECIFIED) == 0) {
+               specified_context = *argv++;
+               /* specified_context is never NULL -
+                * "-1" in opt_complementary prevents this. */
+               if (!argv[0])
+                       bb_error_msg_and_die("too few arguments");
+       }
+
+       for (i = 0; (fname = argv[i]) != NULL; i++) {
+               int fname_len = strlen(fname);
+               while (fname_len > 1 && fname[fname_len - 1] == '/')
+                       fname_len--;
+               fname[fname_len] = '\0';
+
+               if (recursive_action(fname,
+                                    1<<option_mask32 & OPT_RECURSIVE,
+                                    change_filedir_context,
+                                    change_filedir_context,
+                                    NULL, 0) != TRUE)
+                       errors = 1;
+       }
+       return errors;
+}
diff --git a/selinux/getenforce.c b/selinux/getenforce.c
new file mode 100644 (file)
index 0000000..3d3eef1
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+ * getenforce
+ *
+ * Based on libselinux 1.33.1
+ * Port to BusyBox  Hiroshi Shinji <shiroshi@my.email.ne.jp>
+ *
+ * Licensed under GPLv2, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.h"
+
+int getenforce_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int getenforce_main(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
+{
+       int rc;
+
+       rc = is_selinux_enabled();
+       if (rc < 0)
+               bb_error_msg_and_die("is_selinux_enabled() failed");
+
+       if (rc == 1) {
+               rc = security_getenforce();
+               if (rc < 0)
+                       bb_error_msg_and_die("getenforce() failed");
+
+               if (rc)
+                       puts("Enforcing");
+               else
+                       puts("Permissive");
+       } else {
+               puts("Disabled");
+       }
+
+       return 0;
+}
diff --git a/selinux/getsebool.c b/selinux/getsebool.c
new file mode 100644 (file)
index 0000000..b761b72
--- /dev/null
@@ -0,0 +1,67 @@
+/*
+ * getsebool
+ *
+ * Based on libselinux 1.33.1
+ * Port to BusyBox  Hiroshi Shinji <shiroshi@my.email.ne.jp>
+ *
+ * Licensed under GPLv2, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.h"
+
+int getsebool_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int getsebool_main(int argc, char **argv)
+{
+       int i, rc = 0, active, pending, len = 0;
+       char **names;
+       unsigned opt;
+
+       selinux_or_die();
+       opt = getopt32(argv, "a");
+
+       if (opt) { /* -a */
+               if (argc > 2)
+                       bb_show_usage();
+
+               rc = security_get_boolean_names(&names, &len);
+               if (rc)
+                       bb_perror_msg_and_die("cannot get boolean names");
+
+               if (!len) {
+                       puts("No booleans");
+                       return 0;
+               }
+       }
+
+       if (!len) {
+               if (argc < 2)
+                       bb_show_usage();
+               len = argc - 1;
+               names = xmalloc(sizeof(char *) * len);
+               for (i = 0; i < len; i++)
+                       names[i] = xstrdup(argv[i + 1]);
+       }
+
+       for (i = 0; i < len; i++) {
+               active = security_get_boolean_active(names[i]);
+               if (active < 0) {
+                       bb_error_msg_and_die("error getting active value for %s", names[i]);
+               }
+               pending = security_get_boolean_pending(names[i]);
+               if (pending < 0) {
+                       bb_error_msg_and_die("error getting pending value for %s", names[i]);
+               }
+               printf("%s --> %s", names[i], (active ? "on" : "off"));
+               if (pending != active)
+                       printf(" pending: %s", (pending ? "on" : "off"));
+               bb_putchar('\n');
+       }
+
+       if (ENABLE_FEATURE_CLEAN_UP) {
+               for (i = 0; i < len; i++)
+                       free(names[i]);
+               free(names);
+       }
+
+       return rc;
+}
diff --git a/selinux/load_policy.c b/selinux/load_policy.c
new file mode 100644 (file)
index 0000000..4bc873e
--- /dev/null
@@ -0,0 +1,24 @@
+/*
+ * load_policy
+ * Author: Yuichi Nakamura <ynakam@hitachisoft.jp>
+ *
+ * Licensed under GPLv2, see file LICENSE in this tarball for details.
+ */
+#include "libbb.h"
+
+int load_policy_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int load_policy_main(int argc, char **argv UNUSED_PARAM)
+{
+       int rc;
+
+       if (argc != 1) {
+               bb_show_usage();
+       }
+
+       rc = selinux_mkload_policy(1);
+       if (rc < 0) {
+               bb_perror_msg_and_die("can't load policy");
+       }
+
+       return 0;
+}
diff --git a/selinux/matchpathcon.c b/selinux/matchpathcon.c
new file mode 100644 (file)
index 0000000..1532429
--- /dev/null
@@ -0,0 +1,87 @@
+/* matchpathcon  -  get the default security context for the specified
+ *                  path from the file contexts configuration.
+ *                  based on libselinux-1.32
+ * Port to busybox: KaiGai Kohei <kaigai@kaigai.gr.jp>
+ *
+ * Licensed under GPLv2, see file LICENSE in this tarball for details.
+ */
+#include "libbb.h"
+
+static int print_matchpathcon(char *path, int noprint)
+{
+       char *buf;
+       int rc = matchpathcon(path, 0, &buf);
+       if (rc < 0) {
+               bb_perror_msg("matchpathcon(%s) failed", path);
+               return 1;
+       }
+       if (!noprint)
+               printf("%s\t%s\n", path, buf);
+       else
+               puts(buf);
+
+       freecon(buf);
+       return 0;
+}
+
+#define OPT_NOT_PRINT   (1<<0)  /* -n */
+#define OPT_NOT_TRANS   (1<<1)  /* -N */
+#define OPT_FCONTEXT    (1<<2)  /* -f */
+#define OPT_PREFIX      (1<<3)  /* -p */
+#define OPT_VERIFY      (1<<4)  /* -V */
+
+int matchpathcon_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int matchpathcon_main(int argc UNUSED_PARAM, char **argv)
+{
+       int error = 0;
+       unsigned opts;
+       char *fcontext, *prefix, *path;
+
+       opt_complementary = "-1" /* at least one param reqd */
+               ":?:f--p:p--f"; /* mutually exclusive */
+       opts = getopt32(argv, "nNf:p:V", &fcontext, &prefix);
+       argv += optind;
+
+       if (opts & OPT_NOT_TRANS) {
+               set_matchpathcon_flags(MATCHPATHCON_NOTRANS);
+       }
+       if (opts & OPT_FCONTEXT) {
+               if (matchpathcon_init(fcontext))
+                       bb_perror_msg_and_die("error while processing %s", fcontext);
+       }
+       if (opts & OPT_PREFIX) {
+               if (matchpathcon_init_prefix(NULL, prefix))
+                       bb_perror_msg_and_die("error while processing %s", prefix);
+       }
+
+       while ((path = *argv++) != NULL) {
+               security_context_t con;
+               int rc;
+
+               if (!(opts & OPT_VERIFY)) {
+                       error += print_matchpathcon(path, opts & OPT_NOT_PRINT);
+                       continue;
+               }
+
+               if (selinux_file_context_verify(path, 0)) {
+                       printf("%s verified\n", path);
+                       continue;
+               }
+
+               if (opts & OPT_NOT_TRANS)
+                       rc = lgetfilecon_raw(path, &con);
+               else
+                       rc = lgetfilecon(path, &con);
+
+               if (rc >= 0) {
+                       printf("%s has context %s, should be ", path, con);
+                       error += print_matchpathcon(path, 1);
+                       freecon(con);
+                       continue;
+               }
+               printf("actual context unknown: %s, should be ", strerror(errno));
+               error += print_matchpathcon(path, 1);
+       }
+       matchpathcon_fini();
+       return error;
+}
diff --git a/selinux/runcon.c b/selinux/runcon.c
new file mode 100644 (file)
index 0000000..e94ff14
--- /dev/null
@@ -0,0 +1,138 @@
+/*
+ * runcon [ context |
+ *         ( [ -c ] [ -r role ] [-t type] [ -u user ] [ -l levelrange ] )
+ *         command [arg1 [arg2 ...] ]
+ *
+ * attempt to run the specified command with the specified context.
+ *
+ * -r role  : use the current context with the specified role
+ * -t type  : use the current context with the specified type
+ * -u user  : use the current context with the specified user
+ * -l level : use the current context with the specified level range
+ * -c       : compute process transition context before modifying
+ *
+ * Contexts are interpreted as follows:
+ *
+ * Number of       MLS
+ * components    system?
+ *
+ *     1            -         type
+ *     2            -         role:type
+ *     3            Y         role:type:range
+ *     3            N         user:role:type
+ *     4            Y         user:role:type:range
+ *     4            N         error
+ *
+ * Port to busybox: KaiGai Kohei <kaigai@kaigai.gr.jp>
+ *                  - based on coreutils-5.97 (in Fedora Core 6)
+ *
+ * Licensed under GPLv2, see file LICENSE in this tarball for details.
+ */
+#include <getopt.h>
+#include <selinux/context.h>
+#include <selinux/flask.h>
+
+#include "libbb.h"
+
+static context_t runcon_compute_new_context(char *user, char *role, char *type, char *range,
+                                           char *command, int compute_trans)
+{
+       context_t con;
+       security_context_t cur_context;
+
+       if (getcon(&cur_context))
+               bb_error_msg_and_die("cannot get current context");
+
+       if (compute_trans) {
+               security_context_t file_context, new_context;
+
+               if (getfilecon(command, &file_context) < 0)
+                       bb_error_msg_and_die("cannot retrieve attributes of '%s'",
+                                            command);
+               if (security_compute_create(cur_context, file_context,
+                                           SECCLASS_PROCESS, &new_context))
+                       bb_error_msg_and_die("unable to compute a new context");
+               cur_context = new_context;
+       }
+
+       con = context_new(cur_context);
+       if (!con)
+               bb_error_msg_and_die("'%s' is not a valid context", cur_context);
+       if (user && context_user_set(con, user))
+               bb_error_msg_and_die("failed to set new user '%s'", user);
+       if (type && context_type_set(con, type))
+               bb_error_msg_and_die("failed to set new type '%s'", type);
+       if (range && context_range_set(con, range))
+               bb_error_msg_and_die("failed to set new range '%s'", range);
+       if (role && context_role_set(con, role))
+               bb_error_msg_and_die("failed to set new role '%s'", role);
+
+       return con;
+}
+
+#if ENABLE_FEATURE_RUNCON_LONG_OPTIONS
+static const char runcon_longopts[] ALIGN1 =
+       "user\0"    Required_argument "u"
+       "role\0"    Required_argument "r"
+       "type\0"    Required_argument "t"
+       "range\0"   Required_argument "l"
+       "compute\0" No_argument "c"
+       "help\0"    No_argument "h"
+       ;
+#endif
+
+#define OPTS_ROLE      (1<<0)  /* r */
+#define OPTS_TYPE      (1<<1)  /* t */
+#define OPTS_USER      (1<<2)  /* u */
+#define OPTS_RANGE     (1<<3)  /* l */
+#define OPTS_COMPUTE   (1<<4)  /* c */
+#define OPTS_HELP      (1<<5)  /* h */
+#define OPTS_CONTEXT_COMPONENT         (OPTS_ROLE | OPTS_TYPE | OPTS_USER | OPTS_RANGE)
+
+int runcon_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int runcon_main(int argc UNUSED_PARAM, char **argv)
+{
+       char *role = NULL;
+       char *range = NULL;
+       char *user = NULL;
+       char *type = NULL;
+       char *context = NULL;
+       unsigned opts;
+       context_t con;
+
+       selinux_or_die();
+
+#if ENABLE_FEATURE_RUNCON_LONG_OPTIONS
+       applet_long_options = runcon_longopts;
+#endif
+       opt_complementary = "-1";
+       opts = getopt32(argv, "r:t:u:l:ch", &role, &type, &user, &range);
+       argv += optind;
+
+       if (!(opts & OPTS_CONTEXT_COMPONENT)) {
+               context = *argv++;
+               if (!argv[0])
+                       bb_error_msg_and_die("no command given");
+       }
+
+       if (context) {
+               con = context_new(context);
+               if (!con)
+                       bb_error_msg_and_die("'%s' is not a valid context", context);
+       } else {
+               con = runcon_compute_new_context(user, role, type, range,
+                               argv[0], opts & OPTS_COMPUTE);
+       }
+
+       if (security_check_context(context_str(con)))
+               bb_error_msg_and_die("'%s' is not a valid context",
+                                    context_str(con));
+
+       if (setexeccon(context_str(con)))
+               bb_error_msg_and_die("cannot set up security context '%s'",
+                                    context_str(con));
+
+       execvp(argv[0], argv);
+
+       bb_perror_msg_and_die("cannot execute '%s'", argv[0]);
+}
diff --git a/selinux/selinuxenabled.c b/selinux/selinuxenabled.c
new file mode 100644 (file)
index 0000000..1cf93c3
--- /dev/null
@@ -0,0 +1,15 @@
+/*
+ * selinuxenabled
+ *
+ * Based on libselinux 1.33.1
+ * Port to BusyBox  Hiroshi Shinji <shiroshi@my.email.ne.jp>
+ *
+ * Licensed under GPLv2, see file LICENSE in this tarball for details.
+ */
+#include "libbb.h"
+
+int selinuxenabled_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int selinuxenabled_main(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
+{
+       return !is_selinux_enabled();
+}
diff --git a/selinux/sestatus.c b/selinux/sestatus.c
new file mode 100644 (file)
index 0000000..1a02a6b
--- /dev/null
@@ -0,0 +1,205 @@
+/*
+ * sestatus -- displays the status of SELinux
+ *
+ * Ported to busybox: KaiGai Kohei <kaigai@ak.jp.nec.com>
+ *
+ * Copyright (C) KaiGai Kohei <kaigai@ak.jp.nec.com>
+ *
+ * Licensed under GPLv2, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.h"
+
+extern char *selinux_mnt;
+
+#define OPT_VERBOSE    (1 << 0)
+#define OPT_BOOLEAN    (1 << 1)
+
+#define COL_FMT                "%-31s "
+
+static void display_boolean(void)
+{
+       char **bools;
+       int i, active, pending, nbool;
+
+       if (security_get_boolean_names(&bools, &nbool) < 0)
+               return;
+
+       puts("\nPolicy booleans:");
+
+       for (i = 0; i < nbool; i++) {
+               active = security_get_boolean_active(bools[i]);
+               if (active < 0)
+                       goto skip;
+               pending = security_get_boolean_pending(bools[i]);
+               if (pending < 0)
+                       goto skip;
+               printf(COL_FMT "%s",
+                      bools[i], active == 0 ? "off" : "on");
+               if (active != pending)
+                       printf(" (%sactivate pending)", pending == 0 ? "in" : "");
+               bb_putchar('\n');
+ skip:
+               if (ENABLE_FEATURE_CLEAN_UP)
+                       free(bools[i]);
+       }
+       if (ENABLE_FEATURE_CLEAN_UP)
+               free(bools);
+}
+
+static void read_config(char **pc, int npc, char **fc, int nfc)
+{
+       char *buf;
+       parser_t *parser;
+       int pc_ofs = 0, fc_ofs = 0, section = -1;
+
+       pc[0] = fc[0] = NULL;
+
+       parser = config_open("/etc/sestatus.conf");
+       while (config_read(parser, &buf, 1, 1, "# \t", PARSE_NORMAL)) {
+               if (strcmp(buf, "[process]") == 0) {
+                       section = 1;
+               } else if (strcmp(buf, "[files]") == 0) {
+                       section = 2;
+               } else {
+                       if (section == 1 && pc_ofs < npc -1) {
+                               pc[pc_ofs++] = xstrdup(buf);
+                               pc[pc_ofs] = NULL;
+                       } else if (section == 2 && fc_ofs < nfc - 1) {
+                               fc[fc_ofs++] = xstrdup(buf);
+                               fc[fc_ofs] = NULL;
+                       }
+               }
+       }
+       config_close(parser);
+}
+
+static void display_verbose(void)
+{
+       security_context_t con, _con;
+       char *fc[50], *pc[50], *cterm;
+       pid_t *pidList;
+       int i;
+
+       read_config(pc, ARRAY_SIZE(pc), fc, ARRAY_SIZE(fc));
+
+       /* process contexts */
+       puts("\nProcess contexts:");
+
+       /* current context */
+       if (getcon(&con) == 0) {
+               printf(COL_FMT "%s\n", "Current context:", con);
+               if (ENABLE_FEATURE_CLEAN_UP)
+                       freecon(con);
+       }
+       /* /sbin/init context */
+       if (getpidcon(1, &con) == 0) {
+               printf(COL_FMT "%s\n", "Init context:", con);
+               if (ENABLE_FEATURE_CLEAN_UP)
+                       freecon(con);
+       }
+
+       /* [process] context */
+       for (i = 0; pc[i] != NULL; i++) {
+               pidList = find_pid_by_name(bb_basename(pc[i]));
+               if (pidList[0] > 0 && getpidcon(pidList[0], &con) == 0) {
+                       printf(COL_FMT "%s\n", pc[i], con);
+                       if (ENABLE_FEATURE_CLEAN_UP)
+                               freecon(con);
+               }
+               if (ENABLE_FEATURE_CLEAN_UP)
+                       free(pidList);
+       }
+
+       /* files contexts */
+       puts("\nFile contexts:");
+
+       cterm = xmalloc_ttyname(0);
+//FIXME: if cterm == NULL, we segfault!??
+       puts(cterm);
+       if (cterm && lgetfilecon(cterm, &con) >= 0) {
+               printf(COL_FMT "%s\n", "Controlling term:", con);
+               if (ENABLE_FEATURE_CLEAN_UP)
+                       freecon(con);
+       }
+
+       for (i = 0; fc[i] != NULL; i++) {
+               struct stat stbuf;
+
+               if (lgetfilecon(fc[i], &con) < 0)
+                       continue;
+               if (lstat(fc[i], &stbuf) == 0) {
+                       if (S_ISLNK(stbuf.st_mode)) {
+                               if (getfilecon(fc[i], &_con) >= 0) {
+                                       printf(COL_FMT "%s -> %s\n", fc[i], _con, con);
+                                       if (ENABLE_FEATURE_CLEAN_UP)
+                                               freecon(_con);
+                               }
+                       } else {
+                               printf(COL_FMT "%s\n", fc[i], con);
+                       }
+               }
+               if (ENABLE_FEATURE_CLEAN_UP)
+                       freecon(con);
+       }
+}
+
+int sestatus_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int sestatus_main(int argc UNUSED_PARAM, char **argv)
+{
+       unsigned opts;
+       const char *pol_path;
+       int rc;
+
+       opt_complementary = "?0";       /* no arguments are required. */
+       opts = getopt32(argv, "vb");
+
+       /* SELinux status: line */
+       rc = is_selinux_enabled();
+       if (rc < 0)
+               goto error;
+       printf(COL_FMT "%s\n", "SELinux status:",
+              rc == 1 ? "enabled" : "disabled");
+
+       /* SELinuxfs mount: line */
+       if (!selinux_mnt)
+               goto error;
+       printf(COL_FMT "%s\n", "SELinuxfs mount:",
+              selinux_mnt);
+
+       /* Current mode: line */
+       rc = security_getenforce();
+       if (rc < 0)
+               goto error;
+       printf(COL_FMT "%s\n", "Current mode:",
+              rc == 0 ? "permissive" : "enforcing");
+
+       /* Mode from config file: line */
+       if (selinux_getenforcemode(&rc) != 0)
+               goto error;
+       printf(COL_FMT "%s\n", "Mode from config file:",
+              rc < 0 ? "disabled" : (rc == 0 ? "permissive" : "enforcing"));
+
+       /* Policy version: line */
+       rc = security_policyvers();
+       if (rc < 0)
+               goto error;
+       printf(COL_FMT "%u\n", "Policy version:", rc);
+
+       /* Policy from config file: line */
+       pol_path = selinux_policy_root();
+       if (!pol_path)
+               goto error;
+       printf(COL_FMT "%s\n", "Policy from config file:",
+              bb_basename(pol_path));
+
+       if (opts & OPT_BOOLEAN)
+               display_boolean();
+       if (opts & OPT_VERBOSE)
+               display_verbose();
+
+       return 0;
+
+  error:
+       bb_perror_msg_and_die("libselinux returns unknown state");
+}
diff --git a/selinux/setenforce.c b/selinux/setenforce.c
new file mode 100644 (file)
index 0000000..a2d0428
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ * setenforce
+ *
+ * Based on libselinux 1.33.1
+ * Port to BusyBox  Hiroshi Shinji <shiroshi@my.email.ne.jp>
+ *
+ * Licensed under GPLv2, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.h"
+
+/* These strings are arranged so that odd ones
+ * result in security_setenforce(1) being done,
+ * the rest will do security_setenforce(0) */
+static const char *const setenforce_cmd[] = {
+       "0",
+       "1",
+       "permissive",
+       "enforcing",
+       NULL,
+};
+
+int setenforce_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int setenforce_main(int argc, char **argv)
+{
+       int i, rc;
+
+       if (argc != 2)
+               bb_show_usage();
+
+       selinux_or_die();
+
+       for (i = 0; setenforce_cmd[i]; i++) {
+               if (strcasecmp(argv[1], setenforce_cmd[i]) != 0)
+                       continue;
+               rc = security_setenforce(i & 1);
+               if (rc < 0)
+                       bb_perror_msg_and_die("setenforce() failed");
+               return 0;
+       }
+
+       bb_show_usage();
+}
diff --git a/selinux/setfiles.c b/selinux/setfiles.c
new file mode 100644 (file)
index 0000000..8eb04e6
--- /dev/null
@@ -0,0 +1,645 @@
+/*
+  setfiles: based on policycoreutils 2.0.19
+  policycoreutils was released under GPL 2.
+  Port to BusyBox (c) 2007 by Yuichi Nakamura <ynakam@hitachisoft.jp>
+*/
+
+#include "libbb.h"
+#if ENABLE_FEATURE_SETFILES_CHECK_OPTION
+#include <sepol/sepol.h>
+#endif
+
+#define MAX_EXCLUDES 50
+
+struct edir {
+       char *directory;
+       size_t size;
+};
+
+struct globals {
+       FILE *outfile;
+       char *policyfile;
+       char *rootpath;
+       int rootpathlen;
+       unsigned count;
+       int excludeCtr;
+       int errors;
+       int verbose; /* getopt32 uses it, has to be int */
+       smallint recurse; /* Recursive descent */
+       smallint follow_mounts;
+       /* Behavior flags determined based on setfiles vs. restorecon */
+       smallint expand_realpath;  /* Expand paths via realpath */
+       smallint abort_on_error; /* Abort the file tree walk upon an error */
+       int add_assoc; /* Track inode associations for conflict detection */
+       int matchpathcon_flags; /* Flags to matchpathcon */
+       dev_t dev_id; /* Device id where target file exists */
+       int nerr;
+       struct edir excludeArray[MAX_EXCLUDES];
+};
+
+#define G (*(struct globals*)&bb_common_bufsiz1)
+void BUG_setfiles_globals_too_big(void);
+#define INIT_G() do { \
+       if (sizeof(G) > COMMON_BUFSIZE) \
+               BUG_setfiles_globals_too_big(); \
+       /* memset(&G, 0, sizeof(G)); - already is */ \
+} while (0)
+#define outfile            (G.outfile           )
+#define policyfile         (G.policyfile        )
+#define rootpath           (G.rootpath          )
+#define rootpathlen        (G.rootpathlen       )
+#define count              (G.count             )
+#define excludeCtr         (G.excludeCtr        )
+#define errors             (G.errors            )
+#define verbose            (G.verbose           )
+#define recurse            (G.recurse           )
+#define follow_mounts      (G.follow_mounts     )
+#define expand_realpath    (G.expand_realpath   )
+#define abort_on_error     (G.abort_on_error    )
+#define add_assoc          (G.add_assoc         )
+#define matchpathcon_flags (G.matchpathcon_flags)
+#define dev_id             (G.dev_id            )
+#define nerr               (G.nerr              )
+#define excludeArray       (G.excludeArray      )
+
+/* Must match getopt32 string! */
+enum {
+       OPT_d = (1 << 0),
+       OPT_e = (1 << 1),
+       OPT_f = (1 << 2),
+       OPT_i = (1 << 3),
+       OPT_l = (1 << 4),
+       OPT_n = (1 << 5),
+       OPT_p = (1 << 6),
+       OPT_q = (1 << 7),
+       OPT_r = (1 << 8),
+       OPT_s = (1 << 9),
+       OPT_v = (1 << 10),
+       OPT_o = (1 << 11),
+       OPT_F = (1 << 12),
+       OPT_W = (1 << 13),
+       OPT_c = (1 << 14), /* c only for setfiles */
+       OPT_R = (1 << 14), /* R only for restorecon */
+};
+#define FLAG_d_debug         (option_mask32 & OPT_d)
+#define FLAG_e               (option_mask32 & OPT_e)
+#define FLAG_f               (option_mask32 & OPT_f)
+#define FLAG_i_ignore_enoent (option_mask32 & OPT_i)
+#define FLAG_l_take_log      (option_mask32 & OPT_l)
+#define FLAG_n_dry_run       (option_mask32 & OPT_n)
+#define FLAG_p_progress      (option_mask32 & OPT_p)
+#define FLAG_q_quiet         (option_mask32 & OPT_q)
+#define FLAG_r               (option_mask32 & OPT_r)
+#define FLAG_s               (option_mask32 & OPT_s)
+#define FLAG_v               (option_mask32 & OPT_v)
+#define FLAG_o               (option_mask32 & OPT_o)
+#define FLAG_F_force         (option_mask32 & OPT_F)
+#define FLAG_W_warn_no_match (option_mask32 & OPT_W)
+#define FLAG_c               (option_mask32 & OPT_c)
+#define FLAG_R               (option_mask32 & OPT_R)
+
+
+static void qprintf(const char *fmt UNUSED_PARAM, ...)
+{
+       /* quiet, do nothing */
+}
+
+static void inc_err(void)
+{
+       nerr++;
+       if (nerr > 9 && !FLAG_d_debug) {
+               bb_error_msg_and_die("exiting after 10 errors");
+       }
+}
+
+static void add_exclude(const char *const directory)
+{
+       struct stat sb;
+       size_t len;
+
+       if (directory == NULL || directory[0] != '/') {
+               bb_error_msg_and_die("full path required for exclude: %s", directory);
+
+       }
+       if (lstat(directory, &sb)) {
+               bb_error_msg("directory \"%s\" not found, ignoring", directory);
+               return;
+       }
+       if ((sb.st_mode & S_IFDIR) == 0) {
+               bb_error_msg("\"%s\" is not a directory: mode %o, ignoring",
+                       directory, sb.st_mode);
+               return;
+       }
+       if (excludeCtr == MAX_EXCLUDES) {
+               bb_error_msg_and_die("maximum excludes %d exceeded", MAX_EXCLUDES);
+       }
+
+       len = strlen(directory);
+       while (len > 1 && directory[len - 1] == '/') {
+               len--;
+       }
+       excludeArray[excludeCtr].directory = xstrndup(directory, len);
+       excludeArray[excludeCtr++].size = len;
+}
+
+static bool exclude(const char *file)
+{
+       int i = 0;
+       for (i = 0; i < excludeCtr; i++) {
+               if (strncmp(file, excludeArray[i].directory,
+                                       excludeArray[i].size) == 0) {
+                       if (file[excludeArray[i].size] == '\0'
+                        || file[excludeArray[i].size] == '/') {
+                               return 1;
+                       }
+               }
+       }
+       return 0;
+}
+
+static int match(const char *name, struct stat *sb, char **con)
+{
+       int ret;
+       char path[PATH_MAX + 1];
+       char *tmp_path = xstrdup(name);
+
+       if (excludeCtr > 0 && exclude(name)) {
+               goto err;
+       }
+       ret = lstat(name, sb);
+       if (ret) {
+               if (FLAG_i_ignore_enoent && errno == ENOENT) {
+                       free(tmp_path);
+                       return 0;
+               }
+               bb_error_msg("stat(%s)", name);
+               goto err;
+       }
+
+       if (expand_realpath) {
+               if (S_ISLNK(sb->st_mode)) {
+                       char *p = NULL;
+                       char *file_sep;
+
+                       size_t len = 0;
+
+                       if (verbose > 1)
+                               bb_error_msg("warning! %s refers to a symbolic link, not following last component", name);
+
+                       file_sep = strrchr(tmp_path, '/');
+                       if (file_sep == tmp_path) {
+                               file_sep++;
+                               path[0] = '\0';
+                               p = path;
+                       } else if (file_sep) {
+                               *file_sep++ = '\0';
+                               p = realpath(tmp_path, path);
+                       } else {
+                               file_sep = tmp_path;
+                               p = realpath("./", path);
+                       }
+                       if (p)
+                               len = strlen(p);
+                       if (!p || len + strlen(file_sep) + 2 > PATH_MAX) {
+                               bb_perror_msg("realpath(%s) failed", name);
+                               goto err;
+                       }
+                       p += len;
+                       /* ensure trailing slash of directory name */
+                       if (len == 0 || p[-1] != '/') {
+                               *p++ = '/';
+                       }
+                       strcpy(p, file_sep);
+                       name = path;
+                       if (excludeCtr > 0 && exclude(name))
+                               goto err;
+
+               } else {
+                       char *p;
+                       p = realpath(name, path);
+                       if (!p) {
+                               bb_perror_msg("realpath(%s)", name);
+                               goto err;
+                       }
+                       name = p;
+                       if (excludeCtr > 0 && exclude(name))
+                               goto err;
+               }
+       }
+
+       /* name will be what is matched in the policy */
+       if (NULL != rootpath) {
+               if (0 != strncmp(rootpath, name, rootpathlen)) {
+                       bb_error_msg("%s is not located in %s",
+                               name, rootpath);
+                       goto err;
+               }
+               name += rootpathlen;
+       }
+
+       free(tmp_path);
+       if (rootpath != NULL && name[0] == '\0')
+               /* this is actually the root dir of the alt root */
+               return matchpathcon_index("/", sb->st_mode, con);
+       return matchpathcon_index(name, sb->st_mode, con);
+ err:
+       free(tmp_path);
+       return -1;
+}
+
+/* Compare two contexts to see if their differences are "significant",
+ * or whether the only difference is in the user. */
+static bool only_changed_user(const char *a, const char *b)
+{
+       if (FLAG_F_force)
+               return 0;
+       if (!a || !b)
+               return 0;
+       a = strchr(a, ':'); /* Rest of the context after the user */
+       b = strchr(b, ':');
+       if (!a || !b)
+               return 0;
+       return (strcmp(a, b) == 0);
+}
+
+static int restore(const char *file)
+{
+       char *my_file;
+       struct stat my_sb;
+       int i, j, ret;
+       char *context = NULL;
+       char *newcon = NULL;
+       bool user_only_changed = 0;
+       int retval = 0;
+
+       my_file = bb_simplify_path(file);
+
+       i = match(my_file, &my_sb, &newcon);
+
+       if (i < 0) /* No matching specification. */
+               goto out;
+
+       if (FLAG_p_progress) {
+               count++;
+               if (count % 0x400 == 0) { /* every 1024 times */
+                       count = (count % (80*0x400));
+                       if (count == 0)
+                               bb_putchar('\n');
+                       bb_putchar('*');
+                       fflush(stdout);
+               }
+       }
+
+       /*
+        * Try to add an association between this inode and
+        * this specification. If there is already an association
+        * for this inode and it conflicts with this specification,
+        * then use the last matching specification.
+        */
+       if (add_assoc) {
+               j = matchpathcon_filespec_add(my_sb.st_ino, i, my_file);
+               if (j < 0)
+                       goto err;
+
+               if (j != i) {
+                       /* There was already an association and it took precedence. */
+                       goto out;
+               }
+       }
+
+       if (FLAG_d_debug)
+               printf("%s: %s matched by %s\n", applet_name, my_file, newcon);
+
+       /* Get the current context of the file. */
+       ret = lgetfilecon_raw(my_file, &context);
+       if (ret < 0) {
+               if (errno == ENODATA) {
+                       context = NULL; /* paranoia */
+               } else {
+                       bb_perror_msg("lgetfilecon_raw on %s", my_file);
+                       goto err;
+               }
+               user_only_changed = 0;
+       } else
+               user_only_changed = only_changed_user(context, newcon);
+
+       /*
+        * Do not relabel the file if the matching specification is
+        * <<none>> or the file is already labeled according to the
+        * specification.
+        */
+       if ((strcmp(newcon, "<<none>>") == 0)
+        || (context && (strcmp(context, newcon) == 0) && !FLAG_F_force)) {
+               goto out;
+       }
+
+       if (!FLAG_F_force && context && (is_context_customizable(context) > 0)) {
+               if (verbose > 1) {
+                       bb_error_msg("skipping %s. %s is customizable_types",
+                               my_file, context);
+               }
+               goto out;
+       }
+
+       if (verbose) {
+               /* If we're just doing "-v", trim out any relabels where
+                * the user has changed but the role and type are the
+                * same.  For "-vv", emit everything. */
+               if (verbose > 1 || !user_only_changed) {
+                       bb_info_msg("%s: reset %s context %s->%s",
+                               applet_name, my_file, context ?: "", newcon);
+               }
+       }
+
+       if (FLAG_l_take_log && !user_only_changed) {
+               if (context)
+                       bb_info_msg("relabeling %s from %s to %s", my_file, context, newcon);
+               else
+                       bb_info_msg("labeling %s to %s", my_file, newcon);
+       }
+
+       if (outfile && !user_only_changed)
+               fprintf(outfile, "%s\n", my_file);
+
+       /*
+        * Do not relabel the file if -n was used.
+        */
+       if (FLAG_n_dry_run || user_only_changed)
+               goto out;
+
+       /*
+        * Relabel the file to the specified context.
+        */
+       ret = lsetfilecon(my_file, newcon);
+       if (ret) {
+               bb_perror_msg("lsetfileconon(%s,%s)", my_file, newcon);
+               goto err;
+       }
+
+ out:
+       freecon(context);
+       freecon(newcon);
+       free(my_file);
+       return retval;
+ err:
+       retval--; /* -1 */
+       goto out;
+}
+
+/*
+ * Apply the last matching specification to a file.
+ * This function is called by recursive_action on each file during
+ * the directory traversal.
+ */
+static int FAST_FUNC apply_spec(
+               const char *file,
+               struct stat *sb,
+               void *userData UNUSED_PARAM,
+               int depth UNUSED_PARAM)
+{
+       if (!follow_mounts) {
+               /* setfiles does not process across different mount points */
+               if (sb->st_dev != dev_id) {
+                       return SKIP;
+               }
+       }
+       errors |= restore(file);
+       if (abort_on_error && errors)
+               return FALSE;
+       return TRUE;
+}
+
+
+static int canoncon(const char *path, unsigned lineno, char **contextp)
+{
+       static const char err_msg[] ALIGN1 = "%s: line %u has invalid context %s";
+
+       char *tmpcon;
+       char *context = *contextp;
+       int invalid = 0;
+
+#if ENABLE_FEATURE_SETFILES_CHECK_OPTION
+       if (policyfile) {
+               if (sepol_check_context(context) >= 0)
+                       return 0;
+               /* Exit immediately if we're in checking mode. */
+               bb_error_msg_and_die(err_msg, path, lineno, context);
+       }
+#endif
+
+       if (security_canonicalize_context_raw(context, &tmpcon) < 0) {
+               if (errno != ENOENT) {
+                       invalid = 1;
+                       inc_err();
+               }
+       } else {
+               free(context);
+               *contextp = tmpcon;
+       }
+
+       if (invalid) {
+               bb_error_msg(err_msg, path, lineno, context);
+       }
+
+       return invalid;
+}
+
+static int process_one(char *name)
+{
+       struct stat sb;
+       int rc;
+
+       rc = lstat(name, &sb);
+       if (rc < 0) {
+               if (FLAG_i_ignore_enoent && errno == ENOENT)
+                       return 0;
+               bb_perror_msg("stat(%s)", name);
+               goto err;
+       }
+       dev_id = sb.st_dev;
+
+       if (S_ISDIR(sb.st_mode) && recurse) {
+               if (recursive_action(name,
+                                    ACTION_RECURSE,
+                                    apply_spec,
+                                    apply_spec,
+                                    NULL, 0) != TRUE) {
+                       bb_error_msg("error while labeling %s", name);
+                       goto err;
+               }
+       } else {
+               rc = restore(name);
+               if (rc)
+                       goto err;
+       }
+
+ out:
+       if (add_assoc) {
+               if (FLAG_q_quiet)
+                       set_matchpathcon_printf(&qprintf);
+               matchpathcon_filespec_eval();
+               set_matchpathcon_printf(NULL);
+               matchpathcon_filespec_destroy();
+       }
+
+       return rc;
+
+ err:
+       rc = -1;
+       goto out;
+}
+
+int setfiles_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int setfiles_main(int argc, char **argv)
+{
+       struct stat sb;
+       int rc, i = 0;
+       const char *input_filename = NULL;
+       char *buf = NULL;
+       size_t buf_len;
+       int flags;
+       llist_t *exclude_dir = NULL;
+       char *out_filename = NULL;
+
+       INIT_G();
+
+       if (applet_name[0] == 's') { /* "setfiles" */
+               /*
+                * setfiles:
+                * Recursive descent,
+                * Does not expand paths via realpath,
+                * Aborts on errors during the file tree walk,
+                * Try to track inode associations for conflict detection,
+                * Does not follow mounts,
+                * Validates all file contexts at init time.
+                */
+               recurse = 1;
+               abort_on_error = 1;
+               add_assoc = 1;
+               /* follow_mounts = 0; - already is */
+               matchpathcon_flags = MATCHPATHCON_VALIDATE | MATCHPATHCON_NOTRANS;
+       } else {
+               /*
+                * restorecon:
+                * No recursive descent unless -r/-R,
+                * Expands paths via realpath,
+                * Do not abort on errors during the file tree walk,
+                * Do not try to track inode associations for conflict detection,
+                * Follows mounts,
+                * Does lazy validation of contexts upon use.
+                */
+               expand_realpath = 1;
+               follow_mounts = 1;
+               matchpathcon_flags = MATCHPATHCON_NOTRANS;
+               /* restorecon only */
+               selinux_or_die();
+       }
+
+       set_matchpathcon_flags(matchpathcon_flags);
+
+       opt_complementary = "e::vv:v--p:p--v:v--q:q--v";
+       /* Option order must match OPT_x definitions! */
+       if (applet_name[0] == 'r') { /* restorecon */
+               flags = getopt32(argv, "de:f:ilnpqrsvo:FWR",
+                       &exclude_dir, &input_filename, &out_filename, &verbose);
+       } else { /* setfiles */
+               flags = getopt32(argv, "de:f:ilnpqr:svo:FW"
+                               USE_FEATURE_SETFILES_CHECK_OPTION("c:"),
+                       &exclude_dir, &input_filename, &rootpath, &out_filename,
+                                USE_FEATURE_SETFILES_CHECK_OPTION(&policyfile,)
+                       &verbose);
+       }
+
+#if ENABLE_FEATURE_SETFILES_CHECK_OPTION
+       if ((applet_name[0] == 's') && (flags & OPT_c)) {
+               FILE *policystream;
+
+               policystream = xfopen_for_read(policyfile);
+               if (sepol_set_policydb_from_file(policystream) < 0) {
+                       bb_error_msg_and_die("sepol_set_policydb_from_file on %s", policyfile);
+               }
+               fclose(policystream);
+
+               /* Only process the specified file_contexts file, not
+                  any .homedirs or .local files, and do not perform
+                  context translations. */
+               set_matchpathcon_flags(MATCHPATHCON_BASEONLY |
+                                      MATCHPATHCON_NOTRANS |
+                                      MATCHPATHCON_VALIDATE);
+       }
+#endif
+
+       while (exclude_dir)
+               add_exclude(llist_pop(&exclude_dir));
+
+       if (flags & OPT_o) {
+               outfile = stdout;
+               if (NOT_LONE_CHAR(out_filename, '-')) {
+                       outfile = xfopen_for_write(out_filename);
+               }
+       }
+       if (applet_name[0] == 'r') { /* restorecon */
+               if (flags & (OPT_r | OPT_R))
+                       recurse = 1;
+       } else { /* setfiles */
+               if (flags & OPT_r)
+                       rootpathlen = strlen(rootpath);
+       }
+       if (flags & OPT_s) {
+               input_filename = "-";
+               add_assoc = 0;
+       }
+
+       if (applet_name[0] == 's') { /* setfiles */
+               /* Use our own invalid context checking function so that
+                  we can support either checking against the active policy or
+                  checking against a binary policy file. */
+               set_matchpathcon_canoncon(&canoncon);
+               if (argc == 1)
+                       bb_show_usage();
+               if (stat(argv[optind], &sb) < 0) {
+                       bb_simple_perror_msg_and_die(argv[optind]);
+               }
+               if (!S_ISREG(sb.st_mode)) {
+                       bb_error_msg_and_die("spec file %s is not a regular file", argv[optind]);
+               }
+               /* Load the file contexts configuration and check it. */
+               rc = matchpathcon_init(argv[optind]);
+               if (rc < 0) {
+                       bb_simple_perror_msg_and_die(argv[optind]);
+               }
+
+               optind++;
+
+               if (nerr)
+                       exit(EXIT_FAILURE);
+       }
+
+       if (input_filename) {
+               ssize_t len;
+               FILE *f = stdin;
+
+               if (NOT_LONE_CHAR(input_filename, '-'))
+                       f = xfopen_for_read(input_filename);
+               while ((len = getline(&buf, &buf_len, f)) > 0) {
+                       buf[len - 1] = '\0';
+                       errors |= process_one(buf);
+               }
+               if (ENABLE_FEATURE_CLEAN_UP)
+                       fclose_if_not_stdin(f);
+       } else {
+               if (optind >= argc)
+                       bb_show_usage();
+               for (i = optind; i < argc; i++) {
+                       errors |= process_one(argv[i]);
+               }
+       }
+
+       if (FLAG_W_warn_no_match)
+               matchpathcon_checkmatches(argv[0]);
+
+       if (ENABLE_FEATURE_CLEAN_UP && outfile)
+               fclose(outfile);
+
+       return errors;
+}
diff --git a/selinux/setsebool.c b/selinux/setsebool.c
new file mode 100644 (file)
index 0000000..b615ce7
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+ * setsebool
+ * Simple setsebool
+ * NOTE: -P option requires libsemanage, so this feature is
+ * omitted in this version
+ * Yuichi Nakamura <ynakam@hitachisoft.jp>
+ *
+ * Licensed under GPLv2, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.h"
+
+int setsebool_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int setsebool_main(int argc, char **argv)
+{
+       char *p;
+       int value;
+
+       if (argc != 3)
+               bb_show_usage();
+
+       p = argv[2];
+
+       if (LONE_CHAR(p, '1') || strcasecmp(p, "true") == 0 || strcasecmp(p, "on") == 0) {
+               value = 1;
+       } else if (LONE_CHAR(p, '0') || strcasecmp(p, "false") == 0 || strcasecmp(p, "off") == 0) {
+               value = 0;
+       } else {
+               bb_show_usage();
+       }
+
+       if (security_set_boolean(argv[1], value) < 0)
+               bb_error_msg_and_die("can't set boolean");
+
+       return 0;
+}
diff --git a/shell/Config.in b/shell/Config.in
new file mode 100644 (file)
index 0000000..57969f0
--- /dev/null
@@ -0,0 +1,360 @@
+#
+# 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
+       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_BASH_COMPAT
+       bool "bash-compatible extensions"
+       default y
+       depends on ASH
+       help
+         Enable bash-compatible extensions.
+
+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_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
+       depends on ASH
+       help
+         Enable support for echo, builtin to ash.
+
+config ASH_BUILTIN_PRINTF
+       bool "Builtin version of 'printf'"
+       default y
+       depends on ASH
+       help
+         Enable support for printf, builtin to ash.
+
+config ASH_BUILTIN_TEST
+       bool "Builtin version of 'test'"
+       default y
+       depends on ASH
+       help
+         Enable support for test, builtin 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 $RANDOM variable"
+       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" the 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 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
+       help
+         hush is a small shell (22k). It handles the normal flow control
+         constructs such as if/then/elif/else/fi, for/in/do/done, while loops,
+         case/esac. Redirections, here documents, $((arithmetic))
+         and functions are supported.
+
+         It will compile and work on no-mmu systems.
+
+         It does not handle select, aliases, brace expansion,
+         tilde expansion, &>file and >&file redirection of stdout+stderr.
+
+config HUSH_HELP
+       bool "help builtin"
+       default n
+       depends on HUSH
+       help
+         Enable help builtin in hush. Code size + ~1 kbyte.
+
+config HUSH_INTERACTIVE
+       bool "Interactive mode"
+       default y
+       depends on HUSH
+       help
+         Enable interactive mode (prompt and command editing).
+         Without this, hush simply reads and executes commands
+         from stdin just like a shell script from the file.
+         No prompt, no PS1/PS2 magic shell variables.
+
+config HUSH_JOB
+       bool "Job control"
+       default n
+       depends on HUSH_INTERACTIVE
+       help
+         Enable job control: Ctrl-Z backgrounds, Ctrl-C interrupts current
+         command (not entire shell), fg/bg builtins work. Without this option,
+         "cmd &" still works by simply spawning a process and immediately
+         prompting for next command (or executing next command in a script),
+         but no separate process group is formed.
+
+config HUSH_TICK
+       bool "Process substitution"
+       default n
+       depends on HUSH
+       help
+         Enable process substitution `command` and $(command) in hush.
+
+config HUSH_IF
+       bool "Support if/then/elif/else/fi"
+       default n
+       depends on HUSH
+       help
+         Enable if/then/elif/else/fi in hush.
+
+config HUSH_LOOPS
+       bool "Support for, while and until loops"
+       default n
+       depends on HUSH
+       help
+         Enable for, while and until loops in hush.
+
+config HUSH_CASE
+       bool "Support case ... esac statement"
+       default n
+       depends on HUSH
+       help
+         Enable case ... esac statement in hush. +400 bytes.
+
+config HUSH_FUNCTIONS
+       bool "Support funcname() { commands; } syntax"
+       default n
+       depends on HUSH
+       help
+         Enable support for shell functions in hush. +800 bytes.
+
+config HUSH_EXPORT_N
+       bool "Support export '-n' option"
+       default n
+       depends on HUSH
+       help
+         Enable support for export '-n' option in hush. It is a bash extension.
+
+config LASH
+       bool "lash (deprecated: aliased to hush)"
+       default n
+       select HUSH
+       help
+         lash is deprecated and will be removed, please migrate to hush.
+
+config MSH
+       bool "msh (deprecated: please use hush)"
+       default n
+       help
+         msh is deprecated and will be removed, please migrate to hush.
+         If there is a feature msh has but hush does not, please let us know.
+
+#        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 uses only vfork, so it can be used on uClinux systems.
+
+
+comment "Bourne Shell Options"
+       depends on MSH || LASH || HUSH || ASH
+
+config SH_MATH_SUPPORT
+       bool "POSIX math support"
+       default y
+       depends on ASH || HUSH
+       help
+         Enable math support in the shell via $((...)) syntax.
+
+config SH_MATH_SUPPORT_64
+       bool "Extend POSIX math support to 64 bit"
+       default n
+       depends on SH_MATH_SUPPORT
+       help
+         Enable 64-bit math support in the shell. This will make the shell
+         slightly larger, but will allow computation with very large numbers.
+         This is not in POSIX, so do not rely on this in portable code.
+
+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
+       bool "Standalone shell"
+       default n
+       depends on (MSH || LASH || HUSH || ASH) && FEATURE_PREFER_APPLETS
+       help
+         This option causes busybox shells 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.
+
+         This is implemented by re-execing /proc/self/exe (typically)
+         with right parameters. Some selected applets ("NOFORK" applets)
+         can even be executed without creating new process.
+         Instead, busybox will call <applet>_main() internally.
+
+         However, this causes problems in chroot jails without mounted /proc
+         and with ps/top (command name can be shown as 'exe' for applets
+         started this way).
+# untrue?
+#        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.
+# untrue?
+#        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_SH_NOFORK
+       bool "Run 'nofork' applets directly"
+       default n
+       depends on (MSH || LASH || HUSH || ASH) && FEATURE_PREFER_APPLETS
+       help
+         This option causes busybox shells [currently only ash]
+         to not execute typical fork/exec/wait sequence, but call <applet>_main
+         directly, if possible. (Sometimes it is not possible: for example,
+         this is not possible in pipes).
+
+         This will be done only for some applets (those which are marked
+         NOFORK in include/applets.h).
+
+         This may significantly speed up some shell scripts.
+
+         This feature is relatively new. Use with care.
+
+config CTTYHACK
+       bool "cttyhack"
+       default n
+       help
+         One common problem reported on the mailing list is "can't access tty;
+         job control turned off" error message which typically appears when
+         one tries to use shell with stdin/stdout opened to /dev/console.
+         This device is special - it cannot be a controlling tty.
+
+         Proper solution is to use correct device instead of /dev/console.
+
+         cttyhack provides "quick and dirty" solution to this problem.
+         It analyzes stdin with various ioctls, trying to determine whether
+         it is a /dev/ttyN or /dev/ttySN (virtual terminal or serial line).
+         If it detects one, it closes stdin/out/err and reopens that device.
+         Then it executes given program. Usage example for /etc/inittab
+         (for busybox init):
+
+         ::respawn:/bin/cttyhack /bin/sh
+
+endmenu
diff --git a/shell/Kbuild b/shell/Kbuild
new file mode 100644 (file)
index 0000000..8b693ec
--- /dev/null
@@ -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 ash_ptr_hack.o
+lib-$(CONFIG_HUSH)     += hush.o match.o
+lib-$(CONFIG_MSH)      += msh.o
+lib-$(CONFIG_CTTYHACK) += cttyhack.o
+lib-$(CONFIG_SH_MATH_SUPPORT) += math.o
diff --git a/shell/README b/shell/README
new file mode 100644 (file)
index 0000000..59efe49
--- /dev/null
@@ -0,0 +1,108 @@
+Various bits of what is known about busybox shells, in no particular order.
+
+2008-02-14
+ash: does not restore tty pgrp if killed by HUP. Symptom: Midnight Commander
+is backgrounded if you started ash under it, and then killed it with HUP.
+
+2007-11-23
+hush: fixed bogus glob handling; fixed exec <"$1"; added test and echo builtins
+
+2007-06-13
+hush: exec <"$1" doesn't do parameter subst
+
+2007-05-24
+hush: environment-related memory leak plugged, with net code size
+decrease.
+
+2007-05-24
+hush: '( echo ${name )' will show syntax error message, but prompt
+doesn't return (need to press <enter>). Pressing Ctrl-C, <enter>,
+'( echo ${name )' again, Ctrl-C segfaults.
+
+2007-05-21
+hush: environment cannot be handled by libc routines as they are leaky
+(by API design and thus unfixable): hush will leak memory in this script,
+bash does not:
+pid=$$
+while true; do
+    unset t;
+    t=111111111111111111111111111111111111111111111111111111111111111111111111
+    export t
+    ps -o vsz,pid,comm | grep " $pid "
+done
+The fix is to not use setenv/putenv/unsetenv but manipulate env ourself. TODO.
+hush: meanwhile, first three command subst bugs mentioned below are fixed. :)
+
+2007-05-06
+hush: more bugs spotted. Comparison with bash:
+bash-3.2# echo "TEST`date;echo;echo`BEST"
+TESTSun May  6 09:21:05 CEST 2007BEST         [we dont strip eols]
+bash-3.2# echo "TEST`echo '$(echo ZZ)'`BEST"
+TEST$(echo ZZ)BEST                            [we execute inner echo]
+bash-3.2# echo "TEST`echo "'"`BEST"
+TEST'BEST                                     [we totally mess up this one]
+bash-3.2# echo `sleep 5`
+[Ctrl-C should work, Ctrl-Z should do nothing][we totally mess up this one]
+bash-3.2# if true; then
+> [Ctrl-C]
+bash-3.2#                                     [we re-issue "> "]
+bash-3.2# if echo `sleep 5`; then
+> true; fi                                    [we execute sleep before "> "]
+
+2007-05-04
+hush: made ctrl-Z/C work correctly for "while true; do true; done"
+(namely, it backgrounds/interrupts entire "while")
+
+2007-05-03
+hush: new bug spotted: Ctrl-C on "while true; do true; done" doesn't
+work right:
+# while true; do true; done
+[1] 0 true <-- pressing Ctrl-C several times...
+[2] 0 true
+[3] 0 true
+Segmentation fault
+
+2007-05-03
+hush: update on "sleep 1 | exit 3; echo $?" bug.
+parse_stream_outer() repeatedly calls parse_stream().
+parse_stream() is now fixed to stop on ';' in this example,
+fixing it (parse_stream_outer() will call parse_stream() 1st time,
+execute the parse tree, call parse_stream() 2nd time and execute the tree).
+But it's not the end of story.
+In more complex situations we _must_ parse way farther before executing.
+Example #2: "{ sleep 1 | exit 3; echo $?; ...few_lines... } >file".
+Because of redirection, we cannot execute 1st pipe before we parse it all.
+We probably need to learn to store $var expressions in parse tree.
+Debug printing of parse tree would be nice too.
+
+2007-04-28
+hush: Ctrl-C and Ctrl-Z for single NOFORK commands are working.
+Memory and other resource leaks (opendir) are not addressed
+(testcase is "rm -i" interrupted by ctrl-c).
+
+2007-04-21
+hush: "sleep 5 | sleep 6" + Ctrl-Z + fg seems to work.
+"rm -i" + Ctrl-C, "sleep 5" + Ctrl-Z still doesn't work
+for SH_STANDALONE case :(
+
+2007-04-21
+hush: fixed non-backgrounding of "sleep 1 &" and totally broken
+"sleep 1 | sleep 2 &". Noticed a bug where successive jobs
+get numbers 1,2,3 even when job #1 has exited before job# 2 is started.
+(bash reuses #1 in this case)
+
+2007-04-21
+hush: "sleep 1 | exit 3; echo $?" prints 0 because $? is substituted
+_before_ pipe gets executed!! run_list_real() already has "pipe;echo"
+parsed and handed to it for execution, so it sees "pipe"; "echo 0".
+
+2007-04-21
+hush: removed setsid() and made job control sort-of-sometimes-work.
+Ctrl-C in "rm -i" works now except for SH_STANDALONE case.
+"sleep 1 | exit 3" + "echo $?" works, "sleep 1 | exit 3; echo $?"
+shows exitcode 0 (should be 3). "sleep 1 | sleep 2 &" fails horribly.
+
+2007-04-14
+lash, hush: both do setsid() and as a result don't have ctty!
+Ctrl-C doesn't work for any child (try rm -i), etc...
+lash: bare ">file" doesn't create a file (hush works)
diff --git a/shell/README.job b/shell/README.job
new file mode 100644 (file)
index 0000000..d5ba965
--- /dev/null
@@ -0,0 +1,304 @@
+strace of "sleep 1 | sleep 2" being run from interactive bash 3.0
+
+
+Synopsis:
+open /dev/tty [, if fails, open ttyname(0)]; close /* helps re-establish ctty */
+get current signal mask
+TCGETS on fd# 0
+TCGETS on fd# 2 /* NB: if returns ENOTTY (2>/dev/null), sh seems to disable job control,
+                   does not show prompt, but still executes cmds from fd# 0 */
+install default handlers for CHLD QUIT TERM
+install common handler for HUP INT ILL TRAP ABRT FPE BUS SEGV SYS PIPE ALRM TERM XCPU XFSZ VTALRM USR1 USR2
+ignore QUIT
+install handler for INT
+ignore TERM
+install handler for INT
+ignore TSTP TTOU TTIN
+install handler for WINCH
+get pid, ppid
+block all signals
+unblock all signals
+get our pprocess group
+    minidoc:
+    Each process group is a member of a session and each process is a member
+    of the session of which its process group is a member.
+    Process groups are used for distribution of signals, and by terminals
+    to arbitrate requests for their input: processes that have the same
+    process group as the terminal are foreground and may read, while others
+    will block with a signal if they attempt to read.  These calls are thus used
+    by programs (shells) to create process groups in implementing job control.
+    The TIOCGPGRP and TIOCSPGRP calls described in termios(3) are used to get/set
+    the process group of the control terminal.
+    If a session has a controlling terminal, CLOCAL is not set and a hangup occurs,
+    then the session leader is sent a SIGHUP.  If the session leader exits,
+    the SIGHUP signal will be sent to each process in the foreground process
+    group of the controlling terminal.
+    If the exit of the process causes a process group to become orphaned,
+    and if any member of the newly-orphaned process group is stopped, then a SIGHUP
+    signal followed by a SIGCONT signal will be sent to each process
+    in the newly-orphaned process group.
+...
+dup stderr to fd# 255
+move ourself to our own process group
+block CHLD TSTP TTIN TTOU
+set tty's (255, stderr's) foreground process group to our group
+allow all signals
+mark 255 CLOEXEC
+set CHLD handler
+get signal mask
+get fd#0 flags
+get signal mask
+set INT handler
+block CHLD TSTP TTIN TTOU
+set fd #255 foreground process group to our group
+allow all signals
+set INT handler
+block all signals
+allow all signals
+block INT
+allow all signals
+lotsa sigactions: set INT,ALRM,WINCH handlers, ignore TERM,QUIT,TSTP,TTOU,TTIN
+block all signals
+allow all signals
+block all signals
+allow all signals
+block all signals
+allow all signals
+read "sleep 1 | sleep 2\n"
+block INT
+TCSETSW on fd# 0
+allow all signals
+lotsa sigactions: set INT,ALRM,WINCH handlers, ignore TERM,QUIT,TSTP,TTOU,TTIN
+block CHLD
+pipe([4, 5])  /* oops seems I lost another pipe() in editing... */
+fork child #1
+put child in it's own process group
+block only CHLD
+close(5)
+block only INT CHLD
+fork child #2
+put child in the same process group as first one
+block only CHLD
+close(4)
+block only CHLD
+block only CHLD TSTP TTIN TTOU
+set fd# 255 foreground process group to first child's one
+block only CHLD
+block only CHLD
+block only CHLD
+/* note: because shell is not in foreground now, e.g. Ctrl-C will send INT to children only! */
+wait4 for children to die or stop - first child exits
+wait4 for children to die or stop - second child exits
+block CHLD TSTP TTIN TTOU
+set fd# 255 foreground process group to our own one
+block only CHLD
+block only CHLD
+block nothing
+--- SIGCHLD (Child exited) @ 0 (0) ---
+    wait for it - no child (already waited for)
+    sigreturn()
+read signal mask
+lotsa sigactions...
+read next command
+
+
+execve("/bin/sh", ["sh"], [/* 34 vars */]) = 0
+rt_sigprocmask(SIG_BLOCK, NULL, [], 8) = 0
+ioctl(0, SNDCTL_TMR_TIMEBASE or TCGETS, {B38400 opost isig icanon echo ...}) = 0
+ioctl(2, SNDCTL_TMR_TIMEBASE or TCGETS, {B38400 opost isig icanon echo ...}) = 0
+rt_sigaction(SIGCHLD, {SIG_DFL}, {SIG_DFL}, 8) = 0
+rt_sigaction(SIGQUIT, {SIG_DFL}, {SIG_DFL}, 8) = 0
+rt_sigaction(SIGTERM, {SIG_DFL}, {SIG_DFL}, 8) = 0
+rt_sigaction(SIGHUP, {0x808f752, [HUP INT ILL TRAP ABRT BUS FPE USR1 SEGV USR2 PIPE ALRM TERM XCPU XFSZ VTALRM SYS], SA_RESTORER, 0x6ff7a4f8}, {SIG_DFL}, 8) = 0
+rt_sigaction(SIGINT, {0x808f752, [HUP INT ILL TRAP ABRT BUS FPE USR1 SEGV USR2 PIPE ALRM TERM XCPU XFSZ VTALRM SYS], SA_RESTORER, 0x6ff7a4f8}, {SIG_DFL}, 8) = 0
+rt_sigaction(SIGILL, {0x808f752, [HUP INT ILL TRAP ABRT BUS FPE USR1 SEGV USR2 PIPE ALRM TERM XCPU XFSZ VTALRM SYS], SA_RESTORER, 0x6ff7a4f8}, {SIG_DFL}, 8) = 0
+rt_sigaction(SIGTRAP, {0x808f752, [HUP INT ILL TRAP ABRT BUS FPE USR1 SEGV USR2 PIPE ALRM TERM XCPU XFSZ VTALRM SYS], SA_RESTORER, 0x6ff7a4f8}, {SIG_DFL}, 8) = 0
+rt_sigaction(SIGABRT, {0x808f752, [HUP INT ILL TRAP ABRT BUS FPE USR1 SEGV USR2 PIPE ALRM TERM XCPU XFSZ VTALRM SYS], SA_RESTORER, 0x6ff7a4f8}, {SIG_DFL}, 8) = 0
+rt_sigaction(SIGFPE, {0x808f752, [HUP INT ILL TRAP ABRT BUS FPE USR1 SEGV USR2 PIPE ALRM TERM XCPU XFSZ VTALRM SYS], SA_RESTORER, 0x6ff7a4f8}, {SIG_DFL}, 8) = 0
+rt_sigaction(SIGBUS, {0x808f752, [HUP INT ILL TRAP ABRT BUS FPE USR1 SEGV USR2 PIPE ALRM TERM XCPU XFSZ VTALRM SYS], SA_RESTORER, 0x6ff7a4f8}, {SIG_DFL}, 8) = 0
+rt_sigaction(SIGSEGV, {0x808f752, [HUP INT ILL TRAP ABRT BUS FPE USR1 SEGV USR2 PIPE ALRM TERM XCPU XFSZ VTALRM SYS], SA_RESTORER, 0x6ff7a4f8}, {SIG_DFL}, 8) = 0
+rt_sigaction(SIGSYS, {0x808f752, [HUP INT ILL TRAP ABRT BUS FPE USR1 SEGV USR2 PIPE ALRM TERM XCPU XFSZ VTALRM SYS], SA_RESTORER, 0x6ff7a4f8}, {SIG_DFL}, 8) = 0
+rt_sigaction(SIGPIPE, {0x808f752, [HUP INT ILL TRAP ABRT BUS FPE USR1 SEGV USR2 PIPE ALRM TERM XCPU XFSZ VTALRM SYS], SA_RESTORER, 0x6ff7a4f8}, {SIG_DFL}, 8) = 0
+rt_sigaction(SIGALRM, {0x808f752, [HUP INT ILL TRAP ABRT BUS FPE USR1 SEGV USR2 PIPE ALRM TERM XCPU XFSZ VTALRM SYS], SA_RESTORER, 0x6ff7a4f8}, {SIG_DFL}, 8) = 0
+rt_sigaction(SIGTERM, {0x808f752, [HUP INT ILL TRAP ABRT BUS FPE USR1 SEGV USR2 PIPE ALRM TERM XCPU XFSZ VTALRM SYS], SA_RESTORER, 0x6ff7a4f8}, {SIG_DFL}, 8) = 0
+rt_sigaction(SIGXCPU, {0x808f752, [HUP INT ILL TRAP ABRT BUS FPE USR1 SEGV USR2 PIPE ALRM TERM XCPU XFSZ VTALRM SYS], SA_RESTORER, 0x6ff7a4f8}, {SIG_DFL}, 8) = 0
+rt_sigaction(SIGXFSZ, {0x808f752, [HUP INT ILL TRAP ABRT BUS FPE USR1 SEGV USR2 PIPE ALRM TERM XCPU XFSZ VTALRM SYS], SA_RESTORER, 0x6ff7a4f8}, {SIG_DFL}, 8) = 0
+rt_sigaction(SIGVTALRM, {0x808f752, [HUP INT ILL TRAP ABRT BUS FPE USR1 SEGV USR2 PIPE ALRM TERM XCPU XFSZ VTALRM SYS], SA_RESTORER, 0x6ff7a4f8}, {SIG_DFL}, 8) = 0
+rt_sigaction(SIGUSR1, {0x808f752, [HUP INT ILL TRAP ABRT BUS FPE USR1 SEGV USR2 PIPE ALRM TERM XCPU XFSZ VTALRM SYS], SA_RESTORER, 0x6ff7a4f8}, {SIG_DFL}, 8) = 0
+rt_sigaction(SIGUSR2, {0x808f752, [HUP INT ILL TRAP ABRT BUS FPE USR1 SEGV USR2 PIPE ALRM TERM XCPU XFSZ VTALRM SYS], SA_RESTORER, 0x6ff7a4f8}, {SIG_DFL}, 8) = 0
+rt_sigprocmask(SIG_BLOCK, NULL, [], 8) = 0
+rt_sigaction(SIGQUIT, {SIG_IGN}, {SIG_DFL}, 8) = 0
+rt_sigaction(SIGINT, {0x808f7d7, [], SA_RESTORER, 0x6ff7a4f8}, {0x808f752, [HUP INT ILL TRAP ABRT BUS FPE USR1 SEGV USR2 PIPE ALRM TERM XCPU XFSZ VTALRM SYS], SA_RESTORER, 0x6ff7a4f8}, 8) = 0
+rt_sigaction(SIGTERM, {SIG_IGN}, {0x808f752, [HUP INT ILL TRAP ABRT BUS FPE USR1 SEGV USR2 PIPE ALRM TERM XCPU XFSZ VTALRM SYS], SA_RESTORER, 0x6ff7a4f8}, 8) = 0
+rt_sigaction(SIGINT, {0x808f7d7, [], SA_RESTORER, 0x6ff7a4f8}, {0x808f7d7, [], SA_RESTORER, 0x6ff7a4f8}, 8) = 0
+rt_sigaction(SIGTSTP, {SIG_IGN}, {SIG_DFL}, 8) = 0
+rt_sigaction(SIGTTOU, {SIG_IGN}, {SIG_DFL}, 8) = 0
+rt_sigaction(SIGTTIN, {SIG_IGN}, {SIG_DFL}, 8) = 0
+rt_sigaction(SIGWINCH, {0x807dc33, [], SA_RESTORER, 0x6ff7a4f8}, {SIG_DFL}, 8) = 0
+getpid()                = 19473
+getppid()               = 19472
+rt_sigprocmask(SIG_BLOCK, ~[], [], 8) = 0
+rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
+getpgrp()               = 1865
+dup(2)                  = 4
+fcntl64(255, F_GETFD)   = -1 EBADF (Bad file descriptor)
+dup2(4, 255)            = 255
+close(4)                = 0
+ioctl(255, TIOCGPGRP, [1865]) = 0
+getpid()                = 19473
+setpgid(0, 19473)       = 0
+rt_sigprocmask(SIG_BLOCK, [CHLD TSTP TTIN TTOU], [], 8) = 0
+ioctl(255, TIOCSPGRP, [19473]) = 0
+rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
+fcntl64(255, F_SETFD, FD_CLOEXEC) = 0
+rt_sigaction(SIGCHLD, {0x807c922, [], SA_RESTORER, 0x6ff7a4f8}, {SIG_DFL}, 8) = 0
+rt_sigprocmask(SIG_BLOCK, NULL, [], 8) = 0
+fcntl64(0, F_GETFL)     = 0x2 (flags O_RDWR)
+...
+rt_sigprocmask(SIG_BLOCK, NULL, [], 8) = 0
+rt_sigaction(SIGINT, {0x808f7d7, [], SA_RESTORER, 0x6ff7a4f8}, {0x808f7d7, [], SA_RESTORER, 0x6ff7a4f8}, 8) = 0
+rt_sigprocmask(SIG_BLOCK, [CHLD TSTP TTIN TTOU], [], 8) = 0
+ioctl(255, TIOCSPGRP, [19473]) = 0
+rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
+rt_sigaction(SIGINT, {0x808f7d7, [], SA_RESTORER, 0x6ff7a4f8}, {0x808f7d7, [], SA_RESTORER, 0x6ff7a4f8}, 8) = 0
+rt_sigprocmask(SIG_BLOCK, ~[], [], 8) = 0
+rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
+rt_sigprocmask(SIG_BLOCK, [INT], [], 8) = 0
+rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
+rt_sigaction(SIGINT, {0x80ca530, [], SA_RESTORER, 0x6ff7a4f8}, {0x808f7d7, [], SA_RESTORER, 0x6ff7a4f8}, 8) = 0
+rt_sigaction(SIGTERM, {0x80ca530, [], SA_RESTORER, 0x6ff7a4f8}, {SIG_IGN}, 8) = 0
+rt_sigaction(SIGTERM, {SIG_IGN}, {0x80ca530, [], SA_RESTORER, 0x6ff7a4f8}, 8) = 0
+rt_sigaction(SIGQUIT, {0x80ca530, [], SA_RESTORER, 0x6ff7a4f8}, {SIG_IGN}, 8) = 0
+rt_sigaction(SIGQUIT, {SIG_IGN}, {0x80ca530, [], SA_RESTORER, 0x6ff7a4f8}, 8) = 0
+rt_sigaction(SIGALRM, {0x80ca530, [], SA_RESTORER, 0x6ff7a4f8}, {0x808f752, [HUP INT ILL TRAP ABRT BUS FPE USR1 SEGV USR2 PIPE ALRM TERM XCPU XFSZ VTALRM SYS], SA_RESTORER, 0x6ff7a4f8}, 8) = 0
+rt_sigaction(SIGTSTP, {0x80ca530, [], SA_RESTORER, 0x6ff7a4f8}, {SIG_IGN}, 8) = 0
+rt_sigaction(SIGTSTP, {SIG_IGN}, {0x80ca530, [], SA_RESTORER, 0x6ff7a4f8}, 8) = 0
+rt_sigaction(SIGTTOU, {0x80ca530, [], SA_RESTORER, 0x6ff7a4f8}, {SIG_IGN}, 8) = 0
+rt_sigaction(SIGTTOU, {SIG_IGN}, {0x80ca530, [], SA_RESTORER, 0x6ff7a4f8}, 8) = 0
+rt_sigaction(SIGTTIN, {0x80ca530, [], SA_RESTORER, 0x6ff7a4f8}, {SIG_IGN}, 8) = 0
+rt_sigaction(SIGTTIN, {SIG_IGN}, {0x80ca530, [], SA_RESTORER, 0x6ff7a4f8}, 8) = 0
+rt_sigaction(SIGWINCH, {0x80ca5cd, [], SA_RESTORER|SA_RESTART, 0x6ff7a4f8}, {0x807dc33, [], SA_RESTORER, 0x6ff7a4f8}, 8) = 0
+rt_sigprocmask(SIG_BLOCK, ~[], [], 8) = 0
+rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
+rt_sigprocmask(SIG_BLOCK, ~[], [], 8) = 0
+rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
+rt_sigprocmask(SIG_BLOCK, ~[], [], 8) = 0
+rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
+write(2, "sh-3.00# ", 9) = 9
+rt_sigprocmask(SIG_BLOCK, NULL, [], 8) = 0
+read(0, "s", 1)         = 1
+write(2, "s", 1)        = 1
+rt_sigprocmask(SIG_BLOCK, NULL, [], 8) = 0
+read(0, "l", 1)         = 1
+write(2, "l", 1)        = 1
+rt_sigprocmask(SIG_BLOCK, NULL, [], 8) = 0
+... rest of "sleep 1 | sleep 2" entered...
+rt_sigprocmask(SIG_BLOCK, NULL, [], 8) = 0
+read(0, "2", 1)         = 1
+write(2, "2", 1)        = 1
+rt_sigprocmask(SIG_BLOCK, NULL, [], 8) = 0
+read(0, "\r", 1)        = 1
+write(2, "\n", 1)       = 1
+rt_sigprocmask(SIG_BLOCK, [INT], [], 8) = 0
+ioctl(0, SNDCTL_TMR_STOP or TCSETSW, {B38400 opost isig icanon echo ...}) = 0
+rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
+rt_sigaction(SIGINT, {0x808f7d7, [], SA_RESTORER, 0x6ff7a4f8}, {0x80ca530, [], SA_RESTORER, 0x6ff7a4f8}, 8) = 0
+rt_sigaction(SIGTERM, {SIG_IGN}, {SIG_IGN}, 8) = 0
+rt_sigaction(SIGQUIT, {SIG_IGN}, {SIG_IGN}, 8) = 0
+rt_sigaction(SIGALRM, {0x808f752, [HUP INT ILL TRAP ABRT BUS FPE USR1 SEGV USR2 PIPE ALRM TERM XCPU XFSZ VTALRM SYS], SA_RESTORER, 0x6ff7a4f8}, {0x80ca530, [], SA_RESTORER, 0x6ff7a4f8}, 8) = 0
+rt_sigaction(SIGTSTP, {SIG_IGN}, {SIG_IGN}, 8) = 0
+rt_sigaction(SIGTTOU, {SIG_IGN}, {SIG_IGN}, 8) = 0
+rt_sigaction(SIGTTIN, {SIG_IGN}, {SIG_IGN}, 8) = 0
+rt_sigaction(SIGWINCH, {0x807dc33, [], SA_RESTORER, 0x6ff7a4f8}, {0x80ca5cd, [], SA_RESTORER|SA_RESTART, 0x6ff7a4f8}, 8) = 0
+rt_sigaction(SIGINT, {0x808f7d7, [], SA_RESTORER, 0x6ff7a4f8}, {0x808f7d7, [], SA_RESTORER, 0x6ff7a4f8}, 8) = 0
+rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
+pipe([4, 5])            = 0
+rt_sigprocmask(SIG_BLOCK, [INT CHLD], [CHLD], 8) = 0
+fork()                  = 19755
+setpgid(19755, 19755)                = 0
+rt_sigprocmask(SIG_SETMASK, [CHLD], NULL, 8) = 0
+close(5)                = 0
+rt_sigprocmask(SIG_BLOCK, [INT CHLD], [CHLD], 8) = 0
+fork()                  = 19756
+setpgid(19756, 19755)   = 0
+rt_sigprocmask(SIG_SETMASK, [CHLD], NULL, 8) = 0
+close(4)                = 0
+rt_sigprocmask(SIG_BLOCK, [CHLD], [CHLD], 8) = 0
+rt_sigprocmask(SIG_BLOCK, [CHLD TSTP TTIN TTOU], [CHLD], 8) = 0
+ioctl(255, TIOCSPGRP, [19755]) = 0
+rt_sigprocmask(SIG_SETMASK, [CHLD], NULL, 8) = 0
+rt_sigprocmask(SIG_SETMASK, [CHLD], NULL, 8) = 0
+rt_sigprocmask(SIG_BLOCK, [CHLD], [CHLD], 8) = 0
+wait4(-1, [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], WUNTRACED, NULL) = 19755
+wait4(-1, [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], WUNTRACED, NULL) = 19756
+rt_sigprocmask(SIG_BLOCK, [CHLD TSTP TTIN TTOU], [CHLD], 8) = 0
+ioctl(255, TIOCSPGRP, [19473]) = 0
+rt_sigprocmask(SIG_SETMASK, [CHLD], NULL, 8) = 0
+rt_sigprocmask(SIG_SETMASK, [CHLD], NULL, 8) = 0
+rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
+--- SIGCHLD (Child exited) @ 0 (0) ---
+wait4(-1, 0x77fc9c54, WNOHANG|WUNTRACED, NULL) = -1 ECHILD (No child processes)
+sigreturn()             = ? (mask now [])
+rt_sigprocmask(SIG_BLOCK, NULL, [], 8) = 0
+rt_sigaction(SIGINT, {0x808f7d7, [], SA_RESTORER, 0x6ff7a4f8}, {0x808f7d7, [], SA_RESTORER, 0x6ff7a4f8}, 8) = 0
+rt_sigprocmask(SIG_BLOCK, [CHLD TSTP TTIN TTOU], [], 8) = 0
+ioctl(255, TIOCSPGRP, [19473]) = 0
+rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
+rt_sigaction(SIGINT, {0x808f7d7, [], SA_RESTORER, 0x6ff7a4f8}, {0x808f7d7, [], SA_RESTORER, 0x6ff7a4f8}, 8) = 0
+rt_sigprocmask(SIG_BLOCK, [INT], [], 8) = 0
+rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
+rt_sigaction(SIGINT, {0x80ca530, [], SA_RESTORER, 0x6ff7a4f8}, {0x808f7d7, [], SA_RESTORER, 0x6ff7a4f8}, 8) = 0
+rt_sigaction(SIGTERM, {0x80ca530, [], SA_RESTORER, 0x6ff7a4f8}, {SIG_IGN}, 8) = 0
+rt_sigaction(SIGTERM, {SIG_IGN}, {0x80ca530, [], SA_RESTORER, 0x6ff7a4f8}, 8) = 0
+rt_sigaction(SIGQUIT, {0x80ca530, [], SA_RESTORER, 0x6ff7a4f8}, {SIG_IGN}, 8) = 0
+rt_sigaction(SIGQUIT, {SIG_IGN}, {0x80ca530, [], SA_RESTORER, 0x6ff7a4f8}, 8) = 0
+rt_sigaction(SIGALRM, {0x80ca530, [], SA_RESTORER, 0x6ff7a4f8}, {0x808f752, [HUP INT ILL TRAP ABRT BUS FPE USR1 SEGV USR2 PIPE ALRM TERM XCPU XFSZ VTALRM SYS], SA_RESTORER, 0x6ff7a4f8}, 8) = 0
+rt_sigaction(SIGTSTP, {0x80ca530, [], SA_RESTORER, 0x6ff7a4f8}, {SIG_IGN}, 8) = 0
+rt_sigaction(SIGTSTP, {SIG_IGN}, {0x80ca530, [], SA_RESTORER, 0x6ff7a4f8}, 8) = 0
+rt_sigaction(SIGTTOU, {0x80ca530, [], SA_RESTORER, 0x6ff7a4f8}, {SIG_IGN}, 8) = 0
+rt_sigaction(SIGTTOU, {SIG_IGN}, {0x80ca530, [], SA_RESTORER, 0x6ff7a4f8}, 8) = 0
+rt_sigaction(SIGTTIN, {0x80ca530, [], SA_RESTORER, 0x6ff7a4f8}, {SIG_IGN}, 8) = 0
+rt_sigaction(SIGTTIN, {SIG_IGN}, {0x80ca530, [], SA_RESTORER, 0x6ff7a4f8}, 8) = 0
+rt_sigaction(SIGWINCH, {0x80ca5cd, [], SA_RESTORER|SA_RESTART, 0x6ff7a4f8}, {0x807dc33, [], SA_RESTORER, 0x6ff7a4f8}, 8) = 0
+write(2, "sh-3.00# ", 9) = 9
+
+
+getpid() = 19755
+rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
+rt_sigaction(SIGTSTP, {SIG_DFL}, {SIG_IGN}, 8)    = 0
+rt_sigaction(SIGTTIN, {SIG_DFL}, {SIG_IGN}, 8) = 0
+rt_sigaction(SIGTTOU, {SIG_DFL}, {SIG_IGN}, 8) = 0
+setpgid(19755, 19755) = 0
+rt_sigprocmask(SIG_BLOCK, [CHLD TSTP TTIN TTOU], [], 8) = 0
+ioctl(255, TIOCSPGRP, [19755]) = 0
+rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
+close(4)   = 0
+dup2(5, 1) = 1
+close(5)              = 0
+rt_sigaction(SIGINT, {SIG_DFL}, {0x808f7d7, [], SA_RESTORER, 0x6ff7a4f8}, 8) = 0
+rt_sigaction(SIGQUIT, {SIG_DFL}, {SIG_IGN}, 8) = 0
+rt_sigaction(SIGTERM, {SIG_DFL}, {SIG_IGN}, 8) = 0
+rt_sigaction(SIGCHLD, {SIG_DFL}, {0x807c922, [], SA_RESTORER, 0x6ff7a4f8}, 8) = 0
+execve("/bin/sleep", ["sleep", "1"], [/* 34 vars */]) = 0
+...
+_exit(0)                = ?
+
+
+getpid() = 19756
+rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
+rt_sigaction(SIGTSTP, {SIG_DFL}, {SIG_IGN}, 8) = 0
+rt_sigaction(SIGTTIN, {SIG_DFL}, {SIG_IGN}, 8) = 0
+rt_sigaction(SIGTTOU, {SIG_DFL}, {SIG_IGN}, 8) = 0
+setpgid(19756, 19755) = 0
+rt_sigprocmask(SIG_BLOCK, [CHLD TSTP TTIN TTOU], [], 8) = 0
+ioctl(255, TIOCSPGRP, [19755]) = 0
+rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
+dup2(4, 0) = 0
+close(4) = 0
+rt_sigaction(SIGINT, {SIG_DFL}, {0x808f7d7, [], SA_RESTORER, 0x6ff7a4f8}, 8) = 0
+rt_sigaction(SIGQUIT, {SIG_DFL}, {SIG_IGN}, 8) = 0
+rt_sigaction(SIGTERM, {SIG_DFL}, {SIG_IGN}, 8) = 0
+rt_sigaction(SIGCHLD, {SIG_DFL}, {0x807c922, [], SA_RESTORER, 0x6ff7a4f8}, 8) = 0
+execve("/bin/sleep", ["sleep", "2"], [/* 34 vars */]) = 0
+...
+_exit(0)                = ?
diff --git a/shell/ash.c b/shell/ash.c
new file mode 100644 (file)
index 0000000..4981f4c
--- /dev/null
@@ -0,0 +1,13279 @@
+/* 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.
+ */
+
+/*
+ * The following 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
+/* Tweak debug output verbosity here */
+#define DEBUG_TIME 0
+#define DEBUG_PID 1
+#define DEBUG_SIG 1
+
+#define PROFILE 0
+
+#define IFS_BROKEN
+
+#define JOBS ENABLE_ASH_JOB_CONTROL
+
+#if DEBUG
+# ifndef _GNU_SOURCE
+#  define _GNU_SOURCE
+# endif
+#endif
+
+#include "busybox.h" /* for applet_names */
+//TODO: pull in some .h and find out do we have SINGLE_APPLET_MAIN?
+//#include "applet_tables.h" doesn't work
+#include <paths.h>
+#include <setjmp.h>
+#include <fnmatch.h>
+#include "math.h"
+
+#if defined SINGLE_APPLET_MAIN
+/* STANDALONE does not make sense, and won't compile */
+#undef CONFIG_FEATURE_SH_STANDALONE
+#undef ENABLE_FEATURE_SH_STANDALONE
+#undef USE_FEATURE_SH_STANDALONE
+#undef SKIP_FEATURE_SH_STANDALONE(...)
+#define ENABLE_FEATURE_SH_STANDALONE 0
+#define USE_FEATURE_SH_STANDALONE(...)
+#define SKIP_FEATURE_SH_STANDALONE(...) __VA_ARGS__
+#endif
+
+#ifndef PIPE_BUF
+# define PIPE_BUF 4096           /* amount of buffering in a pipe */
+#endif
+
+#if defined(__uClinux__)
+# error "Do not even bother, ash will not run on NOMMU machine"
+#endif
+
+
+/* ============ Hash table sizes. Configurable. */
+
+#define VTABSIZE 39
+#define ATABSIZE 39
+#define CMDTABLESIZE 31         /* should be prime */
+
+
+/* ============ Shell options */
+
+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])
+
+enum { NOPTS = ARRAY_SIZE(optletters_optnames) };
+
+
+/* ============ Misc data */
+
+static const char homestr[] ALIGN1 = "HOME";
+static const char snlfmt[] ALIGN1 = "%s\n";
+static const char illnum[] ALIGN1 = "Illegal number: %s";
+
+/*
+ * 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_type
+ * 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;
+};
+
+struct globals_misc {
+       /* pid of main shell */
+       int rootpid;
+       /* shell level: 0 for the main shell, 1 for its children, and so on */
+       int shlvl;
+#define rootshell (!shlvl)
+       char *minusc;  /* argument to -c option */
+
+       char *curdir; // = nullstr;     /* current working directory */
+       char *physdir; // = nullstr;    /* physical working directory */
+
+       char *arg0; /* value of $0 */
+
+       struct jmploc *exception_handler;
+
+// disabled by vda: cannot understand how it was supposed to work -
+// cannot fix bugs. That's why you have to explain your non-trivial designs!
+//     /* do we generate EXSIG events */
+//     int exsig; /* counter */
+       volatile int suppressint; /* counter */
+// TODO: rename
+// pendingsig -> pending_sig
+// intpending -> pending_int
+       volatile /*sig_atomic_t*/ smallint intpending; /* 1 = got SIGINT */
+       /* last pending signal */
+       volatile /*sig_atomic_t*/ smallint pendingsig;
+       smallint exception_type; /* kind of exception (0..5) */
+       /* 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) */
+
+       smallint isloginsh;
+       char nullstr[1];        /* zero length string */
+
+       char optlist[NOPTS];
+#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
+
+       /* trap handler commands */
+       /*
+        * 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.
+        */
+       char sigmode[NSIG - 1];
+#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 */
+
+       /* indicates specified signal received */
+       uint8_t gotsig[NSIG - 1]; /* offset by 1: "signal" 0 is meaningless */
+       char *trap[NSIG];
+
+       /* Rarely referenced stuff */
+#if ENABLE_ASH_RANDOM_SUPPORT
+       /* Random number generators */
+       int32_t random_galois_LFSR; /* Galois LFSR (fast but weak). signed! */
+       uint32_t random_LCG;        /* LCG (fast but weak) */
+#endif
+       pid_t backgndpid;        /* pid of last background process */
+       smallint job_warning;    /* user was warned about stopped jobs (can be 2, 1 or 0). */
+};
+extern struct globals_misc *const ash_ptr_to_globals_misc;
+#define G_misc (*ash_ptr_to_globals_misc)
+#define rootpid     (G_misc.rootpid    )
+#define shlvl       (G_misc.shlvl      )
+#define minusc      (G_misc.minusc     )
+#define curdir      (G_misc.curdir     )
+#define physdir     (G_misc.physdir    )
+#define arg0        (G_misc.arg0       )
+#define exception_handler (G_misc.exception_handler)
+#define exception_type    (G_misc.exception_type   )
+#define suppressint       (G_misc.suppressint      )
+#define intpending        (G_misc.intpending       )
+//#define exsig             (G_misc.exsig            )
+#define pendingsig        (G_misc.pendingsig       )
+#define isloginsh   (G_misc.isloginsh  )
+#define nullstr     (G_misc.nullstr    )
+#define optlist     (G_misc.optlist    )
+#define sigmode     (G_misc.sigmode    )
+#define gotsig      (G_misc.gotsig     )
+#define trap        (G_misc.trap       )
+#define random_galois_LFSR (G_misc.random_galois_LFSR)
+#define random_LCG         (G_misc.random_LCG        )
+#define backgndpid  (G_misc.backgndpid )
+#define job_warning (G_misc.job_warning)
+#define INIT_G_misc() do { \
+       (*(struct globals_misc**)&ash_ptr_to_globals_misc) = xzalloc(sizeof(G_misc)); \
+       barrier(); \
+       curdir = nullstr; \
+       physdir = nullstr; \
+} while (0)
+
+
+/* ============ DEBUG */
+#if DEBUG
+static void trace_printf(const char *fmt, ...);
+static void trace_vprintf(const char *fmt, va_list va);
+# define TRACE(param)    trace_printf param
+# define TRACEV(param)   trace_vprintf param
+# define close(fd) do { \
+       int dfd = (fd); \
+       if (close(dfd) < 0) \
+               bb_error_msg("bug on %d: closing %d(%x)", \
+                       __LINE__, dfd, dfd); \
+} while (0)
+#else
+# define TRACE(param)
+# define TRACEV(param)
+#endif
+
+
+/* ============ Utility functions */
+#define xbarrier() do { __asm__ __volatile__ ("": : :"memory"); } while (0)
+
+/* C99 say: "char" declaration may be signed or unsigned by default */
+#define signed_char2int(sc) ((int)(signed char)(sc))
+
+static int isdigit_str9(const char *str)
+{
+       int maxlen = 9 + 1; /* max 9 digits: 999999999 */
+       while (--maxlen && isdigit(*str))
+               str++;
+       return (*str == '\0');
+}
+
+
+/* ============ Interrupts / exceptions */
+/*
+ * These macros allow the user to suspend the handling of interrupt signals
+ * over a period of time.  This is similar to SIGHOLD or to sigblock, but
+ * much more efficient and portable.  (But hacking the kernel is so much
+ * more fun than worrying about efficiency and portability. :-))
+ */
+#define INT_OFF do { \
+       suppressint++; \
+       xbarrier(); \
+} while (0)
+
+/*
+ * 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_type".
+ */
+static void raise_exception(int) NORETURN;
+static void
+raise_exception(int e)
+{
+#if DEBUG
+       if (exception_handler == NULL)
+               abort();
+#endif
+       INT_OFF;
+       exception_type = e;
+       longjmp(exception_handler->loc, 1);
+}
+#if DEBUG
+#define raise_exception(e) do { \
+       TRACE(("raising exception %d on line %d\n", (e), __LINE__)); \
+       raise_exception(e); \
+} while (0)
+#endif
+
+/*
+ * 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 INT_OFF macro.  (The test for iflag is just
+ * defensive programming.)
+ */
+static void raise_interrupt(void) NORETURN;
+static void
+raise_interrupt(void)
+{
+       int ex_type;
+
+       intpending = 0;
+       /* Signal is not automatically unmasked after it is raised,
+        * do it ourself - unmask all signals */
+       sigprocmask_allsigs(SIG_UNBLOCK);
+       /* pendingsig = 0; - now done in onsig() */
+
+       ex_type = EXSIG;
+       if (gotsig[SIGINT - 1] && !trap[SIGINT]) {
+               if (!(rootshell && iflag)) {
+                       /* Kill ourself with SIGINT */
+                       signal(SIGINT, SIG_DFL);
+                       raise(SIGINT);
+               }
+               ex_type = EXINT;
+       }
+       raise_exception(ex_type);
+       /* NOTREACHED */
+}
+#if DEBUG
+#define raise_interrupt() do { \
+       TRACE(("raising interrupt on line %d\n", __LINE__)); \
+       raise_interrupt(); \
+} while (0)
+#endif
+
+static USE_ASH_OPTIMIZE_FOR_SIZE(inline) void
+int_on(void)
+{
+       xbarrier();
+       if (--suppressint == 0 && intpending) {
+               raise_interrupt();
+       }
+}
+#define INT_ON int_on()
+static USE_ASH_OPTIMIZE_FOR_SIZE(inline) void
+force_int_on(void)
+{
+       xbarrier();
+       suppressint = 0;
+       if (intpending)
+               raise_interrupt();
+}
+#define FORCE_INT_ON force_int_on()
+
+#define SAVE_INT(v) ((v) = suppressint)
+
+#define RESTORE_INT(v) do { \
+       xbarrier(); \
+       suppressint = (v); \
+       if (suppressint == 0 && intpending) \
+               raise_interrupt(); \
+} while (0)
+
+
+/* ============ Stdout/stderr output */
+
+static void
+outstr(const char *p, FILE *file)
+{
+       INT_OFF;
+       fputs(p, file);
+       INT_ON;
+}
+
+static void
+flush_stdout_stderr(void)
+{
+       INT_OFF;
+       fflush(stdout);
+       fflush(stderr);
+       INT_ON;
+}
+
+static void
+flush_stderr(void)
+{
+       INT_OFF;
+       fflush(stderr);
+       INT_ON;
+}
+
+static void
+outcslow(int c, FILE *dest)
+{
+       INT_OFF;
+       putc(c, dest);
+       fflush(dest);
+       INT_ON;
+}
+
+static int out1fmt(const char *, ...) __attribute__((__format__(__printf__,1,2)));
+static int
+out1fmt(const char *fmt, ...)
+{
+       va_list ap;
+       int r;
+
+       INT_OFF;
+       va_start(ap, fmt);
+       r = vprintf(fmt, ap);
+       va_end(ap);
+       INT_ON;
+       return r;
+}
+
+static int fmtstr(char *, size_t, const char *, ...) __attribute__((__format__(__printf__,3,4)));
+static int
+fmtstr(char *outbuf, size_t length, const char *fmt, ...)
+{
+       va_list ap;
+       int ret;
+
+       va_start(ap, fmt);
+       INT_OFF;
+       ret = vsnprintf(outbuf, length, fmt, ap);
+       va_end(ap);
+       INT_ON;
+       return ret;
+}
+
+static void
+out1str(const char *p)
+{
+       outstr(p, stdout);
+}
+
+static void
+out2str(const char *p)
+{
+       outstr(p, stderr);
+       flush_stderr();
+}
+
+
+/* ============ Parser structures */
+
+/* control characters in argument strings */
+#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'
+
+/* 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} */
+#if ENABLE_ASH_BASH_COMPAT
+#define VSSUBSTR        0xc     /* ${var:position:length} */
+#define VSREPLACE       0xd     /* ${var/pattern/replacement} */
+#define VSREPLACEALL    0xe     /* ${var//pattern/replacement} */
+#endif
+
+static const char dolatstr[] ALIGN1 = {
+       CTLVAR, VSNORMAL|VSQUOTE, '@', '=', '\0'
+};
+
+#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
+#if ENABLE_ASH_BASH_COMPAT
+#define NTO2     17
+#endif
+#define NCLOBBER 18
+#define NFROM    19
+#define NFROMTO  20
+#define NAPPEND  21
+#define NTOFD    22
+#define NFROMFD  23
+#define NHERE    24
+#define NXHERE   25
+#define NNOT     26
+#define N_NUMBER 27
+
+union node;
+
+struct ncmd {
+       smallint type; /* Nxxxx */
+       union node *assign;
+       union node *args;
+       union node *redirect;
+};
+
+struct npipe {
+       smallint type;
+       smallint pipe_backgnd;
+       struct nodelist *cmdlist;
+};
+
+struct nredir {
+       smallint type;
+       union node *n;
+       union node *redirect;
+};
+
+struct nbinary {
+       smallint type;
+       union node *ch1;
+       union node *ch2;
+};
+
+struct nif {
+       smallint type;
+       union node *test;
+       union node *ifpart;
+       union node *elsepart;
+};
+
+struct nfor {
+       smallint type;
+       union node *args;
+       union node *body;
+       char *var;
+};
+
+struct ncase {
+       smallint type;
+       union node *expr;
+       union node *cases;
+};
+
+struct nclist {
+       smallint type;
+       union node *next;
+       union node *pattern;
+       union node *body;
+};
+
+struct narg {
+       smallint type;
+       union node *next;
+       char *text;
+       struct nodelist *backquote;
+};
+
+/* nfile and ndup layout must match!
+ * NTOFD (>&fdnum) uses ndup structure, but we may discover mid-flight
+ * that it is actually NTO2 (>&file), and change its type.
+ */
+struct nfile {
+       smallint type;
+       union node *next;
+       int fd;
+       int _unused_dupfd;
+       union node *fname;
+       char *expfname;
+};
+
+struct ndup {
+       smallint type;
+       union node *next;
+       int fd;
+       int dupfd;
+       union node *vname;
+       char *_unused_expfname;
+};
+
+struct nhere {
+       smallint type;
+       union node *next;
+       int fd;
+       union node *doc;
+};
+
+struct nnot {
+       smallint type;
+       union node *com;
+};
+
+union node {
+       smallint 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;
+};
+
+/*
+ * Free a parse tree.
+ */
+static void
+freefunc(struct funcnode *f)
+{
+       if (f && --f->count < 0)
+               free(f);
+}
+
+
+/* ============ Debugging output */
+
+#if DEBUG
+
+static FILE *tracefile;
+
+static void
+trace_printf(const char *fmt, ...)
+{
+       va_list va;
+
+       if (debug != 1)
+               return;
+       if (DEBUG_TIME)
+               fprintf(tracefile, "%u ", (int) time(NULL));
+       if (DEBUG_PID)
+               fprintf(tracefile, "[%u] ", (int) getpid());
+       if (DEBUG_SIG)
+               fprintf(tracefile, "pending s:%d i:%d(supp:%d) ", pendingsig, intpending, suppressint);
+       va_start(va, fmt);
+       vfprintf(tracefile, fmt, va);
+       va_end(va);
+}
+
+static void
+trace_vprintf(const char *fmt, va_list va)
+{
+       if (debug != 1)
+               return;
+       if (DEBUG_TIME)
+               fprintf(tracefile, "%u ", (int) time(NULL));
+       if (DEBUG_PID)
+               fprintf(tracefile, "[%u] ", (int) getpid());
+       if (DEBUG_SIG)
+               fprintf(tracefile, "pending s:%d i:%d(supp:%d) ", pendingsig, intpending, suppressint);
+       vfprintf(tracefile, fmt, va);
+}
+
+static void
+trace_puts(const char *s)
+{
+       if (debug != 1)
+               return;
+       fputs(s, tracefile);
+}
+
+static void
+trace_puts_quoted(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);
+}
+
+static void
+trace_puts_args(char **ap)
+{
+       if (debug != 1)
+               return;
+       if (!*ap)
+               return;
+       while (1) {
+               trace_puts_quoted(*ap);
+               if (!*++ap) {
+                       putc('\n', tracefile);
+                       break;
+               }
+               putc(' ', tracefile);
+       }
+}
+
+static 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;
+       }
+       strcpy(s, "./trace");
+       if (tracefile) {
+               if (!freopen(s, "a", tracefile)) {
+                       fprintf(stderr, "Can't re-open %s\n", s);
+                       debug = 0;
+                       return;
+               }
+       } else {
+               tracefile = fopen(s, "a");
+               if (tracefile == NULL) {
+                       fprintf(stderr, "Can't open %s\n", s);
+                       debug = 0;
+                       return;
+               }
+       }
+#ifdef O_APPEND
+       flags = fcntl(fileno(tracefile), F_GETFL);
+       if (flags >= 0)
+               fcntl(fileno(tracefile), F_SETFL, flags | O_APPEND);
+#endif
+       setlinebuf(tracefile);
+       fputs("\nTracing started.\n", tracefile);
+}
+
+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);
+       }
+}
+
+/* little circular references here... */
+static void shtree(union node *n, int ind, char *pfx, FILE *fp);
+
+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
+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)
+                       putc(' ', fp);
+               sharg(np, fp);
+               first = 0;
+       }
+       for (np = cmd->ncmd.redirect; np; np = np->nfile.next) {
+               if (!first)
+                       putc(' ', fp);
+               dftfd = 0;
+               switch (np->nfile.type) {
+               case NTO:      s = ">>"+1; dftfd = 1; break;
+               case NCLOBBER: s = ">|"; dftfd = 1; break;
+               case NAPPEND:  s = ">>"; dftfd = 1; break;
+#if ENABLE_ASH_BASH_COMPAT
+               case NTO2:
+#endif
+               case NTOFD:    s = ">&"; dftfd = 1; break;
+               case NFROM:    s = "<"; break;
+               case NFROMFD:  s = "<&"; break;
+               case NFROMTO:  s = "<>"; break;
+               default:       s = "*error*"; 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
+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.pipe_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
+showtree(union node *n)
+{
+       trace_puts("showtree called\n");
+       shtree(n, 1, NULL, stdout);
+}
+
+#endif /* DEBUG */
+
+
+/* ============ Parser data */
+
+/*
+ * ash_vmsg() needs parsefile->fd, hence parsefile definition is moved up.
+ */
+struct strlist {
+       struct strlist *next;
+       char *text;
+};
+
+struct alias;
+
+struct strpush {
+       struct strpush *prev;   /* preceding string on stack */
+       char *prev_string;
+       int prev_left_in_line;
+#if ENABLE_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 left_in_line;       /* number of chars left in this line */
+       int left_in_buffer;     /* number of chars left in this buffer past the line */
+       char *next_to_pgetc;    /* 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 */
+static struct parsefile *g_parsefile = &basepf;  /* current input file */
+static int startlinno;                 /* line # where last token started */
+static char *commandname;              /* currently executing command */
+static struct strlist *cmdenviron;     /* environment for builtin command */
+static uint8_t exitstatus;             /* exit status of last command */
+
+
+/* ============ Message printing */
+
+static void
+ash_vmsg(const char *msg, va_list ap)
+{
+       fprintf(stderr, "%s: ", arg0);
+       if (commandname) {
+               if (strcmp(arg0, commandname))
+                       fprintf(stderr, "%s: ", commandname);
+               if (!iflag || g_parsefile->fd)
+                       fprintf(stderr, "line %d: ", startlinno);
+       }
+       vfprintf(stderr, msg, ap);
+       outcslow('\n', stderr);
+}
+
+/*
+ * 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 ash_vmsg_and_raise(int, const char *, va_list) NORETURN;
+static void
+ash_vmsg_and_raise(int cond, const char *msg, va_list ap)
+{
+#if DEBUG
+       if (msg) {
+               TRACE(("ash_vmsg_and_raise(%d, \"", cond));
+               TRACEV((msg, ap));
+               TRACE(("\") pid=%d\n", getpid()));
+       } else
+               TRACE(("ash_vmsg_and_raise(%d, NULL) pid=%d\n", cond, getpid()));
+       if (msg)
+#endif
+               ash_vmsg(msg, ap);
+
+       flush_stdout_stderr();
+       raise_exception(cond);
+       /* NOTREACHED */
+}
+
+static void ash_msg_and_raise_error(const char *, ...) NORETURN;
+static void
+ash_msg_and_raise_error(const char *msg, ...)
+{
+       va_list ap;
+
+       va_start(ap, msg);
+       ash_vmsg_and_raise(EXERROR, msg, ap);
+       /* NOTREACHED */
+       va_end(ap);
+}
+
+static void raise_error_syntax(const char *) NORETURN;
+static void
+raise_error_syntax(const char *msg)
+{
+       ash_msg_and_raise_error("syntax error: %s", msg);
+       /* NOTREACHED */
+}
+
+static void ash_msg_and_raise(int, const char *, ...) NORETURN;
+static void
+ash_msg_and_raise(int cond, const char *msg, ...)
+{
+       va_list ap;
+
+       va_start(ap, msg);
+       ash_vmsg_and_raise(cond, msg, ap);
+       /* NOTREACHED */
+       va_end(ap);
+}
+
+/*
+ * error/warning routines for external builtins
+ */
+static void
+ash_msg(const char *fmt, ...)
+{
+       va_list ap;
+
+       va_start(ap, fmt);
+       ash_vmsg(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);
+}
+
+
+/* ============ Memory allocation */
+
+/*
+ * 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)
+enum {
+       /* Most machines require the value returned from malloc to be aligned
+        * in some way.  The following macro will get this right
+        * on many machines.  */
+       SHELL_SIZE = sizeof(union {int i; char *cp; double d; }) - 1,
+       /* Minimum size of a block */
+       MINSIZE = SHELL_ALIGN(504),
+};
+
+struct stack_block {
+       struct stack_block *prev;
+       char space[MINSIZE];
+};
+
+struct stackmark {
+       struct stack_block *stackp;
+       char *stacknxt;
+       size_t stacknleft;
+       struct stackmark *marknext;
+};
+
+
+struct globals_memstack {
+       struct stack_block *g_stackp; // = &stackbase;
+       struct stackmark *markp;
+       char *g_stacknxt; // = stackbase.space;
+       char *sstrend; // = stackbase.space + MINSIZE;
+       size_t g_stacknleft; // = MINSIZE;
+       int    herefd; // = -1;
+       struct stack_block stackbase;
+};
+extern struct globals_memstack *const ash_ptr_to_globals_memstack;
+#define G_memstack (*ash_ptr_to_globals_memstack)
+#define g_stackp     (G_memstack.g_stackp    )
+#define markp        (G_memstack.markp       )
+#define g_stacknxt   (G_memstack.g_stacknxt  )
+#define sstrend      (G_memstack.sstrend     )
+#define g_stacknleft (G_memstack.g_stacknleft)
+#define herefd       (G_memstack.herefd      )
+#define stackbase    (G_memstack.stackbase   )
+#define INIT_G_memstack() do { \
+       (*(struct globals_memstack**)&ash_ptr_to_globals_memstack) = xzalloc(sizeof(G_memstack)); \
+       barrier(); \
+       g_stackp = &stackbase; \
+       g_stacknxt = stackbase.space; \
+       g_stacknleft = MINSIZE; \
+       sstrend = stackbase.space + MINSIZE; \
+       herefd = -1; \
+} while (0)
+
+#define stackblock()     ((void *)g_stacknxt)
+#define stackblocksize() g_stacknleft
+
+
+static void *
+ckrealloc(void * p, size_t nbytes)
+{
+       p = realloc(p, nbytes);
+       if (!p)
+               ash_msg_and_raise_error(bb_msg_memory_exhausted);
+       return p;
+}
+
+static void *
+ckmalloc(size_t nbytes)
+{
+       return ckrealloc(NULL, nbytes);
+}
+
+static void *
+ckzalloc(size_t nbytes)
+{
+       return memset(ckmalloc(nbytes), 0, nbytes);
+}
+
+/*
+ * Make a copy of a string in safe storage.
+ */
+static char *
+ckstrdup(const char *s)
+{
+       char *p = strdup(s);
+       if (!p)
+               ash_msg_and_raise_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 void *
+stalloc(size_t nbytes)
+{
+       char *p;
+       size_t aligned;
+
+       aligned = SHELL_ALIGN(nbytes);
+       if (aligned > g_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)
+                       ash_msg_and_raise_error(bb_msg_memory_exhausted);
+               INT_OFF;
+               sp = ckmalloc(len);
+               sp->prev = g_stackp;
+               g_stacknxt = sp->space;
+               g_stacknleft = blocksize;
+               sstrend = g_stacknxt + blocksize;
+               g_stackp = sp;
+               INT_ON;
+       }
+       p = g_stacknxt;
+       g_stacknxt += aligned;
+       g_stacknleft -= aligned;
+       return p;
+}
+
+static void *
+stzalloc(size_t nbytes)
+{
+       return memset(stalloc(nbytes), 0, nbytes);
+}
+
+static void
+stunalloc(void *p)
+{
+#if DEBUG
+       if (!p || (g_stacknxt < (char *)p) || ((char *)p < g_stackp->space)) {
+               write(STDERR_FILENO, "stunalloc\n", 10);
+               abort();
+       }
+#endif
+       g_stacknleft += g_stacknxt - (char *)p;
+       g_stacknxt = p;
+}
+
+/*
+ * Like strdup but works with the ash stack.
+ */
+static char *
+ststrdup(const char *p)
+{
+       size_t len = strlen(p) + 1;
+       return memcpy(stalloc(len), p, len);
+}
+
+static void
+setstackmark(struct stackmark *mark)
+{
+       mark->stackp = g_stackp;
+       mark->stacknxt = g_stacknxt;
+       mark->stacknleft = g_stacknleft;
+       mark->marknext = markp;
+       markp = mark;
+}
+
+static void
+popstackmark(struct stackmark *mark)
+{
+       struct stack_block *sp;
+
+       if (!mark->stackp)
+               return;
+
+       INT_OFF;
+       markp = mark->marknext;
+       while (g_stackp != mark->stackp) {
+               sp = g_stackp;
+               g_stackp = sp->prev;
+               free(sp);
+       }
+       g_stacknxt = mark->stacknxt;
+       g_stacknleft = mark->stacknleft;
+       sstrend = mark->stacknxt + mark->stacknleft;
+       INT_ON;
+}
+
+/*
+ * 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.
+ */
+static void
+growstackblock(void)
+{
+       size_t newlen;
+
+       newlen = g_stacknleft * 2;
+       if (newlen < g_stacknleft)
+               ash_msg_and_raise_error(bb_msg_memory_exhausted);
+       if (newlen < 128)
+               newlen += 128;
+
+       if (g_stacknxt == g_stackp->space && g_stackp != &stackbase) {
+               struct stack_block *oldstackp;
+               struct stackmark *xmark;
+               struct stack_block *sp;
+               struct stack_block *prevstackp;
+               size_t grosslen;
+
+               INT_OFF;
+               oldstackp = g_stackp;
+               sp = g_stackp;
+               prevstackp = sp->prev;
+               grosslen = newlen + sizeof(struct stack_block) - MINSIZE;
+               sp = ckrealloc(sp, grosslen);
+               sp->prev = prevstackp;
+               g_stackp = sp;
+               g_stacknxt = sp->space;
+               g_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 = g_stackp;
+                       xmark->stacknxt = g_stacknxt;
+                       xmark->stacknleft = g_stacknleft;
+                       xmark = xmark->marknext;
+               }
+               INT_ON;
+       } else {
+               char *oldspace = g_stacknxt;
+               size_t oldlen = g_stacknleft;
+               char *p = stalloc(newlen);
+
+               /* free the space we just allocated */
+               g_stacknxt = memcpy(p, oldspace, oldlen);
+               g_stacknleft += newlen;
+       }
+}
+
+static void
+grabstackblock(size_t len)
+{
+       len = SHELL_ALIGN(len);
+       g_stacknxt += len;
+       g_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.
+ */
+static void *
+growstackstr(void)
+{
+       size_t len = stackblocksize();
+       if (herefd >= 0 && len >= 1024) {
+               full_write(herefd, stackblock(), len);
+               return stackblock();
+       }
+       growstackblock();
+       return (char *)stackblock() + len;
+}
+
+/*
+ * Called from CHECKSTRSPACE.
+ */
+static char *
+makestrspace(size_t newlen, char *p)
+{
+       size_t len = p - g_stacknxt;
+       size_t size = stackblocksize();
+
+       for (;;) {
+               size_t nleft;
+
+               size = stackblocksize();
+               nleft = size - len;
+               if (nleft >= newlen)
+                       break;
+               growstackblock();
+       }
+       return (char *)stackblock() + len;
+}
+
+static char *
+stack_nputstr(const char *s, size_t n, char *p)
+{
+       p = makestrspace(n, p);
+       p = (char *)memcpy(p, s, n) + n;
+       return p;
+}
+
+static char *
+stack_putstr(const char *s, char *p)
+{
+       return stack_nputstr(s, strlen(s), p);
+}
+
+static char *
+_STPUTC(int c, char *p)
+{
+       if (p == sstrend)
+               p = growstackstr();
+       *p++ = c;
+       return p;
+}
+
+#define STARTSTACKSTR(p)        ((p) = stackblock())
+#define STPUTC(c, p)            ((p) = _STPUTC((c), (p)))
+#define CHECKSTRSPACE(n, p) do { \
+       char *q = (p); \
+       size_t l = (n); \
+       size_t m = sstrend - q; \
+       if (l > m) \
+               (p) = makestrspace(l, q); \
+} while (0)
+#define USTPUTC(c, p)           (*(p)++ = (c))
+#define STACKSTRNUL(p) do { \
+       if ((p) == sstrend) \
+               (p) = growstackstr(); \
+       *(p) = '\0'; \
+} while (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)
+
+
+/* ============ String helpers */
+
+/*
+ * prefix -- see if pfx is a prefix of string.
+ */
+static char *
+prefix(const char *string, const char *pfx)
+{
+       while (*pfx) {
+               if (*pfx++ != *string++)
+                       return NULL;
+       }
+       return (char *) string;
+}
+
+/*
+ * Check for a valid number.  This should be elsewhere.
+ */
+static int
+is_number(const char *p)
+{
+       do {
+               if (!isdigit(*p))
+                       return 0;
+       } while (*++p != '\0');
+       return 1;
+}
+
+/*
+ * Convert a string of digits to an integer, printing an error message on
+ * failure.
+ */
+static int
+number(const char *s)
+{
+       if (!is_number(s))
+               ash_msg_and_raise_error(illnum, s);
+       return atoi(s);
+}
+
+/*
+ * Produce a possibly single quoted string suitable as input to the shell.
+ * The return string is allocated on the stack.
+ */
+static 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 = (char *)memcpy(q, s, len) + len;
+               *q++ = '\'';
+               s += len;
+
+               STADJUST(q - p, p);
+
+               len = strspn(s, "'");
+               if (!len)
+                       break;
+
+               q = p = makestrspace(len + 3, p);
+
+               *q++ = '"';
+               q = (char *)memcpy(q, s, len) + len;
+               *q++ = '"';
+               s += len;
+
+               STADJUST(q - p, p);
+       } while (*s);
+
+       USTPUTC(0, p);
+
+       return stackblock();
+}
+
+
+/* ============ nextopt */
+
+static char **argptr;                  /* argument list for builtin commands */
+static char *optionarg;                /* set by nextopt (like getopt) */
+static char *optptr;                   /* used by nextopt */
+
+/*
+ * 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 returns the character,
+ * or '\0' on end of input.
+ */
+static int
+nextopt(const char *optstring)
+{
+       char *p;
+       const char *q;
+       char c;
+
+       p = optptr;
+       if (p == NULL || *p == '\0') {
+               /* We ate entire "-param", take next one */
+               p = *argptr;
+               if (p == NULL)
+                       return '\0';
+               if (*p != '-')
+                       return '\0';
+               if (*++p == '\0') /* just "-" ? */
+                       return '\0';
+               argptr++;
+               if (LONE_DASH(p)) /* "--" ? */
+                       return '\0';
+               /* p => next "-param" */
+       }
+       /* p => some option char in the middle of a "-param" */
+       c = *p++;
+       for (q = optstring; *q != c;) {
+               if (*q == '\0')
+                       ash_msg_and_raise_error("illegal option -%c", c);
+               if (*++q == ':')
+                       q++;
+       }
+       if (*++q == ':') {
+               if (*p == '\0') {
+                       p = *argptr++;
+                       if (p == NULL)
+                               ash_msg_and_raise_error("no arg for -%c option", c);
+               }
+               optionarg = p;
+               p = NULL;
+       }
+       optptr = p;
+       return c;
+}
+
+
+/* ============ Shell variables */
+
+/*
+ * The parsefile structure pointed to by the global variable parsefile
+ * contains information about the current file being read.
+ */
+struct shparam {
+       int nparam;             /* # of positional parameters (without $0) */
+#if ENABLE_ASH_GETOPTS
+       int optind;             /* next parameter to be processed by getopts */
+       int optoff;             /* used by getopts */
+#endif
+       unsigned char malloced; /* if parameter list dynamically allocated */
+       char **p;               /* parameter list */
+};
+
+/*
+ * Free the list of positional parameters.
+ */
+static void
+freeparam(volatile struct shparam *param)
+{
+       if (param->malloced) {
+               char **ap, **ap1;
+               ap = ap1 = param->p;
+               while (*ap)
+                       free(*ap++);
+               free(ap1);
+       }
+}
+
+#if ENABLE_ASH_GETOPTS
+static void getoptsreset(const char *value);
+#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 */
+};
+
+/* 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 */
+#if ENABLE_ASH_RANDOM_SUPPORT
+# define VDYNAMIC       0x200   /* dynamic variable */
+#else
+# define VDYNAMIC       0
+#endif
+
+#ifdef IFS_BROKEN
+static const char defifsvar[] ALIGN1 = "IFS= \t\n";
+#define defifs (defifsvar + 4)
+#else
+static const char defifs[] ALIGN1 = " \t\n";
+#endif
+
+
+/* Need to be before varinit_data[] */
+#if ENABLE_LOCALE_SUPPORT
+static void
+change_lc_all(const char *value)
+{
+       if (value && *value != '\0')
+               setlocale(LC_ALL, value);
+}
+static void
+change_lc_ctype(const char *value)
+{
+       if (value && *value != '\0')
+               setlocale(LC_CTYPE, value);
+}
+#endif
+#if ENABLE_ASH_MAIL
+static void chkmail(void);
+static void changemail(const char *);
+#endif
+static void changepath(const char *);
+#if ENABLE_ASH_RANDOM_SUPPORT
+static void change_random(const char *);
+#endif
+
+static const struct {
+       int flags;
+       const char *text;
+       void (*func)(const char *);
+} varinit_data[] = {
+#ifdef IFS_BROKEN
+       { VSTRFIXED|VTEXTFIXED       , defifsvar   , NULL            },
+#else
+       { VSTRFIXED|VTEXTFIXED|VUNSET, "IFS\0"     , NULL            },
+#endif
+#if ENABLE_ASH_MAIL
+       { VSTRFIXED|VTEXTFIXED|VUNSET, "MAIL\0"    , changemail      },
+       { VSTRFIXED|VTEXTFIXED|VUNSET, "MAILPATH\0", changemail      },
+#endif
+       { VSTRFIXED|VTEXTFIXED       , bb_PATH_root_path, changepath },
+       { VSTRFIXED|VTEXTFIXED       , "PS1=$ "    , NULL            },
+       { VSTRFIXED|VTEXTFIXED       , "PS2=> "    , NULL            },
+       { VSTRFIXED|VTEXTFIXED       , "PS4=+ "    , NULL            },
+#if ENABLE_ASH_GETOPTS
+       { VSTRFIXED|VTEXTFIXED       , "OPTIND=1"  , getoptsreset    },
+#endif
+#if ENABLE_ASH_RANDOM_SUPPORT
+       { VSTRFIXED|VTEXTFIXED|VUNSET|VDYNAMIC, "RANDOM\0", change_random },
+#endif
+#if ENABLE_LOCALE_SUPPORT
+       { VSTRFIXED|VTEXTFIXED|VUNSET, "LC_ALL\0"  , change_lc_all   },
+       { VSTRFIXED|VTEXTFIXED|VUNSET, "LC_CTYPE\0", change_lc_ctype },
+#endif
+#if ENABLE_FEATURE_EDITING_SAVEHISTORY
+       { VSTRFIXED|VTEXTFIXED|VUNSET, "HISTFILE\0", NULL            },
+#endif
+};
+
+struct redirtab;
+
+struct globals_var {
+       struct shparam shellparam;      /* $@ current positional parameters */
+       struct redirtab *redirlist;
+       int g_nullredirs;
+       int preverrout_fd;   /* save fd2 before print debug if xflag is set. */
+       struct var *vartab[VTABSIZE];
+       struct var varinit[ARRAY_SIZE(varinit_data)];
+};
+extern struct globals_var *const ash_ptr_to_globals_var;
+#define G_var (*ash_ptr_to_globals_var)
+#define shellparam    (G_var.shellparam   )
+//#define redirlist     (G_var.redirlist    )
+#define g_nullredirs  (G_var.g_nullredirs )
+#define preverrout_fd (G_var.preverrout_fd)
+#define vartab        (G_var.vartab       )
+#define varinit       (G_var.varinit      )
+#define INIT_G_var() do { \
+       unsigned i; \
+       (*(struct globals_var**)&ash_ptr_to_globals_var) = xzalloc(sizeof(G_var)); \
+       barrier(); \
+       for (i = 0; i < ARRAY_SIZE(varinit_data); i++) { \
+               varinit[i].flags = varinit_data[i].flags; \
+               varinit[i].text  = varinit_data[i].text; \
+               varinit[i].func  = varinit_data[i].func; \
+       } \
+} while (0)
+
+#define vifs      varinit[0]
+#if ENABLE_ASH_MAIL
+# define vmail    (&vifs)[1]
+# define vmpath   (&vmail)[1]
+# define vpath    (&vmpath)[1]
+#else
+# define vpath    (&vifs)[1]
+#endif
+#define vps1      (&vpath)[1]
+#define vps2      (&vps1)[1]
+#define vps4      (&vps2)[1]
+#if ENABLE_ASH_GETOPTS
+# define voptind  (&vps4)[1]
+# if ENABLE_ASH_RANDOM_SUPPORT
+#  define vrandom (&voptind)[1]
+# endif
+#else
+# if ENABLE_ASH_RANDOM_SUPPORT
+#  define vrandom (&vps4)[1]
+# endif
+#endif
+
+/*
+ * 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)
+#if ENABLE_ASH_MAIL
+# define mailval()      (vmail.text + 5)
+# define mpathval()     (vmpath.text + 9)
+# define mpathset()     ((vmpath.flags & VUNSET) == 0)
+#endif
+#define pathval()       (vpath.text + 5)
+#define ps1val()        (vps1.text + 4)
+#define ps2val()        (vps2.text + 4)
+#define ps4val()        (vps4.text + 4)
+#if ENABLE_ASH_GETOPTS
+# define optindval()    (voptind.text + 7)
+#endif
+
+
+#define is_name(c)      ((c) == '_' || isalpha((unsigned char)(c)))
+#define is_in_name(c)   ((c) == '_' || isalnum((unsigned char)(c)))
+
+#if ENABLE_ASH_GETOPTS
+static void
+getoptsreset(const char *value)
+{
+       shellparam.optind = number(value);
+       shellparam.optoff = -1;
+}
+#endif
+
+/*
+ * 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;
+}
+
+/*
+ * 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'.
+ */
+static 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
+varequal(const char *a, const char *b)
+{
+       return !varcmp(a, b);
+}
+
+/*
+ * Find the appropriate entry in the hash table from the name.
+ */
+static struct var **
+hashvar(const char *p)
+{
+       unsigned hashval;
+
+       hashval = ((unsigned char) *p) << 4;
+       while (*p && *p != '=')
+               hashval += (unsigned char) *p++;
+       return &vartab[hashval % VTABSIZE];
+}
+
+static int
+vpcmp(const void *a, const void *b)
+{
+       return varcmp(*(const char **)a, *(const char **)b);
+}
+
+/*
+ * This routine initializes the builtin variables.
+ */
+static void
+initvar(void)
+{
+       struct var *vp;
+       struct var *end;
+       struct var **vpp;
+
+       /*
+        * PS1 depends on uid
+        */
+#if ENABLE_FEATURE_EDITING && ENABLE_FEATURE_EDITING_FANCY_PROMPT
+       vps1.text = "PS1=\\w \\$ ";
+#else
+       if (!geteuid())
+               vps1.text = "PS1=# ";
+#endif
+       vp = varinit;
+       end = vp + ARRAY_SIZE(varinit);
+       do {
+               vpp = hashvar(vp->text);
+               vp->next = *vpp;
+               *vpp = vp;
+       } while (++vp < end);
+}
+
+static struct var **
+findvar(struct var **vpp, const char *name)
+{
+       for (; *vpp; vpp = &(*vpp)->next) {
+               if (varequal((*vpp)->text, name)) {
+                       break;
+               }
+       }
+       return vpp;
+}
+
+/*
+ * Find the value of a variable.  Returns NULL if not set.
+ */
+static const char *
+lookupvar(const char *name)
+{
+       struct var *v;
+
+       v = *findvar(hashvar(name), name);
+       if (v) {
+#if ENABLE_ASH_RANDOM_SUPPORT
+       /*
+        * 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 const 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);
+}
+
+/*
+ * 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.
+ */
+static 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;
+                       ash_msg_and_raise_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)
+                       free((char*)vp->text);
+
+               flags |= vp->flags & ~(VTEXTFIXED|VSTACK|VNOSAVE|VUNSET);
+       } else {
+               if (flags & VNOSET)
+                       return;
+               /* not found */
+               vp = ckzalloc(sizeof(*vp));
+               vp->next = *vpp;
+               /*vp->func = NULL; - ckzalloc did it */
+               *vpp = vp;
+       }
+       if (!(flags & (VTEXTFIXED|VSTACK|VNOSAVE)))
+               s = ckstrdup(s);
+       vp->text = s;
+       vp->flags = flags;
+}
+
+/*
+ * 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)
+               ash_msg_and_raise_error("%.*s: bad variable name", namelen, name);
+       vallen = 0;
+       if (val == NULL) {
+               flags |= VUNSET;
+       } else {
+               vallen = strlen(val);
+       }
+       INT_OFF;
+       nameeq = ckmalloc(namelen + vallen + 2);
+       p = (char *)memcpy(nameeq, name, namelen) + namelen;
+       if (val) {
+               *p++ = '=';
+               p = (char *)memcpy(p, val, vallen) + vallen;
+       }
+       *p = '\0';
+       setvareq(nameeq, flags | VNOSAVE);
+       INT_ON;
+}
+
+#if ENABLE_ASH_GETOPTS
+/*
+ * Safe version of setvar, returns 1 on success 0 on failure.
+ */
+static int
+setvarsafe(const char *name, const char *val, int flags)
+{
+       int err;
+       volatile int saveint;
+       struct jmploc *volatile savehandler = exception_handler;
+       struct jmploc jmploc;
+
+       SAVE_INT(saveint);
+       if (setjmp(jmploc.loc))
+               err = 1;
+       else {
+               exception_handler = &jmploc;
+               setvar(name, val, flags);
+               err = 0;
+       }
+       exception_handler = savehandler;
+       RESTORE_INT(saveint);
+       return err;
+}
+#endif
+
+/*
+ * Unset the specified variable.
+ */
+static 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;
+#if ENABLE_ASH_RANDOM_SUPPORT
+               vp->flags &= ~VDYNAMIC;
+#endif
+               if (flags & VUNSET)
+                       goto ok;
+               if ((flags & VSTRFIXED) == 0) {
+                       INT_OFF;
+                       if ((flags & (VTEXTFIXED|VSTACK)) == 0)
+                               free((char*)vp->text);
+                       *vpp = vp->next;
+                       free(vp);
+                       INT_ON;
+               } else {
+                       setvar(s, 0, 0);
+                       vp->flags &= ~VEXPORT;
+               }
+ ok:
+               retval = 0;
+       }
+ out:
+       return retval;
+}
+
+/*
+ * 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;
+       INT_OFF;
+       do {
+               setvareq(lp->text, flags);
+               lp = lp->next;
+       } while (lp);
+       INT_ON;
+}
+
+/*
+ * 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);
+}
+
+
+/* ============ Path search helper
+ *
+ * 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 const char *pathopt;     /* set by padvance */
+
+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++)
+               continue;
+       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);
+}
+
+
+/* ============ Prompt */
+
+static smallint doprompt;                   /* if set, prompt the user */
+static smallint needprompt;                 /* true if interactive and at start of line */
+
+#if ENABLE_FEATURE_EDITING
+static line_input_t *line_input_state;
+static const char *cmdedit_prompt;
+static void
+putprompt(const char *s)
+{
+       if (ENABLE_ASH_EXPAND_PRMT) {
+               free((char*)cmdedit_prompt);
+               cmdedit_prompt = ckstrdup(s);
+               return;
+       }
+       cmdedit_prompt = s;
+}
+#else
+static void
+putprompt(const char *s)
+{
+       out2str(s);
+}
+#endif
+
+#if ENABLE_ASH_EXPAND_PRMT
+/* expandstr() needs parsing machinery, so it is far away ahead... */
+static const char *expandstr(const char *ps);
+#else
+#define expandstr(s) s
+#endif
+
+static void
+setprompt(int whichprompt)
+{
+       const char *prompt;
+#if ENABLE_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;
+       }
+#if ENABLE_ASH_EXPAND_PRMT
+       setstackmark(&smark);
+       stalloc(stackblocksize());
+#endif
+       putprompt(expandstr(prompt));
+#if ENABLE_ASH_EXPAND_PRMT
+       popstackmark(&smark);
+#endif
+}
+
+
+/* ============ The cd and pwd commands */
+
+#define CD_PHYSICAL 1
+#define CD_PRINT 2
+
+static int docd(const char *, int);
+
+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;
+}
+
+/*
+ * 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 = ststrdup(dir);
+       STARTSTACKSTR(new);
+       if (*dir != '/') {
+               if (curdir == nullstr)
+                       return 0;
+               new = stack_putstr(curdir, new);
+       }
+       new = makestrspace(strlen(dir) + 2, new);
+       lim = (char *)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;
+                       }
+                       if (p[1] == '\0')
+                               break;
+                       /* fall through */
+               default:
+                       new = stack_putstr(p, new);
+                       USTPUTC('/', new);
+               }
+               p = strtok(0, "/");
+       }
+       if (new > lim)
+               STUNPUTC(new);
+       *new = 0;
+       return stackblock();
+}
+
+/*
+ * 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(NULL, 0); /* huh, using glibc extension? */
+       return dir ? dir : nullstr;
+}
+
+static void
+setpwd(const char *val, int setold)
+{
+       char *oldcur, *dir;
+
+       oldcur = dir = curdir;
+
+       if (setold) {
+               setvar("OLDPWD", oldcur, VEXPORT);
+       }
+       INT_OFF;
+       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 = ckstrdup(val);
+       if (oldcur != dir && oldcur != nullstr) {
+               free(oldcur);
+       }
+       curdir = dir;
+       INT_ON;
+       setvar("PWD", dir, VEXPORT);
+}
+
+static void hashcd(void);
+
+/*
+ * 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));
+
+       INT_OFF;
+       if (!(flags & CD_PHYSICAL)) {
+               dir = updatepwd(dest);
+               if (dir)
+                       dest = dir;
+       }
+       err = chdir(dest);
+       if (err)
+               goto out;
+       setpwd(dir, 1);
+       hashcd();
+ out:
+       INT_ON;
+       return err;
+}
+
+static int
+cdcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
+{
+       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 (LONE_DASH(dest)) {
+               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 = ".";
+       path = bltinlookup("CDPATH");
+       if (!path) {
+ 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);
+       ash_msg_and_raise_error("can't cd to %s", dest);
+       /* NOTREACHED */
+ out:
+       if (flags & CD_PRINT)
+               out1fmt(snlfmt, curdir);
+       return 0;
+}
+
+static int
+pwdcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
+{
+       int flags;
+       const char *dir = curdir;
+
+       flags = cdopt();
+       if (flags) {
+               if (physdir == nullstr)
+                       setpwd(dir, 0);
+               dir = physdir;
+       }
+       out1fmt(snlfmt, dir);
+       return 0;
+}
+
+
+/* ============ ... */
+
+
+#define IBUFSIZ COMMON_BUFSIZE
+/* buffer for top level input file */
+#define basebuf bb_common_bufsiz1
+
+/* 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 */
+
+#if ENABLE_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
+
+/* 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 */
+#define PSSYNTAX   4    /* prompt */
+
+#if ENABLE_ASH_OPTIMIZE_FOR_SIZE
+#define USE_SIT_FUNCTION
+#endif
+
+#if ENABLE_SH_MATH_SUPPORT
+static const char S_I_T[][4] = {
+#if ENABLE_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] = {
+#if ENABLE_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 /* SH_MATH_SUPPORT */
+
+#ifdef USE_SIT_FUNCTION
+
+static int
+SIT(int c, int syntax)
+{
+       static const char spec_symbls[] ALIGN1 = "\t\n !\"$&'()*-/:;<=>?[\\]`|}~";
+#if ENABLE_ASH_ALIAS
+       static const char syntax_index_table[] ALIGN1 = {
+               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[] ALIGN1 = {
+               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;
+       }
+#if ENABLE_ASH_ALIAS
+       if (c == PEOA) {         /* 2^8+1 */
+               indx = 0;
+       } else
+#endif
+       {
+               if ((unsigned char)c >= (unsigned char)(CTLESC)
+                && (unsigned char)c <= (unsigned char)(CTLQUOTEMARK)
+               ) {
+                       return CCTL;
+               }
+               s = strchrnul(spec_symbls, c);
+               if (*s == '\0') {
+                       return CWORD;
+               }
+               indx = syntax_index_table[s - spec_symbls];
+       }
+       return S_I_T[indx][syntax];
+}
+
+#else   /* !USE_SIT_FUNCTION */
+
+#if ENABLE_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,
+#if ENABLE_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,
+};
+
+#define SIT(c, syntax) (S_I_T[(int)syntax_index_table[(int)(c) + SYNBASE]][syntax])
+
+#endif  /* USE_SIT_FUNCTION */
+
+
+/* ============ Alias handling */
+
+#if ENABLE_ASH_ALIAS
+
+#define ALIASINUSE 1
+#define ALIASDEAD  2
+
+struct alias {
+       struct alias *next;
+       char *name;
+       char *val;
+       int flag;
+};
+
+
+static struct alias **atab; // [ATABSIZE];
+#define INIT_G_alias() do { \
+       atab = xzalloc(ATABSIZE * sizeof(atab[0])); \
+} while (0)
+
+
+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 (strcmp(name, (*app)->name) == 0) {
+                       break;
+               }
+       }
+
+       return app;
+}
+
+static struct alias *
+lookupalias(const char *name, int check)
+{
+       struct alias *ap = *__lookupalias(name);
+
+       if (check && ap && (ap->flag & ALIASINUSE))
+               return NULL;
+       return ap;
+}
+
+static struct alias *
+freealias(struct alias *ap)
+{
+       struct alias *next;
+
+       if (ap->flag & ALIASINUSE) {
+               ap->flag |= ALIASDEAD;
+               return ap;
+       }
+
+       next = ap->next;
+       free(ap->name);
+       free(ap->val);
+       free(ap);
+       return next;
+}
+
+static void
+setalias(const char *name, const char *val)
+{
+       struct alias *ap, **app;
+
+       app = __lookupalias(name);
+       ap = *app;
+       INT_OFF;
+       if (ap) {
+               if (!(ap->flag & ALIASINUSE)) {
+                       free(ap->val);
+               }
+               ap->val = ckstrdup(val);
+               ap->flag &= ~ALIASDEAD;
+       } else {
+               /* not found */
+               ap = ckzalloc(sizeof(struct alias));
+               ap->name = ckstrdup(name);
+               ap->val = ckstrdup(val);
+               /*ap->flag = 0; - ckzalloc did it */
+               /*ap->next = NULL;*/
+               *app = ap;
+       }
+       INT_ON;
+}
+
+static int
+unalias(const char *name)
+{
+       struct alias **app;
+
+       app = __lookupalias(name);
+
+       if (*app) {
+               INT_OFF;
+               *app = freealias(*app);
+               INT_ON;
+               return 0;
+       }
+
+       return 1;
+}
+
+static void
+rmaliases(void)
+{
+       struct alias *ap, **app;
+       int i;
+
+       INT_OFF;
+       for (i = 0; i < ATABSIZE; i++) {
+               app = &atab[i];
+               for (ap = *app; ap; ap = *app) {
+                       *app = freealias(*app);
+                       if (ap == *app) {
+                               app = &ap->next;
+                       }
+               }
+       }
+       INT_ON;
+}
+
+static void
+printalias(const struct alias *ap)
+{
+       out1fmt("%s=%s\n", ap->name, single_quote(ap->val));
+}
+
+/*
+ * TODO - sort output
+ */
+static int
+aliascmd(int argc UNUSED_PARAM, char **argv)
+{
+       char *n, *v;
+       int ret = 0;
+       struct alias *ap;
+
+       if (!argv[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) {
+               v = strchr(n+1, '=');
+               if (v == NULL) { /* n+1: funny ksh stuff */
+                       ap = *__lookupalias(n);
+                       if (ap == 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 UNUSED_PARAM, char **argv UNUSED_PARAM)
+{
+       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;
+}
+
+#endif /* ASH_ALIAS */
+
+
+/* ============ jobs.c */
+
+/* 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 struct job *makejob(/*union node *,*/ int);
+#if !JOBS
+#define forkshell(job, node, mode) forkshell(job, mode)
+#endif
+static int forkshell(struct job *, union node *, int);
+static int waitforjob(struct job *);
+
+#if !JOBS
+enum { doing_jobctl = 0 };
+#define setjobctl(on) do {} while (0)
+#else
+static smallint doing_jobctl; //references:8
+static void setjobctl(int);
+#endif
+
+/*
+ * Ignore a signal.
+ */
+static void
+ignoresig(int signo)
+{
+       /* Avoid unnecessary system calls. Is it already SIG_IGNed? */
+       if (sigmode[signo - 1] != S_IGN && sigmode[signo - 1] != S_HARD_IGN) {
+               /* No, need to do it */
+               signal(signo, SIG_IGN);
+       }
+       sigmode[signo - 1] = S_HARD_IGN;
+}
+
+/*
+ * Signal handler. Only one usage site - in setsignal()
+ */
+static void
+onsig(int signo)
+{
+       gotsig[signo - 1] = 1;
+
+       if (/* exsig || */ (signo == SIGINT && !trap[SIGINT])) {
+               if (!suppressint) {
+                       pendingsig = 0;
+                       raise_interrupt(); /* does not return */
+               }
+               intpending = 1;
+       } else {
+               pendingsig = signo;
+       }
+}
+
+/*
+ * Set the signal handler for the specified signal.  The routine figures
+ * out what it should be set to.
+ */
+static void
+setsignal(int signo)
+{
+       char *t;
+       char cur_act, new_act;
+       struct sigaction act;
+
+       t = trap[signo];
+       new_act = S_DFL;
+       if (t != NULL) { /* trap for this sig is set */
+               new_act = S_CATCH;
+               if (t[0] == '\0') /* trap is "": ignore this sig */
+                       new_act = S_IGN;
+       }
+
+       if (rootshell && new_act == S_DFL) {
+               switch (signo) {
+               case SIGINT:
+                       if (iflag || minusc || sflag == 0)
+                               new_act = S_CATCH;
+                       break;
+               case SIGQUIT:
+#if DEBUG
+                       if (debug)
+                               break;
+#endif
+                       /* man bash:
+                        * "In all cases, bash ignores SIGQUIT. Non-builtin
+                        * commands run by bash have signal handlers
+                        * set to the values inherited by the shell
+                        * from its parent". */
+                       new_act = S_IGN;
+                       break;
+               case SIGTERM:
+                       if (iflag)
+                               new_act = S_IGN;
+                       break;
+#if JOBS
+               case SIGTSTP:
+               case SIGTTOU:
+                       if (mflag)
+                               new_act = S_IGN;
+                       break;
+#endif
+               }
+       }
+//TODO: if !rootshell, we reset SIGQUIT to DFL,
+//whereas we have to restore it to what shell got on entry
+//from the parent. See comment above
+
+       t = &sigmode[signo - 1];
+       cur_act = *t;
+       if (cur_act == 0) {
+               /* current setting is not yet known */
+               if (sigaction(signo, NULL, &act)) {
+                       /* pretend it worked; maybe we should give a warning,
+                        * but other shells don't. We don't alter sigmode,
+                        * so we retry every time.
+                        * btw, in Linux it never fails. --vda */
+                       return;
+               }
+               if (act.sa_handler == SIG_IGN) {
+                       cur_act = S_HARD_IGN;
+                       if (mflag
+                        && (signo == SIGTSTP || signo == SIGTTIN || signo == SIGTTOU)
+                       ) {
+                               cur_act = S_IGN;   /* don't hard ignore these */
+                       }
+               }
+       }
+       if (cur_act == S_HARD_IGN || cur_act == new_act)
+               return;
+
+       act.sa_handler = SIG_DFL;
+       switch (new_act) {
+       case S_CATCH:
+               act.sa_handler = onsig;
+               act.sa_flags = 0; /* matters only if !DFL and !IGN */
+               sigfillset(&act.sa_mask); /* ditto */
+               break;
+       case S_IGN:
+               act.sa_handler = SIG_IGN;
+               break;
+       }
+       sigaction_set(signo, &act);
+
+       *t = new_act;
+}
+
+/* mode flags for set_curjob */
+#define CUR_DELETE 2
+#define CUR_RUNNING 1
+#define CUR_STOPPED 0
+
+/* mode flags for dowait */
+#define DOWAIT_NONBLOCK WNOHANG
+#define DOWAIT_BLOCK    0
+
+#if JOBS
+/* pgrp of shell on invocation */
+static int initialpgrp; //references:2
+static int ttyfd = -1; //5
+#endif
+/* array of jobs */
+static struct job *jobtab; //5
+/* size of array */
+static unsigned njobs; //4
+/* current job */
+static struct job *curjob; //lots
+/* number of presumed living untracked jobs */
+static int jobless; //4
+
+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 || DEBUG
+static int
+jobno(const struct job *jp)
+{
+       return jp - jobtab + 1;
+}
+#endif
+
+/*
+ * Convert a job name to a job structure.
+ */
+#if !JOBS
+#define getjob(name, getctl) getjob(name)
+#endif
+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;
+               }
+               if (c == '-') {
+                       if (jp)
+                               jp = jp->prev_job;
+                       err_msg = "No previous job";
+ check:
+                       if (!jp)
+                               goto err;
+                       goto gotit;
+               }
+       }
+
+       if (is_number(p)) {
+// TODO: number() instead? It does error checking...
+               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:
+       ash_msg_and_raise_error(err_msg, name);
+}
+
+/*
+ * Mark a job structure as unused.
+ */
+static void
+freejob(struct job *jp)
+{
+       struct procstat *ps;
+       int i;
+
+       INT_OFF;
+       for (i = jp->nprocs, ps = jp->ps; --i >= 0; ps++) {
+               if (ps->cmd != nullstr)
+                       free(ps->cmd);
+       }
+       if (jp->ps != &jp->ps0)
+               free(jp->ps);
+       jp->used = 0;
+       set_curjob(jp, CUR_DELETE);
+       INT_ON;
+}
+
+#if JOBS
+static void
+xtcsetpgrp(int fd, pid_t pgrp)
+{
+       if (tcsetpgrp(fd, pgrp))
+               ash_msg_and_raise_error("can't set tty process group (%m)");
+}
+
+/*
+ * 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.
+ */
+static void
+setjobctl(int on)
+{
+       int fd;
+       int pgrp;
+
+       if (on == doing_jobctl || rootshell == 0)
+               return;
+       if (on) {
+               int ofd;
+               ofd = fd = open(_PATH_TTY, O_RDWR);
+               if (fd < 0) {
+       /* BTW, bash will try to open(ttyname(0)) if open("/dev/tty") fails.
+        * That sometimes helps to acquire controlling tty.
+        * Obviously, a workaround for bugs when someone
+        * failed to provide a controlling tty to bash! :) */
+                       fd = 2;
+                       while (!isatty(fd))
+                               if (--fd < 0)
+                                       goto out;
+               }
+               fd = fcntl(fd, F_DUPFD, 10);
+               if (ofd >= 0)
+                       close(ofd);
+               if (fd < 0)
+                       goto out;
+               /* fd is a tty at this point */
+               close_on_exec_on(fd);
+               do { /* while we are in the background */
+                       pgrp = tcgetpgrp(fd);
+                       if (pgrp < 0) {
+ out:
+                               ash_msg("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;
+               /* was xtcsetpgrp, but this can make exiting ash
+                * loop forever if pty is already deleted */
+               tcsetpgrp(fd, pgrp);
+               setpgid(0, pgrp);
+               setsignal(SIGTSTP);
+               setsignal(SIGTTOU);
+               setsignal(SIGTTIN);
+ close:
+               if (fd >= 0)
+                       close(fd);
+               fd = -1;
+       }
+       ttyfd = fd;
+       doing_jobctl = on;
+}
+
+static int
+killcmd(int argc, char **argv)
+{
+       int i = 1;
+       if (argv[1] && strcmp(argv[1], "-l") != 0) {
+               do {
+                       if (argv[i][0] == '%') {
+                               struct job *jp = getjob(argv[i], 0);
+                               unsigned pid = jp->ps[0].pid;
+                               /* Enough space for ' -NNN<nul>' */
+                               argv[i] = alloca(sizeof(int)*3 + 3);
+                               /* kill_main has matching code to expect
+                                * leading space. Needed to not confuse
+                                * negative pids with "kill -SIGNAL_NO" syntax */
+                               sprintf(argv[i], " -%u", pid);
+                       }
+               } while (argv[++i]);
+       }
+       return kill_main(argc, argv);
+}
+
+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);
+       flush_stdout_stderr();
+}
+
+
+static int
+restartjob(struct job *jp, int mode)
+{
+       struct procstat *ps;
+       int i;
+       int status;
+       pid_t pgid;
+
+       INT_OFF;
+       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;
+               }
+               ps++;
+       } while (--i);
+ out:
+       status = (mode == FORK_FG) ? waitforjob(jp) : 0;
+       INT_ON;
+       return status;
+}
+
+static int
+fg_bgcmd(int argc UNUSED_PARAM, 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;
+}
+#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;
+}
+
+static int
+dowait(int wait_flags, struct job *job)
+{
+       int pid;
+       int status;
+       struct job *jp;
+       struct job *thisjob;
+       int state;
+
+       TRACE(("dowait(0x%x) called\n", wait_flags));
+
+       /* Do a wait system call. If job control is compiled in, we accept
+        * stopped processes. wait_flags may have WNOHANG, preventing blocking.
+        * NB: _not_ safe_waitpid, we need to detect EINTR */
+       pid = waitpid(-1, &status,
+                       (doing_jobctl ? (wait_flags | WUNTRACED) : wait_flags));
+       TRACE(("wait returns pid=%d, status=0x%x, errno=%d(%s)\n",
+                               pid, status, errno, strerror(errno)));
+       if (pid <= 0)
+               return pid;
+
+       INT_OFF;
+       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:
+       INT_ON;
+
+       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;
+}
+
+static int
+blocking_wait_with_raise_on_sig(struct job *job)
+{
+       pid_t pid = dowait(DOWAIT_BLOCK, job);
+       if (pid <= 0 && pendingsig)
+               raise_exception(EXSIG);
+       return pid;
+}
+
+#if JOBS
+static void
+showjob(FILE *out, struct job *jp, int mode)
+{
+       struct procstat *ps;
+       struct procstat *psend;
+       int col;
+       int indent_col;
+       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 = 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) {
+               strcpy(s + col, "Running");
+               col += sizeof("Running") - 1;
+       } else {
+               int status = psend[-1].status;
+               if (jp->state == JOBSTOPPED)
+                       status = jp->stopstatus;
+               col += sprint_status(s + col, status, 0);
+       }
+
+       goto start;
+
+       do {
+               /* for each process */
+               col = fmtstr(s, 48, " |\n%*c%d ", indent_col, ' ', 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);
+       }
+}
+
+/*
+ * 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));
+
+       /* Handle all finished jobs */
+       while (dowait(DOWAIT_NONBLOCK, NULL) > 0)
+               continue;
+
+       for (jp = curjob; jp; jp = jp->prev_job) {
+               if (!(mode & SHOW_CHANGED) || jp->changed) {
+                       showjob(out, jp, mode);
+               }
+       }
+}
+
+static int
+jobscmd(int argc UNUSED_PARAM, char **argv)
+{
+       int mode, m;
+
+       mode = 0;
+       while ((m = nextopt("lp"))) {
+               if (m == 'l')
+                       mode = SHOW_PID;
+               else
+                       mode = SHOW_PGID;
+       }
+
+       argv = argptr;
+       if (*argv) {
+               do
+                       showjob(stdout, getjob(*argv,0), mode);
+               while (*++argv);
+       } else
+               showjobs(stdout, mode);
+
+       return 0;
+}
+#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;
+}
+
+static int
+waitcmd(int argc UNUSED_PARAM, char **argv)
+{
+       struct job *job;
+       int retval;
+       struct job *jp;
+
+//     exsig++;
+//     xbarrier();
+       if (pendingsig)
+               raise_exception(EXSIG);
+
+       nextopt(nullstr);
+       retval = 0;
+
+       argv = argptr;
+       if (!*argv) {
+               /* wait for all jobs */
+               for (;;) {
+                       jp = curjob;
+                       while (1) {
+                               if (!jp) /* no running procs */
+                                       goto ret;
+                               if (jp->state == JOBRUNNING)
+                                       break;
+                               jp->waited = 1;
+                               jp = jp->prev_job;
+                       }
+       /* man bash:
+        * "When bash is waiting for an asynchronous command via
+        * the wait builtin, the reception of a signal for which a trap
+        * has been set will cause the wait builtin to return immediately
+        * with an exit status greater than 128, immediately after which
+        * the trap is executed."
+        * Do we do it that way? */
+                       blocking_wait_with_raise_on_sig(NULL);
+               }
+       }
+
+       retval = 127;
+       do {
+               if (**argv != '%') {
+                       pid_t pid = number(*argv);
+                       job = curjob;
+                       while (1) {
+                               if (!job)
+                                       goto repeat;
+                               if (job->ps[job->nprocs - 1].pid == pid)
+                                       break;
+                               job = job->prev_job;
+                       }
+               } else
+                       job = getjob(*argv, 0);
+               /* loop until process terminated or stopped */
+               while (job->state == JOBRUNNING)
+                       blocking_wait_with_raise_on_sig(NULL);
+               job->waited = 1;
+               retval = getstatus(job);
+ repeat: ;
+       } while (*++argv);
+
+ ret:
+       return retval;
+}
+
+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 (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;
+}
+
+/*
+ * 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 (doing_jobctl)
+                       continue;
+#endif
+               freejob(jp);
+               break;
+       }
+       memset(jp, 0, sizeof(*jp));
+#if JOBS
+       /* jp->jobctl is a bitfield.
+        * "jp->jobctl |= jobctl" likely to give awful code */
+       if (doing_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(%d) returns %%%d\n", nprocs,
+                               jobno(jp)));
+       return jp;
+}
+
+#if JOBS
+/*
+ * Return a string identifying a command (to be printed by the
+ * jobs command).
+ */
+static char *cmdnextc;
+
+static void
+cmdputs(const char *s)
+{
+       static const char vstype[VSTYPE + 1][3] = {
+               "", "}", "-", "+", "?", "=",
+               "%", "%%", "#", "##"
+               USE_ASH_BASH_COMPAT(, ":", "/", "//")
+       };
+
+       const char *p, *str;
+       char c, cc[2] = " ";
+       char *nextc;
+       int subtype = 0;
+       int quoted = 0;
+
+       nextc = makestrspace((strlen(s) + 1) * 8, cmdnextc);
+       p = s;
+       while ((c = *p++) != 0) {
+               str = NULL;
+               switch (c) {
+               case CTLESC:
+                       c = *p++;
+                       break;
+               case CTLVAR:
+                       subtype = *p++;
+                       if ((subtype & VSTYPE) == VSLENGTH)
+                               str = "${#";
+                       else
+                               str = "${";
+                       if (!(subtype & VSQUOTE) == !(quoted & 1))
+                               goto dostr;
+                       quoted ^= 1;
+                       c = '"';
+                       break;
+               case CTLENDVAR:
+                       str = "\"}" + !(quoted & 1);
+                       quoted >>= 1;
+                       subtype = 0;
+                       goto dostr;
+               case CTLBACKQ:
+                       str = "$(...)";
+                       goto dostr;
+               case CTLBACKQ+CTLQUOTE:
+                       str = "\"$(...)\"";
+                       goto dostr;
+#if ENABLE_SH_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;
+}
+
+/* cmdtxt() and cmdlist() call each other */
+static void cmdtxt(union node *n);
+
+static void
+cmdlist(union node *np, int sep)
+{
+       for (; np; np = np->narg.next) {
+               if (!sep)
+                       cmdputs(" ");
+               cmdtxt(np);
+               if (sep && np->narg.next)
+                       cmdputs(" ");
+       }
+}
+
+static void
+cmdtxt(union node *n)
+{
+       union node *np;
+       struct nodelist *lp;
+       const char *p;
+
+       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;
+#if ENABLE_ASH_BASH_COMPAT
+       case NTO2:
+#endif
+       case NTOFD:
+               p = ">&";
+               goto redir;
+       case NFROM:
+               p = "<";
+               goto redir;
+       case NFROMFD:
+               p = "<&";
+               goto redir;
+       case NFROMTO:
+               p = "<>";
+ redir:
+               cmdputs(utoa(n->nfile.fd));
+               cmdputs(p);
+               if (n->type == NTOFD || n->type == NFROMFD) {
+                       cmdputs(utoa(n->ndup.dupfd));
+                       break;
+               }
+               n = n->nfile.fname;
+               goto donode;
+       }
+}
+
+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 ckstrdup(name);
+}
+#endif /* JOBS */
+
+/*
+ * 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.
+ */
+/*
+ * Clear traps on a fork.
+ */
+static void
+clear_traps(void)
+{
+       char **tp;
+
+       for (tp = trap; tp < &trap[NSIG]; tp++) {
+               if (*tp && **tp) {      /* trap not NULL or "" (SIG_IGN) */
+                       INT_OFF;
+                       free(*tp);
+                       *tp = NULL;
+                       if (tp != &trap[0])
+                               setsignal(tp - trap);
+                       INT_ON;
+               }
+       }
+}
+
+/* Lives far away from here, needed for forkchild */
+static void closescript(void);
+
+/* Called after fork(), in child */
+static void
+forkchild(struct job *jp, /*union node *n,*/ int mode)
+{
+       int oldlvl;
+
+       TRACE(("Child shell %d\n", getpid()));
+       oldlvl = shlvl;
+       shlvl++;
+
+       /* man bash: "Non-builtin commands run by bash have signal handlers
+        * set to the values inherited by the shell from its parent".
+        * Do we do it correctly? */
+
+       closescript();
+       clear_traps();
+#if JOBS
+       /* do job control only in root shell */
+       doing_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 */
+               setpgid(0, pgrp);
+               if (mode == FORK_FG)
+                       xtcsetpgrp(ttyfd, pgrp);
+               setsignal(SIGTSTP);
+               setsignal(SIGTTOU);
+       } else
+#endif
+       if (mode == FORK_BG) {
+               /* man bash: "When job control is not in effect,
+                * asynchronous commands ignore SIGINT and SIGQUIT" */
+               ignoresig(SIGINT);
+               ignoresig(SIGQUIT);
+               if (jp->nprocs == 0) {
+                       close(0);
+                       if (open(bb_dev_null, O_RDONLY) != 0)
+                               ash_msg_and_raise_error("can't open '%s'", bb_dev_null);
+               }
+       }
+       if (!oldlvl) {
+               if (iflag) { /* why if iflag only? */
+                       setsignal(SIGINT);
+                       setsignal(SIGTERM);
+               }
+               /* man bash:
+                * "In all cases, bash ignores SIGQUIT. Non-builtin
+                * commands run by bash have signal handlers
+                * set to the values inherited by the shell
+                * from its parent".
+                * Take care of the second rule: */
+               setsignal(SIGQUIT);
+       }
+       for (jp = curjob; jp; jp = jp->prev_job)
+               freejob(jp);
+       jobless = 0;
+}
+
+/* Called after fork(), in parent */
+#if !JOBS
+#define forkparent(jp, n, mode, pid) forkparent(jp, mode, pid)
+#endif
+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_NONBLOCK, NULL) > 0)
+                       continue;
+               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 */
+               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 (doing_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);
+               ash_msg_and_raise_error("can't 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 interactive 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.
+ */
+static int
+waitforjob(struct job *jp)
+{
+       int st;
+
+       TRACE(("waitforjob(%%%d) called\n", jobno(jp)));
+
+       INT_OFF;
+       while (jp->state == JOBRUNNING) {
+               /* In non-interactive shells, we _can_ get
+                * a keyboard signal here and be EINTRed,
+                * but we just loop back, waiting for command to complete.
+                *
+                * man bash:
+                * "If bash is waiting for a command to complete and receives
+                * a signal for which a trap has been set, the trap
+                * will not be executed until the command completes."
+                *
+                * Reality is that even if trap is not set, bash
+                * will not act on the signal until command completes.
+                * Try this. sleep5intoff.c:
+                * #include <signal.h>
+                * #include <unistd.h>
+                * int main() {
+                *         sigset_t set;
+                *         sigemptyset(&set);
+                *         sigaddset(&set, SIGINT);
+                *         sigaddset(&set, SIGQUIT);
+                *         sigprocmask(SIG_BLOCK, &set, NULL);
+                *         sleep(5);
+                *         return 0;
+                * }
+                * $ bash -c './sleep5intoff; echo hi'
+                * ^C^C^C^C <--- pressing ^C once a second
+                * $ _
+                * $ bash -c './sleep5intoff; echo hi'
+                * ^\^\^\^\hi <--- pressing ^\ (SIGQUIT)
+                * $ _
+                */
+               dowait(DOWAIT_BLOCK, jp);
+       }
+       INT_ON;
+
+       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) /* TODO: do the same with all signals */
+                       raise(SIGINT); /* ... by raise(jp->sig) instead? */
+       }
+       if (jp->state == JOBDONE)
+#endif
+               freejob(jp);
+       return st;
+}
+
+/*
+ * return 1 if there are stopped jobs, otherwise 0
+ */
+static 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;
+}
+
+
+/* ============ redir.c
+ *
+ * Code for dealing with input/output redirection.
+ */
+
+#define EMPTY -2                /* marks an unused slot in redirtab */
+#define CLOSED -3               /* marks a slot of previously-closed fd */
+
+/*
+ * 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.
+ */
+/* openhere needs this forward reference */
+static void expandhere(union node *arg, int fd);
+static int
+openhere(union node *redir)
+{
+       int pip[2];
+       size_t len = 0;
+
+       if (pipe(pip) < 0)
+               ash_msg_and_raise_error("pipe call failed");
+       if (redir->type == NHERE) {
+               len = strlen(redir->nhere.doc->narg.text);
+               if (len <= PIPE_BUF) {
+                       full_write(pip[1], redir->nhere.doc->narg.text, len);
+                       goto out;
+               }
+       }
+       if (forkshell((struct job *)NULL, (union node *)NULL, FORK_NOJOB) == 0) {
+               /* child */
+               close(pip[0]);
+               ignoresig(SIGINT);  //signal(SIGINT, SIG_IGN);
+               ignoresig(SIGQUIT); //signal(SIGQUIT, SIG_IGN);
+               ignoresig(SIGHUP);  //signal(SIGHUP, SIG_IGN);
+               ignoresig(SIGTSTP); //signal(SIGTSTP, SIG_IGN);
+               signal(SIGPIPE, SIG_DFL);
+               if (redir->type == NHERE)
+                       full_write(pip[1], redir->nhere.doc->narg.text, len);
+               else /* NXHERE */
+                       expandhere(redir->nhere.doc, pip[1]);
+               _exit(EXIT_SUCCESS);
+       }
+ 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;
+               f = open(fname, O_RDONLY);
+               if (f < 0)
+                       goto eopen;
+               break;
+       case NFROMTO:
+               fname = redir->nfile.expfname;
+               f = open(fname, O_RDWR|O_CREAT|O_TRUNC, 0666);
+               if (f < 0)
+                       goto ecreate;
+               break;
+       case NTO:
+#if ENABLE_ASH_BASH_COMPAT
+       case NTO2:
+#endif
+               /* Take care of noclobber mode. */
+               if (Cflag) {
+                       fname = redir->nfile.expfname;
+                       f = noclobberopen(fname);
+                       if (f < 0)
+                               goto ecreate;
+                       break;
+               }
+               /* FALLTHROUGH */
+       case NCLOBBER:
+               fname = redir->nfile.expfname;
+               f = open(fname, O_WRONLY|O_CREAT|O_TRUNC, 0666);
+               if (f < 0)
+                       goto ecreate;
+               break;
+       case NAPPEND:
+               fname = redir->nfile.expfname;
+               f = open(fname, O_WRONLY|O_CREAT|O_APPEND, 0666);
+               if (f < 0)
+                       goto ecreate;
+               break;
+       default:
+#if DEBUG
+               abort();
+#endif
+               /* Fall through to eliminate warning. */
+/* Our single caller does this itself */
+//     case NTOFD:
+//     case NFROMFD:
+//             f = -1;
+//             break;
+       case NHERE:
+       case NXHERE:
+               f = openhere(redir);
+               break;
+       }
+
+       return f;
+ ecreate:
+       ash_msg_and_raise_error("can't create %s: %s", fname, errmsg(errno, "nonexistent directory"));
+ eopen:
+       ash_msg_and_raise_error("can't open %s: %s", fname, errmsg(errno, "no such file"));
+}
+
+/*
+ * 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.
+ */
+/* 0x800..00: bit to set in "to" to request dup2 instead of fcntl(F_DUPFD).
+ * old code was doing close(to) prior to copyfd() to achieve the same */
+enum {
+       COPYFD_EXACT   = (int)~(INT_MAX),
+       COPYFD_RESTORE = (int)((unsigned)COPYFD_EXACT >> 1),
+};
+static int
+copyfd(int from, int to)
+{
+       int newfd;
+
+       if (to & COPYFD_EXACT) {
+               to &= ~COPYFD_EXACT;
+               /*if (from != to)*/
+                       newfd = dup2(from, to);
+       } else {
+               newfd = fcntl(from, F_DUPFD, to);
+       }
+       if (newfd < 0) {
+               if (errno == EMFILE)
+                       return EMPTY;
+               /* Happens when source fd is not open: try "echo >&99" */
+               ash_msg_and_raise_error("%d: %m", from);
+       }
+       return newfd;
+}
+
+/* Struct def and variable are moved down to the first usage site */
+struct two_fd_t {
+       int orig, copy;
+};
+struct redirtab {
+       struct redirtab *next;
+       int nullredirs;
+       int pair_count;
+       struct two_fd_t two_fd[0];
+};
+#define redirlist (G_var.redirlist)
+
+static int need_to_remember(struct redirtab *rp, int fd)
+{
+       int i;
+
+       if (!rp) /* remembering was not requested */
+               return 0;
+
+       for (i = 0; i < rp->pair_count; i++) {
+               if (rp->two_fd[i].orig == fd) {
+                       /* already remembered */
+                       return 0;
+               }
+       }
+       return 1;
+}
+
+/* "hidden" fd is a fd used to read scripts, or a copy of such */
+static int is_hidden_fd(struct redirtab *rp, int fd)
+{
+       int i;
+       struct parsefile *pf;
+
+       if (fd == -1)
+               return 0;
+       pf = g_parsefile;
+       while (pf) {
+               if (fd == pf->fd) {
+                       return 1;
+               }
+               pf = pf->prev;
+       }
+       if (!rp)
+               return 0;
+       fd |= COPYFD_RESTORE;
+       for (i = 0; i < rp->pair_count; i++) {
+               if (rp->two_fd[i].copy == fd) {
+                       return 1;
+               }
+       }
+       return 0;
+}
+
+/*
+ * 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.
+ */
+/* flags passed to redirect */
+#define REDIR_PUSH    01        /* save previous values of file descriptors */
+#define REDIR_SAVEFD2 03        /* set preverrout */
+static void
+redirect(union node *redir, int flags)
+{
+       struct redirtab *sv;
+       int sv_pos;
+       int i;
+       int fd;
+       int newfd;
+       int copied_fd2 = -1;
+
+       g_nullredirs++;
+       if (!redir) {
+               return;
+       }
+
+       sv = NULL;
+       sv_pos = 0;
+       INT_OFF;
+       if (flags & REDIR_PUSH) {
+               union node *tmp = redir;
+               do {
+                       sv_pos++;
+#if ENABLE_ASH_BASH_COMPAT
+                       if (redir->nfile.type == NTO2)
+                               sv_pos++;
+#endif
+                       tmp = tmp->nfile.next;
+               } while (tmp);
+               sv = ckmalloc(sizeof(*sv) + sv_pos * sizeof(sv->two_fd[0]));
+               sv->next = redirlist;
+               sv->pair_count = sv_pos;
+               redirlist = sv;
+               sv->nullredirs = g_nullredirs - 1;
+               g_nullredirs = 0;
+               while (sv_pos > 0) {
+                       sv_pos--;
+                       sv->two_fd[sv_pos].orig = sv->two_fd[sv_pos].copy = EMPTY;
+               }
+       }
+
+       do {
+               fd = redir->nfile.fd;
+               if (redir->nfile.type == NTOFD || redir->nfile.type == NFROMFD) {
+                       int right_fd = redir->ndup.dupfd;
+                       /* redirect from/to same file descriptor? */
+                       if (right_fd == fd)
+                               continue;
+                       /* echo >&10 and 10 is a fd opened to the sh script? */
+                       if (is_hidden_fd(sv, right_fd)) {
+                               errno = EBADF; /* as if it is closed */
+                               ash_msg_and_raise_error("%d: %m", right_fd);
+                       }
+                       newfd = -1;
+               } else {
+                       newfd = openredirect(redir); /* always >= 0 */
+                       if (fd == newfd) {
+                               /* Descriptor wasn't open before redirect.
+                                * Mark it for close in the future */
+                               if (need_to_remember(sv, fd)) {
+                                       goto remember_to_close;
+                               }
+                               continue;
+                       }
+               }
+#if ENABLE_ASH_BASH_COMPAT
+ redirect_more:
+#endif
+               if (need_to_remember(sv, fd)) {
+                       /* Copy old descriptor */
+                       i = fcntl(fd, F_DUPFD, 10);
+/* You'd expect copy to be CLOEXECed. Currently these extra "saved" fds
+ * are closed in popredir() in the child, preventing them from leaking
+ * into child. (popredir() also cleans up the mess in case of failures)
+ */
+                       if (i == -1) {
+                               i = errno;
+                               if (i != EBADF) {
+                                       /* Strange error (e.g. "too many files" EMFILE?) */
+                                       if (newfd >= 0)
+                                               close(newfd);
+                                       errno = i;
+                                       ash_msg_and_raise_error("%d: %m", fd);
+                                       /* NOTREACHED */
+                               }
+                               /* EBADF: it is not open - good, remember to close it */
+ remember_to_close:
+                               i = CLOSED;
+                       } else { /* fd is open, save its copy */
+                               /* "exec fd>&-" should not close fds
+                                * which point to script file(s).
+                                * Force them to be restored afterwards */
+                               if (is_hidden_fd(sv, fd))
+                                       i |= COPYFD_RESTORE;
+                       }
+                       if (fd == 2)
+                               copied_fd2 = i;
+                       sv->two_fd[sv_pos].orig = fd;
+                       sv->two_fd[sv_pos].copy = i;
+                       sv_pos++;
+               }
+               if (newfd < 0) {
+                       /* NTOFD/NFROMFD: copy redir->ndup.dupfd to fd */
+                       if (redir->ndup.dupfd < 0) { /* "fd>&-" */
+                               /* Don't want to trigger debugging */
+                               if (fd != -1)
+                                       close(fd);
+                       } else {
+                               copyfd(redir->ndup.dupfd, fd | COPYFD_EXACT);
+                       }
+               } else if (fd != newfd) { /* move newfd to fd */
+                       copyfd(newfd, fd | COPYFD_EXACT);
+#if ENABLE_ASH_BASH_COMPAT
+                       if (!(redir->nfile.type == NTO2 && fd == 2))
+#endif
+                               close(newfd);
+               }
+#if ENABLE_ASH_BASH_COMPAT
+               if (redir->nfile.type == NTO2 && fd == 1) {
+                       /* We already redirected it to fd 1, now copy it to 2 */
+                       newfd = 1;
+                       fd = 2;
+                       goto redirect_more;
+               }
+#endif
+       } while ((redir = redir->nfile.next) != NULL);
+
+       INT_ON;
+       if ((flags & REDIR_SAVEFD2) && copied_fd2 >= 0)
+               preverrout_fd = copied_fd2;
+}
+
+/*
+ * Undo the effects of the last redirection.
+ */
+static void
+popredir(int drop, int restore)
+{
+       struct redirtab *rp;
+       int i;
+
+       if (--g_nullredirs >= 0)
+               return;
+       INT_OFF;
+       rp = redirlist;
+       for (i = 0; i < rp->pair_count; i++) {
+               int fd = rp->two_fd[i].orig;
+               int copy = rp->two_fd[i].copy;
+               if (copy == CLOSED) {
+                       if (!drop)
+                               close(fd);
+                       continue;
+               }
+               if (copy != EMPTY) {
+                       if (!drop || (restore && (copy & COPYFD_RESTORE))) {
+                               copy &= ~COPYFD_RESTORE;
+                               /*close(fd);*/
+                               copyfd(copy, fd | COPYFD_EXACT);
+                       }
+                       close(copy & ~COPYFD_RESTORE);
+               }
+       }
+       redirlist = rp->next;
+       g_nullredirs = rp->nullredirs;
+       free(rp);
+       INT_ON;
+}
+
+/*
+ * Undo all redirections.  Called on error or interrupt.
+ */
+
+/*
+ * Discard all saved file descriptors.
+ */
+static void
+clearredir(int drop)
+{
+       for (;;) {
+               g_nullredirs = 0;
+               if (!redirlist)
+                       break;
+               popredir(drop, /*restore:*/ 0);
+       }
+}
+
+static int
+redirectsafe(union node *redir, int flags)
+{
+       int err;
+       volatile int saveint;
+       struct jmploc *volatile savehandler = exception_handler;
+       struct jmploc jmploc;
+
+       SAVE_INT(saveint);
+       /* "echo 9>/dev/null; echo >&9; echo result: $?" - result should be 1, not 2! */
+       err = setjmp(jmploc.loc); // huh?? was = setjmp(jmploc.loc) * 2;
+       if (!err) {
+               exception_handler = &jmploc;
+               redirect(redir, flags);
+       }
+       exception_handler = savehandler;
+       if (err && exception_type != EXERROR)
+               longjmp(exception_handler->loc, 1);
+       RESTORE_INT(saveint);
+       return err;
+}
+
+
+/* ============ Routines to expand arguments to commands
+ *
+ * We have to deal with backquotes, shell variables, and file metacharacters.
+ */
+
+#if ENABLE_SH_MATH_SUPPORT
+static arith_t
+ash_arith(const char *s)
+{
+       arith_eval_hooks_t math_hooks;
+       arith_t result;
+       int errcode = 0;
+
+       math_hooks.lookupvar = lookupvar;
+       math_hooks.setvar = setvar;
+       math_hooks.endofname = endofname;
+
+       INT_OFF;
+       result = arith(s, &errcode, &math_hooks);
+       if (errcode < 0) {
+               if (errcode == -3)
+                       ash_msg_and_raise_error("exponent less than 0");
+               if (errcode == -2)
+                       ash_msg_and_raise_error("divide by zero");
+               if (errcode == -5)
+                       ash_msg_and_raise_error("expression recursion loop detected");
+               raise_error_syntax(s);
+       }
+       INT_ON;
+
+       return result;
+}
+#endif
+
+/*
+ * 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 */
+/*
+ * _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 */
+};
+
+struct arglist {
+       struct strlist *list;
+       struct strlist **lastp;
+};
+
+/* 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;
+
+/*
+ * Our own itoa().
+ */
+static int
+cvtnum(arith_t num)
+{
+       int len;
+
+       expdest = makestrspace(32, expdest);
+       len = fmtstr(expdest, 32, arith_t_fmt, num);
+       STADJUST(len, expdest);
+       return len;
+}
+
+static size_t
+esclen(const char *start, const char *p)
+{
+       size_t esc = 0;
+
+       while (p > start && *--p == CTLESC) {
+               esc++;
+       }
+       return esc;
+}
+
+/*
+ * Remove any CTLESC characters from a string.
+ */
+static char *
+_rmescapes(char *str, int flag)
+{
+       static const char qchars[] ALIGN1 = { CTLESC, CTLQUOTEMARK, '\0' };
+
+       char *p, *q, *r;
+       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 = (char *)memcpy(q, str, len) + 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;
+}
+#define rmescapes(p) _rmescapes((p), 0)
+
+#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);
+}
+
+/*
+ * 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 = signed_char2int(*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);
+}
+
+/*
+ * 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 {
+               INT_OFF;
+               ifsp = ckzalloc(sizeof(*ifsp));
+               /*ifsp->next = NULL; - ckzalloc did it */
+               ifslastp->next = ifsp;
+               INT_ON;
+       }
+       ifslastp = ifsp;
+       ifslastp->begoff = start;
+       ifslastp->endoff = end;
+       ifslastp->nulonly = nulonly;
+}
+
+static void
+removerecordregions(int endoff)
+{
+       if (ifslastp == NULL)
+               return;
+
+       if (ifsfirst.endoff > endoff) {
+               while (ifsfirst.next != NULL) {
+                       struct ifsregion *ifsp;
+                       INT_OFF;
+                       ifsp = ifsfirst.next->next;
+                       free(ifsfirst.next);
+                       ifsfirst.next = ifsp;
+                       INT_ON;
+               }
+               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;
+               INT_OFF;
+               ifsp = ifslastp->next->next;
+               free(ifslastp->next);
+               ifslastp->next = ifsp;
+               INT_ON;
+       }
+       if (ifslastp->endoff > endoff)
+               ifslastp->endoff = endoff;
+}
+
+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 {
+               pw = getpwnam(name);
+               if (pw == 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;
+}
+
+/*
+ * 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.
+ */
+struct backcmd {                /* result of evalbackcmd */
+       int fd;                 /* file descriptor to read from */
+       int nleft;              /* number of chars in buffer */
+       char *buf;              /* buffer */
+       struct job *jp;         /* job structure for command */
+};
+
+/* These forward decls are needed to use "eval" code for backticks handling: */
+static uint8_t back_exitstatus; /* exit status of backquoted command */
+#define EV_EXIT 01              /* exit after evaluating tree */
+static void evaltree(union node *, int);
+
+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)
+                       ash_msg_and_raise_error("pipe call failed");
+               jp = makejob(/*n,*/ 1);
+               if (forkshell(jp, n, FORK_NOJOB) == 0) {
+                       FORCE_INT_ON;
+                       close(pip[0]);
+                       if (pip[1] != 1) {
+                               /*close(1);*/
+                               copyfd(pip[1], 1 | COPYFD_EXACT);
+                               close(pip[1]);
+                       }
+                       eflag = 0;
+                       evaltree(n, EV_EXIT); /* actually evaltreenr... */
+                       /* 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));
+}
+
+/*
+ * 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;
+
+       INT_OFF;
+       setstackmark(&smark);
+       dest = expdest;
+       startloc = dest - (char *)stackblock();
+       grabstackstr(dest);
+       evalbackcmd(cmd, &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 = nonblock_safe_read(in.fd, buf, sizeof(buf));
+               TRACE(("expbackq: read returns %d\n", i));
+               if (i <= 0)
+                       break;
+               p = buf;
+       }
+
+       free(in.buf);
+       if (in.fd >= 0) {
+               close(in.fd);
+               back_exitstatus = waitforjob(in.jp);
+       }
+       INT_ON;
+
+       /* 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));
+}
+
+#if ENABLE_SH_MATH_SUPPORT
+/*
+ * Expand arithmetic expression.  Backup to start of expression,
+ * evaluate, place result in (backed up) result, adjust string position.
+ */
+static 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) {
+                               ash_msg_and_raise_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(ash_arith(p + 2));
+
+       if (flag != '"')
+               recordregion(begoff, begoff + len, 0);
+}
+#endif
+
+/* argstr needs it */
+static char *evalvar(char *p, int flag, struct strlist *var_str_list);
+
+/*
+ * 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.
+ *
+ * var_str_list (can be NULL) is a list of "VAR=val" strings which take precedence
+ * over shell varables. Needed for "A=a B=$A; echo $B" case - we use it
+ * for correct expansion of "B=$A" word.
+ */
+static void
+argstr(char *p, int flag, struct strlist *var_str_list)
+{
+       static const char spclchars[] ALIGN1 = {
+               '=',
+               ':',
+               CTLQUOTEMARK,
+               CTLENDVAR,
+               CTLESC,
+               CTLVAR,
+               CTLBACKQ,
+               CTLBACKQ | CTLQUOTE,
+#if ENABLE_SH_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)
+#if ENABLE_SH_MATH_SUPPORT
+                                       || c == CTLENDARI
+#endif
+                  )) {
+                       /* c == '=' || c == ':' || c == CTLENDARI */
+                       length++;
+               }
+               if (length > 0) {
+                       int newloc;
+                       expdest = stack_nputstr(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, 4) &&
+                               (p[4] == CTLQUOTEMARK || (
+                                       p[4] == CTLENDVAR &&
+                                       p[5] == CTLQUOTEMARK
+                               ))
+                       ) {
+                               p = evalvar(p + 1, flag, /* var_str_list: */ NULL) + 1;
+                               goto start;
+                       }
+                       inquotes = !inquotes;
+ addquote:
+                       if (quotes) {
+                               p--;
+                               length++;
+                               startloc++;
+                       }
+                       break;
+               case CTLESC:
+                       startloc++;
+                       length++;
+                       goto addquote;
+               case CTLVAR:
+                       p = evalvar(p, flag, var_str_list);
+                       goto start;
+               case CTLBACKQ:
+                       c = 0;
+               case CTLBACKQ|CTLQUOTE:
+                       expbackq(argbackq->n, c, quotes);
+                       argbackq = argbackq->next;
+                       goto start;
+#if ENABLE_SH_MATH_SUPPORT
+               case CTLENDARI:
+                       p--;
+                       expari(quotes);
+                       goto start;
+#endif
+               }
+       }
+ breakloop:
+       ;
+}
+
+static char *
+scanleft(char *startp, char *rmesc, char *rmescend UNUSED_PARAM, char *str, int quotes,
+       int zero)
+{
+// This commented out code was added by James Simmons <jsimmons@infradead.org>
+// as part of a larger change when he added support for ${var/a/b}.
+// However, it broke # and % operators:
+//
+//var=ababcdcd
+//                 ok       bad
+//echo ${var#ab}   abcdcd   abcdcd
+//echo ${var##ab}  abcdcd   abcdcd
+//echo ${var#a*b}  abcdcd   ababcdcd  (!)
+//echo ${var##a*b} cdcd     cdcd
+//echo ${var#?}    babcdcd  ababcdcd  (!)
+//echo ${var##?}   babcdcd  babcdcd
+//echo ${var#*}    ababcdcd babcdcd   (!)
+//echo ${var##*}
+//echo ${var%cd}   ababcd   ababcd
+//echo ${var%%cd}  ababcd   abab      (!)
+//echo ${var%c*d}  ababcd   ababcd
+//echo ${var%%c*d} abab     ababcdcd  (!)
+//echo ${var%?}    ababcdc  ababcdc
+//echo ${var%%?}   ababcdc  ababcdcd  (!)
+//echo ${var%*}    ababcdcd ababcdcd
+//echo ${var%%*}
+//
+// Commenting it back out helped. Remove it completely if it really
+// is not needed.
+
+       char *loc, *loc2; //, *full;
+       char c;
+
+       loc = startp;
+       loc2 = rmesc;
+       do {
+               int match; // = strlen(str);
+               const char *s = loc2;
+
+               c = *loc2;
+               if (zero) {
+                       *loc2 = '\0';
+                       s = rmesc;
+               }
+               match = pmatch(str, s); // this line was deleted
+
+//             // chop off end if its '*'
+//             full = strrchr(str, '*');
+//             if (full && full != str)
+//                     match--;
+//
+//             // If str starts with '*' replace with s.
+//             if ((*str == '*') && strlen(s) >= match) {
+//                     full = xstrdup(s);
+//                     strncpy(full+strlen(s)-match+1, str+1, match-1);
+//             } else
+//                     full = xstrndup(str, match);
+//             match = strncmp(s, full, strlen(full));
+//             free(full);
+//
+               *loc2 = c;
+               if (match) // 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 void varunset(const char *, const char *, const char *, int) NORETURN;
+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;
+               }
+       }
+       ash_msg_and_raise_error("%.*s: %s%s", end - var - 1, var, msg, tail);
+}
+
+#if ENABLE_ASH_BASH_COMPAT
+static char *
+parse_sub_pattern(char *arg, int inquotes)
+{
+       char *idx, *repl = NULL;
+       unsigned char c;
+
+       idx = arg;
+       while (1) {
+               c = *arg;
+               if (!c)
+                       break;
+               if (c == '/') {
+                       /* Only the first '/' seen is our separator */
+                       if (!repl) {
+                               repl = idx + 1;
+                               c = '\0';
+                       }
+               }
+               *idx++ = c;
+               if (!inquotes && c == '\\' && arg[1] == '\\')
+                       arg++; /* skip both \\, not just first one */
+               arg++;
+       }
+       *idx = c; /* NUL */
+
+       return repl;
+}
+#endif /* ENABLE_ASH_BASH_COMPAT */
+
+static const char *
+subevalvar(char *p, char *str, int strloc, int subtype,
+               int startloc, int varflags, int quotes, struct strlist *var_str_list)
+{
+       struct nodelist *saveargbackq = argbackq;
+       char *startp;
+       char *loc;
+       char *rmesc, *rmescend;
+       USE_ASH_BASH_COMPAT(char *repl = NULL;)
+       USE_ASH_BASH_COMPAT(char null = '\0';)
+       USE_ASH_BASH_COMPAT(int pos, len, orig_len;)
+       int saveherefd = herefd;
+       int amount, workloc, resetloc;
+       int zero;
+       char *(*scan)(char*, char*, char*, char*, int, int);
+
+       herefd = -1;
+       argstr(p, (subtype != VSASSIGN && subtype != VSQUESTION) ? EXP_CASE : 0,
+                       var_str_list);
+       STPUTC('\0', expdest);
+       herefd = saveherefd;
+       argbackq = saveargbackq;
+       startp = (char *)stackblock() + startloc;
+
+       switch (subtype) {
+       case VSASSIGN:
+               setvar(str, startp, 0);
+               amount = startp - expdest;
+               STADJUST(amount, expdest);
+               return startp;
+
+#if ENABLE_ASH_BASH_COMPAT
+       case VSSUBSTR:
+               loc = str = stackblock() + strloc;
+// TODO: number() instead? It does error checking...
+               pos = atoi(loc);
+               len = str - startp - 1;
+
+               /* *loc != '\0', guaranteed by parser */
+               if (quotes) {
+                       char *ptr;
+
+                       /* We must adjust the length by the number of escapes we find. */
+                       for (ptr = startp; ptr < (str - 1); ptr++) {
+                               if (*ptr == CTLESC) {
+                                       len--;
+                                       ptr++;
+                               }
+                       }
+               }
+               orig_len = len;
+
+               if (*loc++ == ':') {
+// TODO: number() instead? It does error checking...
+                       len = atoi(loc);
+               } else {
+                       len = orig_len;
+                       while (*loc && *loc != ':')
+                               loc++;
+                       if (*loc++ == ':')
+// TODO: number() instead? It does error checking...
+                               len = atoi(loc);
+               }
+               if (pos >= orig_len) {
+                       pos = 0;
+                       len = 0;
+               }
+               if (len > (orig_len - pos))
+                       len = orig_len - pos;
+
+               for (str = startp; pos; str++, pos--) {
+                       if (quotes && *str == CTLESC)
+                               str++;
+               }
+               for (loc = startp; len; len--) {
+                       if (quotes && *str == CTLESC)
+                               *loc++ = *str++;
+                       *loc++ = *str++;
+               }
+               *loc = '\0';
+               amount = loc - expdest;
+               STADJUST(amount, expdest);
+               return loc;
+#endif
+
+       case VSQUESTION:
+               varunset(p, str, startp, varflags);
+               /* NOTREACHED */
+       }
+       resetloc = expdest - (char *)stackblock();
+
+       /* We'll comeback here if we grow the stack while handling
+        * a VSREPLACE or VSREPLACEALL, since our pointers into the
+        * stack will need rebasing, and we'll need to remove our work
+        * areas each time
+        */
+ USE_ASH_BASH_COMPAT(restart:)
+
+       amount = expdest - ((char *)stackblock() + resetloc);
+       STADJUST(-amount, expdest);
+       startp = (char *)stackblock() + startloc;
+
+       rmesc = startp;
+       rmescend = (char *)stackblock() + strloc;
+       if (quotes) {
+               rmesc = _rmescapes(startp, RMESCAPE_ALLOC | RMESCAPE_GROW);
+               if (rmesc != startp) {
+                       rmescend = expdest;
+                       startp = (char *)stackblock() + startloc;
+               }
+       }
+       rmescend--;
+       str = (char *)stackblock() + strloc;
+       preglob(str, varflags & VSQUOTE, 0);
+       workloc = expdest - (char *)stackblock();
+
+#if ENABLE_ASH_BASH_COMPAT
+       if (subtype == VSREPLACE || subtype == VSREPLACEALL) {
+               char *idx, *end, *restart_detect;
+
+               if (!repl) {
+                       repl = parse_sub_pattern(str, varflags & VSQUOTE);
+                       if (!repl)
+                               repl = &null;
+               }
+
+               /* If there's no pattern to match, return the expansion unmolested */
+               if (*str == '\0')
+                       return 0;
+
+               len = 0;
+               idx = startp;
+               end = str - 1;
+               while (idx < end) {
+                       loc = scanright(idx, rmesc, rmescend, str, quotes, 1);
+                       if (!loc) {
+                               /* No match, advance */
+                               restart_detect = stackblock();
+                               STPUTC(*idx, expdest);
+                               if (quotes && *idx == CTLESC) {
+                                       idx++;
+                                       len++;
+                                       STPUTC(*idx, expdest);
+                               }
+                               if (stackblock() != restart_detect)
+                                       goto restart;
+                               idx++;
+                               len++;
+                               rmesc++;
+                               continue;
+                       }
+
+                       if (subtype == VSREPLACEALL) {
+                               while (idx < loc) {
+                                       if (quotes && *idx == CTLESC)
+                                               idx++;
+                                       idx++;
+                                       rmesc++;
+                               }
+                       } else {
+                               idx = loc;
+                       }
+
+                       for (loc = repl; *loc; loc++) {
+                               restart_detect = stackblock();
+                               STPUTC(*loc, expdest);
+                               if (stackblock() != restart_detect)
+                                       goto restart;
+                               len++;
+                       }
+
+                       if (subtype == VSREPLACE) {
+                               while (*idx) {
+                                       restart_detect = stackblock();
+                                       STPUTC(*idx, expdest);
+                                       if (stackblock() != restart_detect)
+                                               goto restart;
+                                       len++;
+                                       idx++;
+                               }
+                               break;
+                       }
+               }
+
+               /* We've put the replaced text into a buffer at workloc, now
+                * move it to the right place and adjust the stack.
+                */
+               startp = stackblock() + startloc;
+               STPUTC('\0', expdest);
+               memmove(startp, stackblock() + workloc, len);
+               startp[len++] = '\0';
+               amount = expdest - ((char *)stackblock() + startloc + len - 1);
+               STADJUST(-amount, expdest);
+               return startp;
+       }
+#endif /* ENABLE_ASH_BASH_COMPAT */
+
+       subtype -= VSTRIMRIGHT;
+#if DEBUG
+       if (subtype < 0 || subtype > 7)
+               abort();
+#endif
+       /* 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;
+}
+
+/*
+ * Add the value of a specialized variable to the stack string.
+ */
+static ssize_t
+varvalue(char *name, int varflags, int flags, struct strlist *var_str_list)
+{
+       int num;
+       const 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 '-':
+               expdest = makestrspace(NOPTS, expdest);
+               for (i = NOPTS - 1; i >= 0; i--) {
+                       if (optlist[i]) {
+                               USTPUTC(optletters(i), expdest);
+                               len++;
+                       }
+               }
+               break;
+       case '@':
+               if (sep)
+                       goto param;
+               /* fall through */
+       case '*':
+               sep = ifsset() ? signed_char2int(ifsval()[0]) : ' ';
+               if (quotes && (SIT(sep, syntax) == CCTL || SIT(sep, syntax) == CBACK))
+                       sepq = 1;
+ param:
+               ap = shellparam.p;
+               if (!ap)
+                       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':
+// TODO: number() instead? It does error checking...
+               num = atoi(name);
+               if (num < 0 || num > shellparam.nparam)
+                       return -1;
+               p = num ? shellparam.p[num - 1] : arg0;
+               goto value;
+       default:
+               /* NB: name has form "VAR=..." */
+
+               /* "A=a B=$A" case: var_str_list is a list of "A=a" strings
+                * which should be considered before we check variables. */
+               if (var_str_list) {
+                       unsigned name_len = (strchrnul(name, '=') - name) + 1;
+                       p = NULL;
+                       do {
+                               char *str, *eq;
+                               str = var_str_list->text;
+                               eq = strchr(str, '=');
+                               if (!eq) /* stop at first non-assignment */
+                                       break;
+                               eq++;
+                               if (name_len == (unsigned)(eq - str)
+                                && strncmp(str, name, name_len) == 0) {
+                                       p = eq;
+                                       /* goto value; - WRONG! */
+                                       /* think "A=1 A=2 B=$A" */
+                               }
+                               var_str_list = var_str_list->next;
+                       } while (var_str_list);
+                       if (p)
+                               goto value;
+               }
+               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;
+}
+
+/*
+ * Expand a variable, and return a pointer to the next character in the
+ * input string.
+ */
+static char *
+evalvar(char *p, int flag, struct strlist *var_str_list)
+{
+       char varflags;
+       char subtype;
+       char quoted;
+       char easy;
+       char *var;
+       int patloc;
+       int startloc;
+       ssize_t varlen;
+
+       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, var_str_list);
+       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),
+                               var_str_list
+                       );
+                       goto end;
+               }
+               if (easy)
+                       goto record;
+               goto end;
+       }
+
+       if (subtype == VSASSIGN || subtype == VSQUESTION) {
+               if (varlen < 0) {
+                       if (subevalvar(p, var, /* strloc: */ 0,
+                                       subtype, startloc, varflags,
+                                       /* quotes: */ 0,
+                                       var_str_list)
+                       ) {
+                               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 record;
+               goto end;
+       }
+
+#if DEBUG
+       switch (subtype) {
+       case VSTRIMLEFT:
+       case VSTRIMLEFTMAX:
+       case VSTRIMRIGHT:
+       case VSTRIMRIGHTMAX:
+#if ENABLE_ASH_BASH_COMPAT
+       case VSSUBSTR:
+       case VSREPLACE:
+       case VSREPLACEALL:
+#endif
+               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 (0 == subevalvar(p, /* str: */ NULL, patloc, subtype,
+                               startloc, varflags,
+                               /* quotes: */ flag & (EXP_FULL | EXP_CASE),
+                               var_str_list)
+               ) {
+                       int amount = expdest - (
+                               (char *)stackblock() + patloc - 1
+                       );
+                       STADJUST(-amount, expdest);
+               }
+               /* Remove any recorded regions beyond start of variable */
+               removerecordregions(startloc);
+ record:
+               recordregion(startloc, expdest - (char *)stackblock(), quoted);
+       }
+
+ end:
+       if (subtype != VSNORMAL) {      /* skip to end of alternative */
+               int nesting = 1;
+               for (;;) {
+                       char c = *p++;
+                       if (c == 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;
+}
+
+/*
+ * 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)) {
+                                       p++;
+                                       continue;
+                               }
+                               if (!nulonly)
+                                       ifsspc = (strchr(defifs, *p) != NULL);
+                               /* Ignore IFS whitespace at start */
+                               if (q == start && ifsspc) {
+                                       p++;
+                                       start = p;
+                                       continue;
+                               }
+                               *q = '\0';
+                               sp = stzalloc(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;
+                                               }
+                                               if (strchr(defifs, *p) == NULL) {
+                                                       if (ifsspc) {
+                                                               p++;
+                                                               ifsspc = 0;
+                                                       } else {
+                                                               p = q;
+                                                               break;
+                                                       }
+                                               } else
+                                                       p++;
+                                       }
+                               }
+                               start = p;
+                       } /* while */
+                       ifsp = ifsp->next;
+               } while (ifsp != NULL);
+               if (nulonly)
+                       goto add;
+       }
+
+       if (!*start)
+               return;
+
+ add:
+       sp = stzalloc(sizeof(*sp));
+       sp->text = start;
+       *arglist->lastp = sp;
+       arglist->lastp = &sp->next;
+}
+
+static void
+ifsfree(void)
+{
+       struct ifsregion *p;
+
+       INT_OFF;
+       p = ifsfirst.next;
+       do {
+               struct ifsregion *ifsp;
+               ifsp = p->next;
+               free(p);
+               p = ifsp;
+       } while (p);
+       ifslastp = NULL;
+       ifsfirst.next = NULL;
+       INT_ON;
+}
+
+/*
+ * Add a file name to the list.
+ */
+static void
+addfname(const char *name)
+{
+       struct strlist *sp;
+
+       sp = stzalloc(sizeof(*sp));
+       sp->text = ststrdup(name);
+       *exparg.lastp = sp;
+       exparg.lastp = &sp->next;
+}
+
+static char *expdir;
+
+/*
+ * 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';
+       }
+       dirp = opendir(cp);
+       if (dirp == 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) {
+                               strcpy(enddir, dp->d_name);
+                               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] = '/';
+}
+
+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 (;;) {
+#if ENABLE_LOCALE_SUPPORT
+               if (strcoll(p->text, q->text) < 0)
+#else
+               if (strcmp(p->text, q->text) < 0)
+#endif
+                                               {
+                       *lpp = p;
+                       lpp = &p->next;
+                       p = *lpp;
+                       if (p == NULL) {
+                               *lpp = q;
+                               break;
+                       }
+               } else {
+                       *lpp = q;
+                       lpp = &q->next;
+                       q = *lpp;
+                       if (q == NULL) {
+                               *lpp = p;
+                               break;
+                       }
+               }
+       }
+       return list;
+}
+
+/*
+ * 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 void
+expandmeta(struct strlist *str /*, int flag*/)
+{
+       static const char metachars[] ALIGN1 = {
+               '*', '?', '[', 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;
+
+               INT_OFF;
+               p = preglob(str->text, 0, RMESCAPE_ALLOC | RMESCAPE_HEAP);
+               {
+                       int i = strlen(str->text);
+                       expdir = ckmalloc(i < 2048 ? 2048 : i); /* XXX */
+               }
+
+               expmeta(expdir, p);
+               free(expdir);
+               if (p != str->text)
+                       free(p);
+               INT_ON;
+               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;
+       }
+}
+
+/*
+ * 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.
+ */
+static 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,
+                       /* var_str_list: */ arglist ? arglist->list : NULL);
+       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 = stzalloc(sizeof(*sp));
+               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;
+       }
+}
+
+/*
+ * 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());
+}
+
+/*
+ * Returns true if the pattern matches the string.
+ */
+static int
+patmatch(char *pattern, const char *string)
+{
+       return pmatch(preglob(pattern, 0, 0), string);
+}
+
+/*
+ * See if a pattern matches in a case statement.
+ */
+static 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,
+                       /* var_str_list: */ NULL);
+       STACKSTRNUL(expdest);
+       result = patmatch(stackblock(), val);
+       popstackmark(&smark);
+       return result;
+}
+
+
+/* ============ find_command */
+
+struct builtincmd {
+       const char *name;
+       int (*builtin)(int, char **);
+       /* unsigned flags; */
+};
+#define IS_BUILTIN_SPECIAL(b) ((b)->name[0] & 1)
+/* "regular" builtins always take precedence over commands,
+ * regardless of PATH=....%builtin... position */
+#define IS_BUILTIN_REGULAR(b) ((b)->name[0] & 2)
+#define IS_BUILTIN_ASSIGN(b)  ((b)->name[0] & 4)
+
+struct cmdentry {
+       smallint cmdtype;       /* CMDxxx */
+       union param {
+               int index;
+               /* index >= 0 for commands without path (slashes) */
+               /* (TODO: what exactly does the value mean? PATH position?) */
+               /* index == -1 for commands with slashes */
+               /* index == (-2 - applet_no) for NOFORK applets */
+               const struct builtincmd *cmd;
+               struct funcnode *func;
+       } u;
+};
+/* 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 */
+
+/* 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 void find_command(char *, struct cmdentry *, int, const char *);
+
+
+/* ============ Hashing commands */
+
+/*
+ * 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.
+ */
+
+struct tblentry {
+       struct tblentry *next;  /* next entry in hash chain */
+       union param param;      /* definition of builtin function */
+       smallint cmdtype;       /* CMDxxx */
+       char rehash;            /* if set, cd done since entry created */
+       char cmdname[1];        /* name of command */
+};
+
+static struct tblentry **cmdtable;
+#define INIT_G_cmdtable() do { \
+       cmdtable = xzalloc(CMDTABLESIZE * sizeof(cmdtable[0])); \
+} while (0)
+
+static int builtinloc = -1;     /* index in path of %builtin, or -1 */
+
+
+static void
+tryexec(USE_FEATURE_SH_STANDALONE(int applet_no,) char *cmd, char **argv, char **envp)
+{
+       int repeated = 0;
+
+#if ENABLE_FEATURE_SH_STANDALONE
+       if (applet_no >= 0) {
+               if (APPLET_IS_NOEXEC(applet_no)) {
+                       while (*envp)
+                               putenv(*envp++);
+                       run_applet_no_and_exit(applet_no, argv);
+               }
+               /* re-exec ourselves with the new arguments */
+               execve(bb_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) {
+               free(argv);
+               return;
+       }
+       if (errno == ENOEXEC) {
+               char **ap;
+               char **new;
+
+               for (ap = argv; *ap; ap++)
+                       continue;
+               ap = new = ckmalloc((ap - argv + 2) * sizeof(ap[0]));
+               ap[1] = cmd;
+               ap[0] = cmd = (char *)DEFAULT_SHELL;
+               ap += 2;
+               argv++;
+               while ((*ap++ = *argv++) != NULL)
+                       continue;
+               argv = new;
+               repeated++;
+               goto repeat;
+       }
+}
+
+/*
+ * 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 **, const char *, int) NORETURN;
+static void
+shellexec(char **argv, const char *path, int idx)
+{
+       char *cmdname;
+       int e;
+       char **envp;
+       int exerrno;
+#if ENABLE_FEATURE_SH_STANDALONE
+       int applet_no = -1;
+#endif
+
+       clearredir(/*drop:*/ 1);
+       envp = listvars(VEXPORT, VUNSET, 0);
+       if (strchr(argv[0], '/') != NULL
+#if ENABLE_FEATURE_SH_STANDALONE
+        || (applet_no = find_applet_by_name(argv[0])) >= 0
+#endif
+       ) {
+               tryexec(USE_FEATURE_SH_STANDALONE(applet_no,) argv[0], argv, envp);
+               e = errno;
+       } else {
+               e = ENOENT;
+               while ((cmdname = padvance(&path, argv[0])) != NULL) {
+                       if (--idx < 0 && pathopt == NULL) {
+                               tryexec(USE_FEATURE_SH_STANDALONE(-1,) 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));
+       ash_msg_and_raise(EXEXEC, "%s: %s", argv[0], errmsg(e, "not found"));
+       /* NOTREACHED */
+}
+
+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));
+}
+
+/*
+ * 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;
+
+       INT_OFF;
+       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;
+                               free(cmdp);
+                       } else {
+                               pp = &cmdp->next;
+                       }
+               }
+       }
+       INT_ON;
+}
+
+/*
+ * 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 (strcmp(cmdp->cmdname, name) == 0)
+                       break;
+               pp = &cmdp->next;
+       }
+       if (add && cmdp == NULL) {
+               cmdp = *pp = ckzalloc(sizeof(struct tblentry)
+                               + strlen(name)
+                               /* + 1 - already done because
+                                * tblentry::cmdname is char[1] */);
+               /*cmdp->next = NULL; - ckzalloc did it */
+               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;
+
+       INT_OFF;
+       cmdp = *lastcmdentry;
+       *lastcmdentry = cmdp->next;
+       if (cmdp->cmdtype == CMDFUNCTION)
+               freefunc(cmdp->param.func);
+       free(cmdp);
+       INT_ON;
+}
+
+/*
+ * 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;
+}
+
+static int
+hashcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
+{
+       struct tblentry **pp;
+       struct tblentry *cmdp;
+       int c;
+       struct cmdentry entry;
+       char *name;
+
+       if (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) {
+               cmdp = cmdlookup(name, 0);
+               if (cmdp != 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;
+}
+
+/*
+ * 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 *new)
+{
+       const char *old;
+       int firstchange;
+       int idx;
+       int idx_bltin;
+
+       old = pathval();
+       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;
+}
+
+#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
+typedef smallint token_id_t;
+
+/* 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];
+
+//try this:
+//if (tok < TSEMI) return tokname_array[tok] + 1;
+//sprintf(buf, "\"%s\"", tokname_array[tok] + 1);
+//return buf;
+
+       if (tok >= TSEMI)
+               buf[0] = '"';
+       sprintf(buf + (tok >= TSEMI), "%s%c",
+                       tokname_array[tok] + 1, (tok >= TSEMI ? '"' : 0));
+       return buf;
+}
+
+/* Wrapper around strcmp for qsort/bsearch/... */
+static int
+pstrcmp(const void *a, const void *b)
+{
+       return strcmp((char*) a, (*(char**) b) + 1);
+}
+
+static const char *const *
+findkwd(const char *s)
+{
+       return bsearch(s, tokname_array + KWDOFFSET,
+                       ARRAY_SIZE(tokname_array) - KWDOFFSET,
+                       sizeof(tokname_array[0]), pstrcmp);
+}
+
+/*
+ * Locate and print what a word is...
+ */
+static int
+describe_command(char *command, int describe_command_verbose)
+{
+       struct cmdentry entry;
+       struct tblentry *cmdp;
+#if ENABLE_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;
+       }
+
+#if ENABLE_ASH_ALIAS
+       /* Then look at the aliases */
+       ap = lookupalias(command, 0);
+       if (ap != NULL) {
+               if (!describe_command_verbose) {
+                       out1str("alias ");
+                       printalias(ap);
+                       return 0;
+               }
+               out1fmt(" is an alias for %s", ap->val);
+               goto out;
+       }
+#endif
+       /* Then check if it is a tracked alias */
+       cmdp = cmdlookup(command, 0);
+       if (cmdp != 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 < 0) {
+                       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 UNUSED_PARAM, char **argv)
+{
+       int i = 1;
+       int err = 0;
+       int verbose = 1;
+
+       /* type -p ... ? (we don't bother checking for 'p') */
+       if (argv[1] && argv[1][0] == '-') {
+               i++;
+               verbose = 0;
+       }
+       while (argv[i]) {
+               err |= describe_command(argv[i++], verbose);
+       }
+       return err;
+}
+
+#if ENABLE_ASH_CMDCMD
+static int
+commandcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
+{
+       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
+       /* Mimic bash: just "command -v" doesn't complain, it's a nop */
+       if (verify && (*argptr != NULL)) {
+               return describe_command(*argptr, verify - VERIFY_BRIEF);
+       }
+
+       return 0;
+}
+#endif
+
+
+/* ============ eval.c */
+
+static int funcblocksize;       /* size of structures in function */
+static int funcstringsize;      /* size of strings in node */
+static void *funcblock;         /* block to allocate function from */
+static char *funcstring;        /* block to allocate strings from */
+
+/* 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 const short nodesize[N_NUMBER] = {
+       [NCMD     ] = SHELL_ALIGN(sizeof(struct ncmd)),
+       [NPIPE    ] = SHELL_ALIGN(sizeof(struct npipe)),
+       [NREDIR   ] = SHELL_ALIGN(sizeof(struct nredir)),
+       [NBACKGND ] = SHELL_ALIGN(sizeof(struct nredir)),
+       [NSUBSHELL] = SHELL_ALIGN(sizeof(struct nredir)),
+       [NAND     ] = SHELL_ALIGN(sizeof(struct nbinary)),
+       [NOR      ] = SHELL_ALIGN(sizeof(struct nbinary)),
+       [NSEMI    ] = SHELL_ALIGN(sizeof(struct nbinary)),
+       [NIF      ] = SHELL_ALIGN(sizeof(struct nif)),
+       [NWHILE   ] = SHELL_ALIGN(sizeof(struct nbinary)),
+       [NUNTIL   ] = SHELL_ALIGN(sizeof(struct nbinary)),
+       [NFOR     ] = SHELL_ALIGN(sizeof(struct nfor)),
+       [NCASE    ] = SHELL_ALIGN(sizeof(struct ncase)),
+       [NCLIST   ] = SHELL_ALIGN(sizeof(struct nclist)),
+       [NDEFUN   ] = SHELL_ALIGN(sizeof(struct narg)),
+       [NARG     ] = SHELL_ALIGN(sizeof(struct narg)),
+       [NTO      ] = SHELL_ALIGN(sizeof(struct nfile)),
+#if ENABLE_ASH_BASH_COMPAT
+       [NTO2     ] = SHELL_ALIGN(sizeof(struct nfile)),
+#endif
+       [NCLOBBER ] = SHELL_ALIGN(sizeof(struct nfile)),
+       [NFROM    ] = SHELL_ALIGN(sizeof(struct nfile)),
+       [NFROMTO  ] = SHELL_ALIGN(sizeof(struct nfile)),
+       [NAPPEND  ] = SHELL_ALIGN(sizeof(struct nfile)),
+       [NTOFD    ] = SHELL_ALIGN(sizeof(struct ndup)),
+       [NFROMFD  ] = SHELL_ALIGN(sizeof(struct ndup)),
+       [NHERE    ] = SHELL_ALIGN(sizeof(struct nhere)),
+       [NXHERE   ] = SHELL_ALIGN(sizeof(struct nhere)),
+       [NNOT     ] = SHELL_ALIGN(sizeof(struct nnot)),
+};
+
+static void calcsize(union node *n);
+
+static void
+sizenodelist(struct nodelist *lp)
+{
+       while (lp) {
+               funcblocksize += SHELL_ALIGN(sizeof(struct nodelist));
+               calcsize(lp->n);
+               lp = lp->next;
+       }
+}
+
+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:
+#if ENABLE_ASH_BASH_COMPAT
+       case NTO2:
+#endif
+       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 char *
+nodeckstrdup(char *s)
+{
+       char *rtn = funcstring;
+
+       strcpy(funcstring, s);
+       funcstring += strlen(s) + 1;
+       return rtn;
+}
+
+static union node *copynode(union node *);
+
+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 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.pipe_backgnd = n->npipe.pipe_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 = nodeckstrdup(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 = nodeckstrdup(n->narg.text);
+               new->narg.next = copynode(n->narg.next);
+               break;
+       case NTO:
+#if ENABLE_ASH_BASH_COMPAT
+       case NTO2:
+#endif
+       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;
+}
+
+/*
+ * 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;
+
+       INT_OFF;
+       entry.cmdtype = CMDFUNCTION;
+       entry.u.func = copyfunc(func);
+       addcmdentry(name, &entry);
+       INT_ON;
+}
+
+/* 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)
+static smallint evalskip;       /* set to SKIPxxx if we are skipping commands */
+static int skipcount;           /* number of levels to skip */
+static int funcnest;            /* depth of function calls */
+static int loopnest;            /* current loop nesting level */
+
+/* Forward decl way out to parsing code - dotrap needs it */
+static int evalstring(char *s, int mask);
+
+/* Called to execute a trap.
+ * Single callsite - at the end of evaltree().
+ * If we return non-zero, exaltree raises EXEXIT exception.
+ *
+ * Perhaps we should avoid entering new trap handlers
+ * while we are executing a trap handler. [is it a TODO?]
+ */
+static int
+dotrap(void)
+{
+       uint8_t *g;
+       int sig;
+       uint8_t savestatus;
+
+       savestatus = exitstatus;
+       pendingsig = 0;
+       xbarrier();
+
+       TRACE(("dotrap entered\n"));
+       for (sig = 1, g = gotsig; sig < NSIG; sig++, g++) {
+               int want_exexit;
+               char *t;
+
+               if (*g == 0)
+                       continue;
+               t = trap[sig];
+               /* non-trapped SIGINT is handled separately by raise_interrupt,
+                * don't upset it by resetting gotsig[SIGINT-1] */
+               if (sig == SIGINT && !t)
+                       continue;
+
+               TRACE(("sig %d is active, will run handler '%s'\n", sig, t));
+               *g = 0;
+               if (!t)
+                       continue;
+               want_exexit = evalstring(t, SKIPEVAL);
+               exitstatus = savestatus;
+               if (want_exexit) {
+                       TRACE(("dotrap returns %d\n", want_exexit));
+                       return want_exexit;
+               }
+       }
+
+       TRACE(("dotrap returns 0\n"));
+       return 0;
+}
+
+/* forward declarations - evaluation is fairly recursive business... */
+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 void prehash(union node *);
+
+/*
+ * Evaluate a parse tree.  The value is left in the global variable
+ * exitstatus.
+ */
+static void
+evaltree(union node *n, int flags)
+{
+       struct jmploc *volatile savehandler = exception_handler;
+       struct jmploc jmploc;
+       int checkexit = 0;
+       void (*evalfn)(union node *, int);
+       int status;
+       int int_level;
+
+       SAVE_INT(int_level);
+
+       if (n == NULL) {
+               TRACE(("evaltree(NULL) called\n"));
+               goto out1;
+       }
+       TRACE(("evaltree(%p: %d, %d) called\n", n, n->type, flags));
+
+       exception_handler = &jmploc;
+       {
+               int err = setjmp(jmploc.loc);
+               if (err) {
+                       /* if it was a signal, check for trap handlers */
+                       if (exception_type == EXSIG) {
+                               TRACE(("exception %d (EXSIG) in evaltree, err=%d\n",
+                                               exception_type, err));
+                               goto out;
+                       }
+                       /* continue on the way out */
+                       TRACE(("exception %d in evaltree, propagating err=%d\n",
+                                       exception_type, err));
+                       exception_handler = savehandler;
+                       longjmp(exception_handler->loc, err);
+               }
+       }
+
+       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(/*drop:*/ 0, /*restore:*/ 0 /* not sure */);
+               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
+               unsigned is_or = n->type - NAND;
+               evaltree(
+                       n->nbinary.ch1,
+                       (flags | ((is_or >> 1) - 1)) & EV_TESTED
+               );
+               if (!exitstatus == is_or)
+                       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;
+               }
+               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:
+       exception_handler = savehandler;
+ out1:
+       if (checkexit & exitstatus)
+               evalskip |= SKIPEVAL;
+       else if (pendingsig && dotrap())
+               goto exexit;
+
+       if (flags & EV_EXIT) {
+ exexit:
+               raise_exception(EXEXIT);
+       }
+
+       RESTORE_INT(int_level);
+       TRACE(("leaving evaltree (no interrupts)\n"));
+}
+
+#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.list = NULL;
+       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.list = NULL;
+       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;
+       INT_OFF;
+       jp = makejob(/*n,*/ 1);
+       if (forkshell(jp, n, backgnd) == 0) {
+               INT_ON;
+               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;
+       INT_ON;
+}
+
+/*
+ * Compute the names of the files in a redirection list.
+ */
+static void fixredir(union node *, const char *, int);
+static void
+expredir(union node *n)
+{
+       union node *redir;
+
+       for (redir = n; redir; redir = redir->nfile.next) {
+               struct arglist fn;
+
+               fn.list = NULL;
+               fn.lastp = &fn.list;
+               switch (redir->type) {
+               case NFROMTO:
+               case NFROM:
+               case NTO:
+#if ENABLE_ASH_BASH_COMPAT
+               case NTO2:
+#endif
+               case NCLOBBER:
+               case NAPPEND:
+                       expandarg(redir->nfile.fname, &fn, EXP_TILDE | EXP_REDIR);
+#if ENABLE_ASH_BASH_COMPAT
+ store_expfname:
+#endif
+                       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)
+                                       ash_msg_and_raise_error("redir error");
+#if ENABLE_ASH_BASH_COMPAT
+//FIXME: we used expandarg with different args!
+                               if (!isdigit_str9(fn.list->text)) {
+                                       /* >&file, not >&fd */
+                                       if (redir->nfile.fd != 1) /* 123>&file - BAD */
+                                               ash_msg_and_raise_error("redir error");
+                                       redir->type = NTO2;
+                                       goto store_expfname;
+                               }
+#endif
+                               fixredir(redir, fn.list->text, 1);
+                       }
+                       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;
+       INT_OFF;
+       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);
+                               ash_msg_and_raise_error("pipe call failed");
+                       }
+               }
+               if (forkshell(jp, lp->n, n->npipe.pipe_backgnd) == 0) {
+                       INT_ON;
+                       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];
+               /* Don't want to trigger debugging */
+               if (pip[1] != -1)
+                       close(pip[1]);
+       }
+       if (n->npipe.pipe_backgnd == 0) {
+               exitstatus = waitforjob(jp);
+               TRACE(("evalpipe:  job done exit status %d\n", exitstatus));
+       }
+       INT_ON;
+}
+
+/*
+ * Controls whether the shell is interactive or not.
+ */
+static void
+setinteractive(int on)
+{
+       static smallint is_interactive;
+
+       if (++on == is_interactive)
+               return;
+       is_interactive = on;
+       setsignal(SIGINT);
+       setsignal(SIGQUIT);
+       setsignal(SIGTERM);
+#if !ENABLE_FEATURE_SH_EXTRA_QUIET
+       if (is_interactive > 1) {
+               /* Looks like they want an interactive shell */
+               static smallint did_banner;
+
+               if (!did_banner) {
+                       out1fmt(
+                               "\n\n"
+                               "%s built-in shell (ash)\n"
+                               "Enter 'help' for a list of built-in commands."
+                               "\n\n",
+                               bb_banner);
+                       did_banner = 1;
+               }
+       }
+#endif
+}
+
+static void
+optschanged(void)
+{
+#if DEBUG
+       opentrace();
+#endif
+       setinteractive(iflag);
+       setjobctl(mflag);
+#if ENABLE_FEATURE_EDITING_VI
+       if (viflag)
+               line_input_state->flags |= VI_MODE;
+       else
+               line_input_state->flags &= ~VI_MODE;
+#else
+       viflag = 0; /* forcibly keep the option off */
+#endif
+}
+
+static struct localvar *localvars;
+
+/*
+ * 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));
+                       free((char*)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)
+                               free((char*)vp->text);
+                       vp->flags = lvp->flags;
+                       vp->text = lvp->text;
+               }
+               free(lvp);
+       }
+}
+
+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;
+       e = setjmp(jmploc.loc);
+       if (e) {
+               goto funcdone;
+       }
+       INT_OFF;
+       savehandler = exception_handler;
+       exception_handler = &jmploc;
+       localvars = NULL;
+       shellparam.malloced = 0;
+       func->count++;
+       funcnest++;
+       INT_ON;
+       shellparam.nparam = argc - 1;
+       shellparam.p = argv + 1;
+#if ENABLE_ASH_GETOPTS
+       shellparam.optind = 1;
+       shellparam.optoff = -1;
+#endif
+       evaltree(&func->n, flags & EV_TESTED);
+ funcdone:
+       INT_OFF;
+       funcnest--;
+       freefunc(func);
+       poplocalvars();
+       localvars = savelocalvars;
+       freeparam(&shellparam);
+       shellparam = saveparam;
+       exception_handler = savehandler;
+       INT_ON;
+       evalskip &= ~SKIPFUNC;
+       return e;
+}
+
+#if ENABLE_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;
+               c = *cp++;
+               if (!c)
+                       break;
+               if (c == '-' && !*cp) {
+                       argv++;
+                       break;
+               }
+               do {
+                       switch (c) {
+                       case 'p':
+                               *path = bb_default_path;
+                               break;
+                       default:
+                               /* run 'typecmd' for other options */
+                               return 0;
+                       }
+                       c = *cp++;
+               } while (c);
+       }
+       return argv;
+}
+#endif
+
+/*
+ * 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;
+
+       INT_OFF;
+       lvp = ckzalloc(sizeof(struct localvar));
+       if (LONE_DASH(name)) {
+               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;
+       INT_ON;
+}
+
+/*
+ * The "local" command.
+ */
+static int
+localcmd(int argc UNUSED_PARAM, char **argv)
+{
+       char *name;
+
+       argv = argptr;
+       while ((name = *argv++) != NULL) {
+               mklocal(name);
+       }
+       return 0;
+}
+
+static int
+falsecmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
+{
+       return 1;
+}
+
+static int
+truecmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
+{
+       return 0;
+}
+
+static int
+execcmd(int argc UNUSED_PARAM, char **argv)
+{
+       if (argv[1]) {
+               iflag = 0;              /* exit on error */
+               mflag = 0;
+               optschanged();
+               shellexec(argv + 1, pathval(), 0);
+       }
+       return 0;
+}
+
+/*
+ * The return command.
+ */
+static int
+returncmd(int argc UNUSED_PARAM, 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;
+}
+
+/* Forward declarations for builtintab[] */
+static int breakcmd(int, char **);
+static int dotcmd(int, char **);
+static int evalcmd(int, char **);
+static int exitcmd(int, char **);
+static int exportcmd(int, char **);
+#if ENABLE_ASH_GETOPTS
+static int getoptscmd(int, char **);
+#endif
+#if !ENABLE_FEATURE_SH_EXTRA_QUIET
+static int helpcmd(int, char **);
+#endif
+#if ENABLE_SH_MATH_SUPPORT
+static int letcmd(int, char **);
+#endif
+static int readcmd(int, char **);
+static int setcmd(int, char **);
+static int shiftcmd(int, char **);
+static int timescmd(int, char **);
+static int trapcmd(int, char **);
+static int umaskcmd(int, char **);
+static int unsetcmd(int, char **);
+static int ulimitcmd(int, char **);
+
+#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"
+
+/* We do not handle [[ expr ]] bashism bash-compatibly,
+ * we make it a synonym of [ expr ].
+ * Basically, word splitting and pathname expansion should NOT be performed
+ * Examples:
+ * no word splitting:     a="a b"; [[ $a = "a b" ]]; echo $? should print "0"
+ * no pathname expansion: [[ /bin/m* = "/bin/m*" ]]; echo $? should print "0"
+ * Additional operators:
+ * || and && should work as -o and -a
+ * =~ regexp match
+ * Apart from the above, [[ expr ]] should work as [ expr ]
+ */
+
+#define echocmd   echo_main
+#define printfcmd printf_main
+#define testcmd   test_main
+
+/* Keep these in proper order since it is searched via bsearch() */
+static const struct builtincmd builtintab[] = {
+       { BUILTIN_SPEC_REG      ".", dotcmd },
+       { BUILTIN_SPEC_REG      ":", truecmd },
+#if ENABLE_ASH_BUILTIN_TEST
+       { BUILTIN_REGULAR       "[", testcmd },
+#if ENABLE_ASH_BASH_COMPAT
+       { BUILTIN_REGULAR       "[[", testcmd },
+#endif
+#endif
+#if ENABLE_ASH_ALIAS
+       { BUILTIN_REG_ASSG      "alias", aliascmd },
+#endif
+#if JOBS
+       { BUILTIN_REGULAR       "bg", fg_bgcmd },
+#endif
+       { BUILTIN_SPEC_REG      "break", breakcmd },
+       { BUILTIN_REGULAR       "cd", cdcmd },
+       { BUILTIN_NOSPEC        "chdir", cdcmd },
+#if ENABLE_ASH_CMDCMD
+       { BUILTIN_REGULAR       "command", commandcmd },
+#endif
+       { BUILTIN_SPEC_REG      "continue", breakcmd },
+#if ENABLE_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", fg_bgcmd },
+#endif
+#if ENABLE_ASH_GETOPTS
+       { BUILTIN_REGULAR       "getopts", getoptscmd },
+#endif
+       { BUILTIN_NOSPEC        "hash", hashcmd },
+#if !ENABLE_FEATURE_SH_EXTRA_QUIET
+       { BUILTIN_NOSPEC        "help", helpcmd },
+#endif
+#if JOBS
+       { BUILTIN_REGULAR       "jobs", jobscmd },
+       { BUILTIN_REGULAR       "kill", killcmd },
+#endif
+#if ENABLE_SH_MATH_SUPPORT
+       { BUILTIN_NOSPEC        "let", letcmd },
+#endif
+       { BUILTIN_ASSIGN        "local", localcmd },
+#if ENABLE_ASH_BUILTIN_PRINTF
+       { BUILTIN_REGULAR       "printf", printfcmd },
+#endif
+       { 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 },
+#if ENABLE_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 },
+#if ENABLE_ASH_ALIAS
+       { BUILTIN_REGULAR       "unalias", unaliascmd },
+#endif
+       { BUILTIN_SPEC_REG      "unset", unsetcmd },
+       { BUILTIN_REGULAR       "wait", waitcmd },
+};
+
+/* Should match the above table! */
+#define COMMANDCMD (builtintab + \
+       2 + \
+       1 * ENABLE_ASH_BUILTIN_TEST + \
+       1 * ENABLE_ASH_BUILTIN_TEST * ENABLE_ASH_BASH_COMPAT + \
+       1 * ENABLE_ASH_ALIAS + \
+       1 * ENABLE_ASH_JOB_CONTROL + \
+       3)
+#define EXECCMD (builtintab + \
+       2 + \
+       1 * ENABLE_ASH_BUILTIN_TEST + \
+       1 * ENABLE_ASH_BUILTIN_TEST * ENABLE_ASH_BASH_COMPAT + \
+       1 * ENABLE_ASH_ALIAS + \
+       1 * ENABLE_ASH_JOB_CONTROL + \
+       3 + \
+       1 * ENABLE_ASH_CMDCMD + \
+       1 + \
+       ENABLE_ASH_BUILTIN_ECHO + \
+       1)
+
+/*
+ * Search the table of builtin commands.
+ */
+static struct builtincmd *
+find_builtin(const char *name)
+{
+       struct builtincmd *bp;
+
+       bp = bsearch(
+               name, builtintab, ARRAY_SIZE(builtintab), sizeof(builtintab[0]),
+               pstrcmp
+       );
+       return bp;
+}
+
+/*
+ * Execute a simple command.
+ */
+static int
+isassignment(const char *p)
+{
+       const char *q = endofname(p);
+       if (p == q)
+               return 0;
+       return *q == '=';
+}
+static int
+bltincmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
+{
+       /* Preserve exitstatus of a previous possible redirection
+        * as POSIX mandates */
+       return back_exitstatus;
+}
+static void
+evalcommand(union node *cmd, int flags)
+{
+       static const struct builtincmd null_bltin = {
+               "\0\0", bltincmd /* why three NULs? */
+       };
+       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 status;
+       char **nargv;
+       struct builtincmd *bcmd;
+       smallint cmd_is_exec;
+       smallint 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 = &null_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++;
+               fdprintf(preverrout_fd, p, expandstr(ps4val()));
+
+               sp = varlist.list;
+               for (n = 0; n < 2; n++) {
+                       while (sp) {
+                               fdprintf(preverrout_fd, p, sp->text);
+                               sp = sp->next;
+                               if (*p == '%') {
+                                       p--;
+                               }
+                       }
+                       sp = arglist.list;
+               }
+               safe_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) {
+                               flush_stderr();
+                               status = 127;
+                               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 = 1;
+#if ENABLE_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)
+                       raise_exception(EXERROR);
+ bail:
+               exitstatus = status;
+               goto out;
+       }
+
+       /* Execute the command. */
+       switch (cmdentry.cmdtype) {
+       default:
+
+#if ENABLE_FEATURE_SH_NOFORK
+/* Hmmm... shouldn't it happen somewhere in forkshell() instead?
+ * Why "fork off a child process if necessary" doesn't apply to NOFORK? */
+       {
+               /* find_command() encodes applet_no as (-2 - applet_no) */
+               int applet_no = (- cmdentry.u.index - 2);
+               if (applet_no >= 0 && APPLET_IS_NOFORK(applet_no)) {
+                       listsetvar(varlist.list, VEXPORT|VSTACK);
+                       /* run <applet>_main() */
+                       exitstatus = run_nofork_applet(applet_no, argv);
+                       break;
+               }
+       }
+#endif
+               /* Fork off a child process if necessary. */
+               if (!(flags & EV_EXIT) || trap[0]) {
+                       INT_OFF;
+                       jp = makejob(/*cmd,*/ 1);
+                       if (forkshell(jp, cmd, FORK_FG) != 0) {
+                               exitstatus = waitforjob(jp);
+                               INT_ON;
+                               TRACE(("forked child exited with %d\n", exitstatus));
+                               break;
+                       }
+                       FORCE_INT_ON;
+               }
+               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);
+               }
+               /* Tight loop with builtins only:
+                * "while kill -0 $child; do true; done"
+                * will never exit even if $child died, unless we do this
+                * to reap the zombie and make kill detect that it's gone: */
+               dowait(DOWAIT_NONBLOCK, NULL);
+
+               if (evalbltin(cmdentry.u.cmd, argc, argv)) {
+                       int exit_status;
+                       int i = exception_type;
+                       if (i == EXEXIT)
+                               goto raise;
+                       exit_status = 2;
+                       if (i == EXINT)
+                               exit_status = 128 + SIGINT;
+                       if (i == EXSIG)
+                               exit_status = 128 + pendingsig;
+                       exitstatus = exit_status;
+                       if (i == EXINT || spclbltin > 0) {
+ raise:
+                               longjmp(exception_handler->loc, 1);
+                       }
+                       FORCE_INT_ON;
+               }
+               break;
+
+       case CMDFUNCTION:
+               listsetvar(varlist.list, 0);
+               /* See above for the rationale */
+               dowait(DOWAIT_NONBLOCK, NULL);
+               if (evalfun(cmdentry.u.func, argc, argv, flags))
+                       goto raise;
+               break;
+       }
+
+ out:
+       popredir(/*drop:*/ cmd_is_exec, /*restore:*/ 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;
+       i = setjmp(jmploc.loc);
+       if (i)
+               goto cmddone;
+       savehandler = exception_handler;
+       exception_handler = &jmploc;
+       commandname = argv[0];
+       argptr = argv + 1;
+       optptr = NULL;                  /* initialize nextopt */
+       exitstatus = (*cmd->builtin)(argc, argv);
+       flush_stdout_stderr();
+ cmddone:
+       exitstatus |= ferror(stdout);
+       clearerr(stdout);
+       commandname = savecmdname;
+//     exsig = 0;
+       exception_handler = savehandler;
+
+       return i;
+}
+
+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 && 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.
+ */
+
+/*
+ * 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 UNUSED_PARAM, char **argv)
+{
+       int n = argv[1] ? number(argv[1]) : 1;
+
+       if (n <= 0)
+               ash_msg_and_raise_error(illnum, argv[1]);
+       if (n > loopnest)
+               n = loopnest;
+       if (n > 0) {
+               evalskip = (**argv == 'c') ? SKIPCONT : SKIPBREAK;
+               skipcount = n;
+       }
+       return 0;
+}
+
+
+/* ============ input.c
+ *
+ * This implements the input routines used by the parser.
+ */
+
+enum {
+       INPUT_PUSH_FILE = 1,
+       INPUT_NOFILE_OK = 2,
+};
+
+static smallint checkkwd;
+/* values of checkkwd variable */
+#define CHKALIAS        0x1
+#define CHKKWD          0x2
+#define CHKNL           0x4
+
+/*
+ * Push a string back onto the input at this current parsefile level.
+ * We handle aliases this way.
+ */
+#if !ENABLE_ASH_ALIAS
+#define pushstring(s, ap) pushstring(s)
+#endif
+static void
+pushstring(char *s, struct alias *ap)
+{
+       struct strpush *sp;
+       int len;
+
+       len = strlen(s);
+       INT_OFF;
+       if (g_parsefile->strpush) {
+               sp = ckzalloc(sizeof(*sp));
+               sp->prev = g_parsefile->strpush;
+       } else {
+               sp = &(g_parsefile->basestrpush);
+       }
+       g_parsefile->strpush = sp;
+       sp->prev_string = g_parsefile->next_to_pgetc;
+       sp->prev_left_in_line = g_parsefile->left_in_line;
+#if ENABLE_ASH_ALIAS
+       sp->ap = ap;
+       if (ap) {
+               ap->flag |= ALIASINUSE;
+               sp->string = s;
+       }
+#endif
+       g_parsefile->next_to_pgetc = s;
+       g_parsefile->left_in_line = len;
+       INT_ON;
+}
+
+static void
+popstring(void)
+{
+       struct strpush *sp = g_parsefile->strpush;
+
+       INT_OFF;
+#if ENABLE_ASH_ALIAS
+       if (sp->ap) {
+               if (g_parsefile->next_to_pgetc[-1] == ' '
+                || g_parsefile->next_to_pgetc[-1] == '\t'
+               ) {
+                       checkkwd |= CHKALIAS;
+               }
+               if (sp->string != sp->ap->val) {
+                       free(sp->string);
+               }
+               sp->ap->flag &= ~ALIASINUSE;
+               if (sp->ap->flag & ALIASDEAD) {
+                       unalias(sp->ap->name);
+               }
+       }
+#endif
+       g_parsefile->next_to_pgetc = sp->prev_string;
+       g_parsefile->left_in_line = sp->prev_left_in_line;
+       g_parsefile->strpush = sp->prev;
+       if (sp != &(g_parsefile->basestrpush))
+               free(sp);
+       INT_ON;
+}
+
+//FIXME: BASH_COMPAT with "...&" does TWO pungetc():
+//it peeks whether it is &>, and then pushes back both chars.
+//This function needs to save last *next_to_pgetc to buf[0]
+//to make two pungetc() reliable. Currently,
+// pgetc (out of buf: does preadfd), pgetc, pungetc, pungetc won't work...
+static int
+preadfd(void)
+{
+       int nr;
+       char *buf = g_parsefile->buf;
+
+       g_parsefile->next_to_pgetc = buf;
+#if ENABLE_FEATURE_EDITING
+ retry:
+       if (!iflag || g_parsefile->fd != STDIN_FILENO)
+               nr = nonblock_safe_read(g_parsefile->fd, buf, BUFSIZ - 1);
+       else {
+#if ENABLE_FEATURE_TAB_COMPLETION
+               line_input_state->path_lookup = pathval();
+#endif
+               nr = read_line_input(cmdedit_prompt, buf, BUFSIZ, line_input_state);
+               if (nr == 0) {
+                       /* Ctrl+C pressed */
+                       if (trap[SIGINT]) {
+                               buf[0] = '\n';
+                               buf[1] = '\0';
+                               raise(SIGINT);
+                               return 1;
+                       }
+                       goto retry;
+               }
+               if (nr < 0 && errno == 0) {
+                       /* Ctrl+D pressed */
+                       nr = 0;
+               }
+       }
+#else
+       nr = nonblock_safe_read(g_parsefile->fd, buf, BUFSIZ - 1);
+#endif
+
+#if 0
+/* nonblock_safe_read() handles this problem */
+       if (nr < 0) {
+               if (parsefile->fd == 0 && errno == EWOULDBLOCK) {
+                       int flags = fcntl(0, F_GETFL);
+                       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;
+                               }
+                       }
+               }
+       }
+#endif
+       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 (g_parsefile->left_in_line < -BIGNUM)
+ *    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.
+ */
+//#define pgetc_debug(...) bb_error_msg(__VA_ARGS__)
+#define pgetc_debug(...) ((void)0)
+/*
+ * NB: due to SIT(c) internals (syntax_index_table[] vector),
+ * pgetc() and related functions must return chars SIGN-EXTENDED into ints,
+ * not zero-extended. Seems fragile to me. Affects only !USE_SIT_FUNCTION case,
+ * so we can fix it by ditching !USE_SIT_FUNCTION if Unicode requires that.
+ */
+static int
+preadbuffer(void)
+{
+       char *q;
+       int more;
+
+       while (g_parsefile->strpush) {
+#if ENABLE_ASH_ALIAS
+               if (g_parsefile->left_in_line == -1
+                && g_parsefile->strpush->ap
+                && g_parsefile->next_to_pgetc[-1] != ' '
+                && g_parsefile->next_to_pgetc[-1] != '\t'
+               ) {
+                       pgetc_debug("preadbuffer PEOA");
+                       return PEOA;
+               }
+#endif
+               popstring();
+               /* try "pgetc" now: */
+               pgetc_debug("preadbuffer internal pgetc at %d:%p'%s'",
+                               g_parsefile->left_in_line,
+                               g_parsefile->next_to_pgetc,
+                               g_parsefile->next_to_pgetc);
+               if (--g_parsefile->left_in_line >= 0)
+                       return (unsigned char)(*g_parsefile->next_to_pgetc++);
+       }
+       /* on both branches above g_parsefile->left_in_line < 0.
+        * "pgetc" needs refilling.
+        */
+
+       /* -90 is our -BIGNUM. Below we use -99 to mark "EOF on read",
+        * pungetc() may increment it a few times.
+        * Assuming it won't increment it to less than -90.
+        */
+       if (g_parsefile->left_in_line < -90 || g_parsefile->buf == NULL) {
+               pgetc_debug("preadbuffer PEOF1");
+               /* even in failure keep left_in_line and next_to_pgetc
+                * in lock step, for correct multi-layer pungetc.
+                * left_in_line was decremented before preadbuffer(),
+                * must inc next_to_pgetc: */
+               g_parsefile->next_to_pgetc++;
+               return PEOF;
+       }
+
+       more = g_parsefile->left_in_buffer;
+       if (more <= 0) {
+               flush_stdout_stderr();
+ again:
+               more = preadfd();
+               if (more <= 0) {
+                       /* don't try reading again */
+                       g_parsefile->left_in_line = -99;
+                       pgetc_debug("preadbuffer PEOF2");
+                       g_parsefile->next_to_pgetc++;
+                       return PEOF;
+               }
+       }
+
+       /* Find out where's the end of line.
+        * Set g_parsefile->left_in_line
+        * and g_parsefile->left_in_buffer acordingly.
+        * NUL chars are deleted.
+        */
+       q = g_parsefile->next_to_pgetc;
+       for (;;) {
+               char c;
+
+               more--;
+
+               c = *q;
+               if (c == '\0') {
+                       memmove(q, q + 1, more);
+               } else {
+                       q++;
+                       if (c == '\n') {
+                               g_parsefile->left_in_line = q - g_parsefile->next_to_pgetc - 1;
+                               break;
+                       }
+               }
+
+               if (more <= 0) {
+                       g_parsefile->left_in_line = q - g_parsefile->next_to_pgetc - 1;
+                       if (g_parsefile->left_in_line < 0)
+                               goto again;
+                       break;
+               }
+       }
+       g_parsefile->left_in_buffer = more;
+
+       if (vflag) {
+               char save = *q;
+               *q = '\0';
+               out2str(g_parsefile->next_to_pgetc);
+               *q = save;
+       }
+
+       pgetc_debug("preadbuffer at %d:%p'%s'",
+                       g_parsefile->left_in_line,
+                       g_parsefile->next_to_pgetc,
+                       g_parsefile->next_to_pgetc);
+       return signed_char2int(*g_parsefile->next_to_pgetc++);
+}
+
+#define pgetc_as_macro() \
+       (--g_parsefile->left_in_line >= 0 \
+       ? signed_char2int(*g_parsefile->next_to_pgetc++) \
+       : preadbuffer() \
+       )
+
+static int
+pgetc(void)
+{
+       pgetc_debug("pgetc_fast at %d:%p'%s'",
+                       g_parsefile->left_in_line,
+                       g_parsefile->next_to_pgetc,
+                       g_parsefile->next_to_pgetc);
+       return pgetc_as_macro();
+}
+
+#if ENABLE_ASH_OPTIMIZE_FOR_SIZE
+#define pgetc_fast() pgetc()
+#else
+#define pgetc_fast() pgetc_as_macro()
+#endif
+
+/*
+ * Same as pgetc(), but ignores PEOA.
+ */
+#if ENABLE_ASH_ALIAS
+static int
+pgetc2(void)
+{
+       int c;
+       do {
+               pgetc_debug("pgetc_fast at %d:%p'%s'",
+                               g_parsefile->left_in_line,
+                               g_parsefile->next_to_pgetc,
+                               g_parsefile->next_to_pgetc);
+               c = pgetc_fast();
+       } while (c == PEOA);
+       return c;
+}
+#else
+#define pgetc2() pgetc()
+#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;
+}
+
+/*
+ * Undo the last call to pgetc.  Only one character may be pushed back.
+ * PEOF may be pushed back.
+ */
+static void
+pungetc(void)
+{
+       g_parsefile->left_in_line++;
+       g_parsefile->next_to_pgetc--;
+       pgetc_debug("pushed back to %d:%p'%s'",
+                       g_parsefile->left_in_line,
+                       g_parsefile->next_to_pgetc,
+                       g_parsefile->next_to_pgetc);
+}
+
+/*
+ * 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;
+
+       pf = ckzalloc(sizeof(*pf));
+       pf->prev = g_parsefile;
+       pf->fd = -1;
+       /*pf->strpush = NULL; - ckzalloc did it */
+       /*pf->basestrpush.prev = NULL;*/
+       g_parsefile = pf;
+}
+
+static void
+popfile(void)
+{
+       struct parsefile *pf = g_parsefile;
+
+       INT_OFF;
+       if (pf->fd >= 0)
+               close(pf->fd);
+       free(pf->buf);
+       while (pf->strpush)
+               popstring();
+       g_parsefile = pf->prev;
+       free(pf);
+       INT_ON;
+}
+
+/*
+ * Return to top level.
+ */
+static void
+popallfiles(void)
+{
+       while (g_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 (g_parsefile->fd > 0) {
+               close(g_parsefile->fd);
+               g_parsefile->fd = 0;
+       }
+}
+
+/*
+ * Like setinputfile, but takes an open file descriptor.  Call this with
+ * interrupts off.
+ */
+static void
+setinputfd(int fd, int push)
+{
+       close_on_exec_on(fd);
+       if (push) {
+               pushfile();
+               g_parsefile->buf = NULL;
+       }
+       g_parsefile->fd = fd;
+       if (g_parsefile->buf == NULL)
+               g_parsefile->buf = ckmalloc(IBUFSIZ);
+       g_parsefile->left_in_buffer = 0;
+       g_parsefile->left_in_line = 0;
+       g_parsefile->linno = 1;
+}
+
+/*
+ * 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;
+
+       INT_OFF;
+       fd = open(fname, O_RDONLY);
+       if (fd < 0) {
+               if (flags & INPUT_NOFILE_OK)
+                       goto out;
+               ash_msg_and_raise_error("can't open '%s'", fname);
+       }
+       if (fd < 10) {
+               fd2 = copyfd(fd, 10);
+               close(fd);
+               if (fd2 < 0)
+                       ash_msg_and_raise_error("out of file descriptors");
+               fd = fd2;
+       }
+       setinputfd(fd, flags & INPUT_PUSH_FILE);
+ out:
+       INT_ON;
+       return fd;
+}
+
+/*
+ * Like setinputfile, but takes input from a string.
+ */
+static void
+setinputstring(char *string)
+{
+       INT_OFF;
+       pushfile();
+       g_parsefile->next_to_pgetc = string;
+       g_parsefile->left_in_line = strlen(string);
+       g_parsefile->buf = NULL;
+       g_parsefile->linno = 1;
+       INT_ON;
+}
+
+
+/* ============ mail.c
+ *
+ * Routines to check for mail.
+ */
+
+#if ENABLE_ASH_MAIL
+
+#define MAXMBOXES 10
+
+/* times of mailboxes */
+static time_t mailtime[MAXMBOXES];
+/* Set if MAIL or MAILPATH is changed. */
+static smallint 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++)
+                       continue;
+#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 UNUSED_PARAM)
+{
+       mail_var_path_changed = 1;
+}
+
+#endif /* ASH_MAIL */
+
+
+/* ============ ??? */
+
+/*
+ * Set the shell parameters.
+ */
+static void
+setparam(char **argv)
+{
+       char **newparam;
+       char **ap;
+       int nparam;
+
+       for (nparam = 0; argv[nparam]; nparam++)
+               continue;
+       ap = newparam = ckmalloc((nparam + 1) * sizeof(*ap));
+       while (*argv) {
+               *ap++ = ckstrdup(*argv++);
+       }
+       *ap = NULL;
+       freeparam(&shellparam);
+       shellparam.malloced = 1;
+       shellparam.nparam = nparam;
+       shellparam.p = newparam;
+#if ENABLE_ASH_GETOPTS
+       shellparam.optind = 1;
+       shellparam.optoff = -1;
+#endif
+}
+
+/*
+ * Process shell options.  The global variable argptr contains a pointer
+ * to the argument list; we advance it past the options.
+ *
+ * SUSv3 section 2.8.1 "Consequences of Shell Errors" says:
+ * For a non-interactive shell, an error condition encountered
+ * by a special built-in ... shall cause the shell to write a diagnostic message
+ * to standard error and exit as shown in the following table:
+ * Error                                           Special Built-In
+ * ...
+ * Utility syntax error (option or operand error)  Shall exit
+ * ...
+ * However, in bug 1142 (http://busybox.net/bugs/view.php?id=1142)
+ * we see that bash does not do that (set "finishes" with error code 1 instead,
+ * and shell continues), and people rely on this behavior!
+ * Testcase:
+ * set -o barfoo 2>/dev/null
+ * echo $?
+ *
+ * Oh well. Let's mimic that.
+ */
+static int
+plus_minus_o(char *name, int val)
+{
+       int i;
+
+       if (name) {
+               for (i = 0; i < NOPTS; i++) {
+                       if (strcmp(name, optnames(i)) == 0) {
+                               optlist[i] = val;
+                               return 0;
+                       }
+               }
+               ash_msg("illegal option %co %s", val ? '-' : '+', name);
+               return 1;
+       }
+       for (i = 0; i < NOPTS; i++) {
+               if (val) {
+                       out1fmt("%-16s%s\n", optnames(i), optlist[i] ? "on" : "off");
+               } else {
+                       out1fmt("set %co %s\n", optlist[i] ? '-' : '+', optnames(i));
+               }
+       }
+       return 0;
+}
+static void
+setoption(int flag, int val)
+{
+       int i;
+
+       for (i = 0; i < NOPTS; i++) {
+               if (optletters(i) == flag) {
+                       optlist[i] = val;
+                       return;
+               }
+       }
+       ash_msg_and_raise_error("illegal option %c%c", val ? '-' : '+', flag);
+       /* NOTREACHED */
+}
+static int
+options(int cmdline)
+{
+       char *p;
+       int val;
+       int c;
+
+       if (cmdline)
+               minusc = NULL;
+       while ((p = *argptr) != NULL) {
+               c = *p++;
+               if (c != '-' && c != '+')
+                       break;
+               argptr++;
+               val = 0; /* val = 0 if c == '+' */
+               if (c == '-') {
+                       val = 1;
+                       if (p[0] == '\0' || LONE_DASH(p)) {
+                               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 */
+                       }
+               }
+               /* first char was + or - */
+               while ((c = *p++) != '\0') {
+                       /* bash 3.2 indeed handles -c CMD and +c CMD the same */
+                       if (c == 'c' && cmdline) {
+                               minusc = p;     /* command is after shell args */
+                       } else if (c == 'o') {
+                               if (plus_minus_o(*argptr, val)) {
+                                       /* it already printed err message */
+                                       return 1; /* error */
+                               }
+                               if (*argptr)
+                                       argptr++;
+                       } else if (cmdline && (c == 'l')) { /* -l or +l == --login */
+                               isloginsh = 1;
+                       /* bash does not accept +-login, we also won't */
+                       } else if (cmdline && val && (c == '-')) { /* long options */
+                               if (strcmp(p, "login") == 0)
+                                       isloginsh = 1;
+                               break;
+                       } else {
+                               setoption(c, val);
+                       }
+               }
+       }
+       return 0;
+}
+
+/*
+ * The shift builtin command.
+ */
+static int
+shiftcmd(int argc UNUSED_PARAM, char **argv)
+{
+       int n;
+       char **ap1, **ap2;
+
+       n = 1;
+       if (argv[1])
+               n = number(argv[1]);
+       if (n > shellparam.nparam)
+               n = 0; /* bash compat, was = shellparam.nparam; */
+       INT_OFF;
+       shellparam.nparam -= n;
+       for (ap1 = shellparam.p; --n >= 0; ap1++) {
+               if (shellparam.malloced)
+                       free(*ap1);
+       }
+       ap2 = shellparam.p;
+       while ((*ap2++ = *ap1++) != NULL)
+               continue;
+#if ENABLE_ASH_GETOPTS
+       shellparam.optind = 1;
+       shellparam.optoff = -1;
+#endif
+       INT_ON;
+       return 0;
+}
+
+/*
+ * 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 ? " " : 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 set command builtin.
+ */
+static int
+setcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
+{
+       int retval;
+
+       if (!argv[1])
+               return showvars(nullstr, 0, VUNSET);
+       INT_OFF;
+       retval = 1;
+       if (!options(0)) { /* if no parse error... */
+               retval = 0;
+               optschanged();
+               if (*argptr != NULL) {
+                       setparam(argptr);
+               }
+       }
+       INT_ON;
+       return retval;
+}
+
+#if ENABLE_ASH_RANDOM_SUPPORT
+static void
+change_random(const char *value)
+{
+       /* Galois LFSR parameter */
+       /* Taps at 32 31 29 1: */
+       enum { MASK = 0x8000000b };
+       /* Another example - taps at 32 31 30 10: */
+       /* MASK = 0x00400007 */
+
+       if (value == NULL) {
+               /* "get", generate */
+               uint32_t t;
+
+               /* LCG has period of 2^32 and alternating lowest bit */
+               random_LCG = 1664525 * random_LCG + 1013904223;
+               /* Galois LFSR has period of 2^32-1 = 3 * 5 * 17 * 257 * 65537 */
+               t = (random_galois_LFSR << 1);
+               if (random_galois_LFSR < 0) /* if we just shifted 1 out of msb... */
+                       t ^= MASK;
+               random_galois_LFSR = t;
+               /* Both are weak, combining them gives better randomness
+                * and ~2^64 period. & 0x7fff is probably bash compat
+                * for $RANDOM range. Combining with subtraction is
+                * just for fun. + and ^ would work equally well. */
+               t = (t - random_LCG) & 0x7fff;
+               /* set without recursion */
+               setvar(vrandom.text, utoa(t), VNOFUNC);
+               vrandom.flags &= ~VNOFUNC;
+       } else {
+               /* set/reset */
+               random_galois_LFSR = random_LCG = strtoul(value, (char **)NULL, 10);
+       }
+}
+#endif
+
+#if ENABLE_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 || (int)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 (LONE_DASH(p))        /* 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);
+                               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);
+                               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;
+               flush_stdout_stderr();
+               raise_exception(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.
+ */
+static int
+getoptscmd(int argc, char **argv)
+{
+       char **optbase;
+
+       if (argc < 3)
+               ash_msg_and_raise_error("usage: getopts optstring var [arg]");
+       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 /* ASH_GETOPTS */
+
+
+/* ============ Shell parser */
+
+struct heredoc {
+       struct heredoc *next;   /* next here document in list */
+       union node *here;       /* redirection node */
+       char *eofmark;          /* string indicating end of input */
+       smallint striptabs;     /* if set, strip leading tabs */
+};
+
+static smallint tokpushback;           /* last token pushed back */
+static smallint parsebackquote;        /* nonzero if we are inside backquotes */
+static smallint quoteflag;             /* set if (part of) last token was quoted */
+static token_id_t lasttoken;           /* last token read (integer id Txxx) */
+static struct heredoc *heredoclist;    /* list of here documents to read */
+static char *wordtext;                 /* text of last word returned by readtoken */
+static struct nodelist *backquotelist;
+static union node *redirnode;
+static struct heredoc *heredoc;
+/*
+ * 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.
+ */
+#define NEOF ((union node *)&tokpushback)
+
+/*
+ * 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 raise_error_unexpected_syntax(int) NORETURN;
+static void
+raise_error_unexpected_syntax(int token)
+{
+       char msg[64];
+       int l;
+
+       l = sprintf(msg, "unexpected %s", tokname(lasttoken));
+       if (token >= 0)
+               sprintf(msg + l, " (expecting %s)", tokname(token));
+       raise_error_syntax(msg);
+       /* NOTREACHED */
+}
+
+#define EOFMARKLEN 79
+
+/* parsing is heavily cross-recursive, need these forward decls */
+static union node *andor(void);
+static union node *pipeline(void);
+static union node *parse_command(void);
+static void parseheredoc(void);
+static char peektoken(void);
+static int readtoken(void);
+
+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.pipe_backgnd = 1;
+                       } else {
+                               if (n2->type != NREDIR) {
+                                       n3 = stzalloc(sizeof(struct nredir));
+                                       n3->nredir.n = n2;
+                                       /*n3->nredir.redirect = NULL; - stzalloc did it */
+                                       n2 = n3;
+                               }
+                               n2->type = NBACKGND;
+                       }
+               }
+               if (n1 == NULL) {
+                       n1 = n2;
+               } else {
+                       n3 = stzalloc(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 = 1;
+                       }
+                       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)
+                               raise_error_unexpected_syntax(-1);
+                       tokpushback = 1;
+                       return n1;
+               }
+       }
+}
+
+static union node *
+andor(void)
+{
+       union node *n1, *n2, *n3;
+       int t;
+
+       n1 = pipeline();
+       for (;;) {
+               t = readtoken();
+               if (t == TAND) {
+                       t = NAND;
+               } else if (t == TOR) {
+                       t = NOR;
+               } else {
+                       tokpushback = 1;
+                       return n1;
+               }
+               checkkwd = CHKNL | CHKKWD | CHKALIAS;
+               n2 = pipeline();
+               n3 = stzalloc(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 = 1;
+       n1 = parse_command();
+       if (readtoken() == TPIPE) {
+               pipenode = stzalloc(sizeof(struct npipe));
+               pipenode->type = NPIPE;
+               /*pipenode->npipe.pipe_backgnd = 0; - stzalloc did it */
+               lp = stzalloc(sizeof(struct nodelist));
+               pipenode->npipe.cmdlist = lp;
+               lp->n = n1;
+               do {
+                       prev = lp;
+                       lp = stzalloc(sizeof(struct nodelist));
+                       checkkwd = CHKNL | CHKKWD | CHKALIAS;
+                       lp->n = parse_command();
+                       prev->next = lp;
+               } while (readtoken() == TPIPE);
+               lp->next = NULL;
+               n1 = pipenode;
+       }
+       tokpushback = 1;
+       if (negate) {
+               n2 = stzalloc(sizeof(struct nnot));
+               n2->type = NNOT;
+               n2->nnot.com = n1;
+               return n2;
+       }
+       return n1;
+}
+
+static union node *
+makename(void)
+{
+       union node *n;
+
+       n = stzalloc(sizeof(struct narg));
+       n->type = NARG;
+       /*n->narg.next = NULL; - stzalloc did it */
+       n->narg.text = wordtext;
+       n->narg.backquote = backquotelist;
+       return n;
+}
+
+static void
+fixredir(union node *n, const char *text, int err)
+{
+       int fd;
+
+       TRACE(("Fix redir %s %d\n", text, err));
+       if (!err)
+               n->ndup.vname = NULL;
+
+       fd = bb_strtou(text, NULL, 10);
+       if (!errno && fd >= 0)
+               n->ndup.dupfd = fd;
+       else if (LONE_DASH(text))
+               n->ndup.dupfd = -1;
+       else {
+               if (err)
+                       raise_error_syntax("bad fd number");
+               n->ndup.vname = makename();
+       }
+}
+
+/*
+ * Returns true if the text contains nothing to expand (no dollar signs
+ * or backquotes).
+ */
+static int
+noexpand(const char *text)
+{
+       const char *p;
+       char c;
+
+       p = text;
+       while ((c = *p++) != '\0') {
+               if (c == CTLQUOTEMARK)
+                       continue;
+               if (c == CTLESC)
+                       p++;
+               else if (SIT((signed char)c, BASESYNTAX) == CCTL)
+                       return 0;
+       }
+       return 1;
+}
+
+static void
+parsefname(void)
+{
+       union node *n = redirnode;
+
+       if (readtoken() != TWORD)
+               raise_error_unexpected_syntax(-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)
+                       raise_error_syntax("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)
+                               continue;
+                       p->next = here;
+               }
+       } else if (n->type == NTOFD || n->type == NFROMFD) {
+               fixredir(n, wordtext, 0);
+       } else {
+               n->nfile.fname = makename();
+       }
+}
+
+static union node *
+simplecmd(void)
+{
+       union node *args, **app;
+       union node *n = NULL;
+       union node *vars, **vpp;
+       union node **rpp, *redir;
+       int savecheckkwd;
+#if ENABLE_ASH_BASH_COMPAT
+       smallint double_brackets_flag = 0;
+#endif
+
+       args = NULL;
+       app = &args;
+       vars = NULL;
+       vpp = &vars;
+       redir = NULL;
+       rpp = &redir;
+
+       savecheckkwd = CHKALIAS;
+       for (;;) {
+               int t;
+               checkkwd = savecheckkwd;
+               t = readtoken();
+               switch (t) {
+#if ENABLE_ASH_BASH_COMPAT
+               case TAND: /* "&&" */
+               case TOR: /* "||" */
+                       if (!double_brackets_flag) {
+                               tokpushback = 1;
+                               goto out;
+                       }
+                       wordtext = (char *) (t == TAND ? "-a" : "-o");
+#endif
+               case TWORD:
+                       n = stzalloc(sizeof(struct narg));
+                       n->type = NARG;
+                       /*n->narg.next = NULL; - stzalloc did it */
+                       n->narg.text = wordtext;
+#if ENABLE_ASH_BASH_COMPAT
+                       if (strcmp("[[", wordtext) == 0)
+                               double_brackets_flag = 1;
+                       else if (strcmp("]]", wordtext) == 0)
+                               double_brackets_flag = 0;
+#endif
+                       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)
+                                       raise_error_unexpected_syntax(TRP);
+                               name = n->narg.text;
+                               if (!goodname(name)
+                                || ((bcmd = find_builtin(name)) && IS_BUILTIN_SPECIAL(bcmd))
+                               ) {
+                                       raise_error_syntax("bad function name");
+                               }
+                               n->type = NDEFUN;
+                               checkkwd = CHKNL | CHKKWD | CHKALIAS;
+                               n->narg.next = parse_command();
+                               return n;
+                       }
+                       /* fall through */
+               default:
+                       tokpushback = 1;
+                       goto out;
+               }
+       }
+ out:
+       *app = NULL;
+       *vpp = NULL;
+       *rpp = NULL;
+       n = stzalloc(sizeof(struct ncmd));
+       n->type = NCMD;
+       n->ncmd.args = args;
+       n->ncmd.assign = vars;
+       n->ncmd.redirect = redir;
+       return n;
+}
+
+static union node *
+parse_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:
+               raise_error_unexpected_syntax(-1);
+               /* NOTREACHED */
+       case TIF:
+               n1 = stzalloc(sizeof(struct nif));
+               n1->type = NIF;
+               n1->nif.test = list(0);
+               if (readtoken() != TTHEN)
+                       raise_error_unexpected_syntax(TTHEN);
+               n1->nif.ifpart = list(0);
+               n2 = n1;
+               while (readtoken() == TELIF) {
+                       n2->nif.elsepart = stzalloc(sizeof(struct nif));
+                       n2 = n2->nif.elsepart;
+                       n2->type = NIF;
+                       n2->nif.test = list(0);
+                       if (readtoken() != TTHEN)
+                               raise_error_unexpected_syntax(TTHEN);
+                       n2->nif.ifpart = list(0);
+               }
+               if (lasttoken == TELSE)
+                       n2->nif.elsepart = list(0);
+               else {
+                       n2->nif.elsepart = NULL;
+                       tokpushback = 1;
+               }
+               t = TFI;
+               break;
+       case TWHILE:
+       case TUNTIL: {
+               int got;
+               n1 = stzalloc(sizeof(struct nbinary));
+               n1->type = (lasttoken == TWHILE) ? NWHILE : NUNTIL;
+               n1->nbinary.ch1 = list(0);
+               got = readtoken();
+               if (got != TDO) {
+                       TRACE(("expecting DO got %s %s\n", tokname(got),
+                                       got == TWORD ? wordtext : ""));
+                       raise_error_unexpected_syntax(TDO);
+               }
+               n1->nbinary.ch2 = list(0);
+               t = TDONE;
+               break;
+       }
+       case TFOR:
+               if (readtoken() != TWORD || quoteflag || !goodname(wordtext))
+                       raise_error_syntax("bad for loop variable");
+               n1 = stzalloc(sizeof(struct nfor));
+               n1->type = NFOR;
+               n1->nfor.var = wordtext;
+               checkkwd = CHKKWD | CHKALIAS;
+               if (readtoken() == TIN) {
+                       app = &ap;
+                       while (readtoken() == TWORD) {
+                               n2 = stzalloc(sizeof(struct narg));
+                               n2->type = NARG;
+                               /*n2->narg.next = NULL; - stzalloc did it */
+                               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)
+                               raise_error_unexpected_syntax(-1);
+               } else {
+                       n2 = stzalloc(sizeof(struct narg));
+                       n2->type = NARG;
+                       /*n2->narg.next = NULL; - stzalloc did it */
+                       n2->narg.text = (char *)dolatstr;
+                       /*n2->narg.backquote = 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 = 1;
+               }
+               checkkwd = CHKNL | CHKKWD | CHKALIAS;
+               if (readtoken() != TDO)
+                       raise_error_unexpected_syntax(TDO);
+               n1->nfor.body = list(0);
+               t = TDONE;
+               break;
+       case TCASE:
+               n1 = stzalloc(sizeof(struct ncase));
+               n1->type = NCASE;
+               if (readtoken() != TWORD)
+                       raise_error_unexpected_syntax(TWORD);
+               n1->ncase.expr = n2 = stzalloc(sizeof(struct narg));
+               n2->type = NARG;
+               /*n2->narg.next = NULL; - stzalloc did it */
+               n2->narg.text = wordtext;
+               n2->narg.backquote = backquotelist;
+               do {
+                       checkkwd = CHKKWD | CHKALIAS;
+               } while (readtoken() == TNL);
+               if (lasttoken != TIN)
+                       raise_error_unexpected_syntax(TIN);
+               cpp = &n1->ncase.cases;
+ next_case:
+               checkkwd = CHKNL | CHKKWD;
+               t = readtoken();
+               while (t != TESAC) {
+                       if (lasttoken == TLP)
+                               readtoken();
+                       *cpp = cp = stzalloc(sizeof(struct nclist));
+                       cp->type = NCLIST;
+                       app = &cp->nclist.pattern;
+                       for (;;) {
+                               *app = ap = stzalloc(sizeof(struct narg));
+                               ap->type = NARG;
+                               /*ap->narg.next = NULL; - stzalloc did it */
+                               ap->narg.text = wordtext;
+                               ap->narg.backquote = backquotelist;
+                               if (readtoken() != TPIPE)
+                                       break;
+                               app = &ap->narg.next;
+                               readtoken();
+                       }
+                       //ap->narg.next = NULL;
+                       if (lasttoken != TRP)
+                               raise_error_unexpected_syntax(TRP);
+                       cp->nclist.body = list(2);
+
+                       cpp = &cp->nclist.next;
+
+                       checkkwd = CHKNL | CHKKWD;
+                       t = readtoken();
+                       if (t != TESAC) {
+                               if (t != TENDCASE)
+                                       raise_error_unexpected_syntax(TENDCASE);
+                               goto next_case;
+                       }
+               }
+               *cpp = NULL;
+               goto redir;
+       case TLP:
+               n1 = stzalloc(sizeof(struct nredir));
+               n1->type = NSUBSHELL;
+               n1->nredir.n = list(0);
+               /*n1->nredir.redirect = NULL; - stzalloc did it */
+               t = TRP;
+               break;
+       case TBEGIN:
+               n1 = list(0);
+               t = TEND;
+               break;
+       case TWORD:
+       case TREDIR:
+               tokpushback = 1;
+               return simplecmd();
+       }
+
+       if (readtoken() != t)
+               raise_error_unexpected_syntax(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 = 1;
+       *rpp = NULL;
+       if (redir) {
+               if (n1->type != NSUBSHELL) {
+                       n2 = stzalloc(sizeof(struct nredir));
+                       n2->type = NREDIR;
+                       n2->nredir.n = n1;
+                       n1 = n2;
+               }
+               n1->nredir.redirect = redir;
+       }
+       return n1;
+}
+
+#if ENABLE_ASH_BASH_COMPAT
+static int decode_dollar_squote(void)
+{
+       static const char C_escapes[] ALIGN1 = "nrbtfav""x\\01234567";
+       int c, cnt;
+       char *p;
+       char buf[4];
+
+       c = pgetc();
+       p = strchr(C_escapes, c);
+       if (p) {
+               buf[0] = c;
+               p = buf;
+               cnt = 3;
+               if ((unsigned char)(c - '0') <= 7) { /* \ooo */
+                       do {
+                               c = pgetc();
+                               *++p = c;
+                       } while ((unsigned char)(c - '0') <= 7 && --cnt);
+                       pungetc();
+               } else if (c == 'x') { /* \xHH */
+                       do {
+                               c = pgetc();
+                               *++p = c;
+                       } while (isxdigit(c) && --cnt);
+                       pungetc();
+                       if (cnt == 3) { /* \x but next char is "bad" */
+                               c = 'x';
+                               goto unrecognized;
+                       }
+               } else { /* simple seq like \\ or \t */
+                       p++;
+               }
+               *p = '\0';
+               p = buf;
+               c = bb_process_escape_sequence((void*)&p);
+       } else { /* unrecognized "\z": print both chars unless ' or " */
+               if (c != '\'' && c != '"') {
+ unrecognized:
+                       c |= 0x100; /* "please encode \, then me" */
+               }
+       }
+       return c;
+}
+#endif
+
+/*
+ * 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)
+{
+       /* NB: syntax parameter fits into smallint */
+       int c = firstc;
+       char *out;
+       int len;
+       char line[EOFMARKLEN + 1];
+       struct nodelist *bqlist;
+       smallint quotef;
+       smallint dblquote;
+       smallint oldstyle;
+       smallint prevsyntax; /* syntax before arithmetic */
+#if ENABLE_ASH_EXPAND_PRMT
+       smallint pssyntax;   /* we are expanding a prompt string */
+#endif
+       int varnest;         /* levels of variables expansion */
+       int arinest;         /* levels of arithmetic expansion */
+       int parenlevel;      /* levels of parens in arithmetic */
+       int dqvarnest;       /* levels of variables expansion within double quotes */
+
+       USE_ASH_BASH_COMPAT(smallint bash_dollar_squote = 0;)
+
+#if __GNUC__
+       /* Avoid longjmp clobbering */
+       (void) &out;
+       (void) &quotef;
+       (void) &dblquote;
+       (void) &varnest;
+       (void) &arinest;
+       (void) &parenlevel;
+       (void) &dqvarnest;
+       (void) &oldstyle;
+       (void) &prevsyntax;
+       (void) &syntax;
+#endif
+       startlinno = g_parsefile->linno;
+       bqlist = NULL;
+       quotef = 0;
+       oldstyle = 0;
+       prevsyntax = 0;
+#if ENABLE_ASH_EXPAND_PRMT
+       pssyntax = (syntax == PSSYNTAX);
+       if (pssyntax)
+               syntax = DQSYNTAX;
+#endif
+       dblquote = (syntax == DQSYNTAX);
+       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);
+                               g_parsefile->linno++;
+                               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);
+#if ENABLE_ASH_BASH_COMPAT
+                               if (c == '\\' && bash_dollar_squote) {
+                                       c = decode_dollar_squote();
+                                       if (c & 0x100) {
+                                               USTPUTC('\\', out);
+                                               c = (unsigned char)c;
+                                       }
+                               }
+#endif
+                               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 ENABLE_ASH_EXPAND_PRMT
+                                       if (c == '$' && pssyntax) {
+                                               USTPUTC(CTLESC, out);
+                                               USTPUTC('\\', out);
+                                       }
+#endif
+                                       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 = 1;
+                               }
+                               break;
+                       case CSQUOTE:
+                               syntax = SQSYNTAX;
+ quotemark:
+                               if (eofmark == NULL) {
+                                       USTPUTC(CTLQUOTEMARK, out);
+                               }
+                               break;
+                       case CDQUOTE:
+                               syntax = DQSYNTAX;
+                               dblquote = 1;
+                               goto quotemark;
+                       case CENDQUOTE:
+                               USE_ASH_BASH_COMPAT(bash_dollar_squote = 0;)
+                               if (eofmark != NULL && arinest == 0
+                                && varnest == 0
+                               ) {
+                                       USTPUTC(c, out);
+                               } else {
+                                       if (dqvarnest == 0) {
+                                               syntax = BASESYNTAX;
+                                               dblquote = 0;
+                                       }
+                                       quotef = 1;
+                                       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;
+#if ENABLE_SH_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;
+                                                       dblquote = (syntax == DQSYNTAX);
+                                               } 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) {
+#if ENABLE_ASH_BASH_COMPAT
+                                       if (c == '&') {
+                                               if (pgetc() == '>')
+                                                       c = 0x100 + '>'; /* flag &> */
+                                               pungetc();
+                                       }
+#endif
+                                       goto endword;   /* exit outer loop */
+                               }
+#if ENABLE_ASH_ALIAS
+                               if (c != PEOA)
+#endif
+                                       USTPUTC(c, out);
+
+                       }
+                       c = pgetc_fast();
+               } /* for (;;) */
+       }
+ endword:
+#if ENABLE_SH_MATH_SUPPORT
+       if (syntax == ARISYNTAX)
+               raise_error_syntax("missing '))'");
+#endif
+       if (syntax != BASESYNTAX && !parsebackquote && eofmark == NULL)
+               raise_error_syntax("unterminated quoted string");
+       if (varnest != 0) {
+               startlinno = g_parsefile->linno;
+               /* { */
+               raise_error_syntax("missing '}'");
+       }
+       USTPUTC('\0', out);
+       len = out - (char *)stackblock();
+       out = stackblock();
+       if (eofmark == NULL) {
+               if ((c == '>' || c == '<' USE_ASH_BASH_COMPAT( || c == 0x100 + '>'))
+                && quotef == 0
+               ) {
+                       if (isdigit_str9(out)) {
+                               PARSEREDIR(); /* passed as params: out, c */
+                               lasttoken = TREDIR;
+                               return lasttoken;
+                       }
+                       /* else: non-number X seen, interpret it
+                        * as "NNNX>file" = "NNNX >file" */
+               }
+               pungetc();
+       }
+       quoteflag = quotef;
+       backquotelist = bqlist;
+       grabstackblock(len);
+       wordtext = out;
+       lasttoken = TWORD;
+       return lasttoken;
+/* 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) {
+#if ENABLE_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++)
+                                       continue;
+                               if (*p == '\n' && *q == '\0') {
+                                       c = PEOF;
+                                       g_parsefile->linno++;
+                                       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: {
+       /* out is already checked to be a valid number or "" */
+       int fd = (*out == '\0' ? -1 : atoi(out));
+       union node *np;
+
+       np = stzalloc(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;
+                       /* it also can be NTO2 (>&file), but we can't figure it out yet */
+               else {
+                       np->type = NTO;
+                       pungetc();
+               }
+       }
+#if ENABLE_ASH_BASH_COMPAT
+       else if (c == 0x100 + '>') { /* this flags &> redirection */
+               np->nfile.fd = 1;
+               pgetc(); /* this is '>', no need to check */
+               np->type = NTO2;
+       }
+#endif
+       else { /* c == '<' */
+               /*np->nfile.fd = 0; - stzalloc did it */
+               c = pgetc();
+               switch (c) {
+               case '<':
+                       if (sizeof(struct nfile) != sizeof(struct nhere)) {
+                               np = stzalloc(sizeof(struct nhere));
+                               /*np->nfile.fd = 0; - stzalloc did it */
+                       }
+                       np->type = NHERE;
+                       heredoc = stzalloc(sizeof(struct heredoc));
+                       heredoc->here = np;
+                       c = pgetc();
+                       if (c == '-') {
+                               heredoc->striptabs = 1;
+                       } else {
+                               /*heredoc->striptabs = 0; - stzalloc did it */
+                               pungetc();
+                       }
+                       break;
+
+               case '&':
+                       np->type = NFROMFD;
+                       break;
+
+               case '>':
+                       np->type = NFROMTO;
+                       break;
+
+               default:
+                       np->type = NFROM;
+                       pungetc();
+                       break;
+               }
+       }
+       if (fd >= 0)
+               np->nfile.fd = fd;
+       redirnode = np;
+       goto parseredir_return;
+}
+
+/*
+ * Parse a substitution.  At this point, we have read the dollar sign
+ * and nothing else.
+ */
+
+/* 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)(c) - 33 < 32) \
+                       && ((0xc1ff920dU >> ((unsigned)(c) - 33)) & 1))
+parsesub: {
+       int subtype;
+       int typeloc;
+       int flags;
+       char *p;
+       static const char types[] ALIGN1 = "}-+?=";
+
+       c = pgetc();
+       if (c <= PEOA_OR_PEOF
+        || (c != '(' && c != '{' && !is_name(c) && !is_special(c))
+       ) {
+#if ENABLE_ASH_BASH_COMPAT
+               if (c == '\'')
+                       bash_dollar_squote = 1;
+               else
+#endif
+                       USTPUTC('$', out);
+               pungetc();
+       } else if (c == '(') {  /* $(command) or $((arith)) */
+               if (pgetc() == '(') {
+#if ENABLE_SH_MATH_SUPPORT
+                       PARSEARITH();
+#else
+                       raise_error_syntax("you disabled math support for $((arith)) syntax");
+#endif
+               } else {
+                       pungetc();
+                       PARSEBACKQNEW();
+               }
+       } else {
+               USTPUTC(CTLVAR, out);
+               typeloc = out - (char *)stackblock();
+               USTPUTC(VSNORMAL, out);
+               subtype = VSNORMAL;
+               if (c == '{') {
+                       c = pgetc();
+                       if (c == '#') {
+                               c = pgetc();
+                               if (c == '}')
+                                       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 (isdigit(c)) {
+                       do {
+                               STPUTC(c, out);
+                               c = pgetc();
+                       } while (isdigit(c));
+               } else if (is_special(c)) {
+                       USTPUTC(c, out);
+                       c = pgetc();
+               } else {
+ badsub:
+                       raise_error_syntax("bad substitution");
+               }
+
+               STPUTC('=', out);
+               flags = 0;
+               if (subtype == 0) {
+                       switch (c) {
+                       case ':':
+                               c = pgetc();
+#if ENABLE_ASH_BASH_COMPAT
+                               if (c == ':' || c == '$' || isdigit(c)) {
+                                       pungetc();
+                                       subtype = VSSUBSTR;
+                                       break;
+                               }
+#endif
+                               flags = VSNUL;
+                               /*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;
+                       }
+#if ENABLE_ASH_BASH_COMPAT
+                       case '/':
+                               subtype = VSREPLACE;
+                               c = pgetc();
+                               if (c == '/')
+                                       subtype++; /* VSREPLACEALL */
+                               else
+                                       pungetc();
+                               break;
+#endif
+                       }
+               } 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;
+       smallint savepbq;
+       union node *n;
+       char *volatile str;
+       struct jmploc jmploc;
+       struct jmploc *volatile savehandler;
+       size_t savelen;
+       smallint saveprompt = 0;
+
+#ifdef __GNUC__
+       (void) &saveprompt;
+#endif
+       savepbq = parsebackquote;
+       if (setjmp(jmploc.loc)) {
+               free(str);
+               parsebackquote = 0;
+               exception_handler = savehandler;
+               longjmp(exception_handler->loc, 1);
+       }
+       INT_OFF;
+       str = NULL;
+       savelen = out - (char *)stackblock();
+       if (savelen > 0) {
+               str = ckmalloc(savelen);
+               memcpy(str, stackblock(), savelen);
+       }
+       savehandler = exception_handler;
+       exception_handler = &jmploc;
+       INT_ON;
+       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);
+                       }
+                       pc = pgetc();
+                       switch (pc) {
+                       case '`':
+                               goto done;
+
+                       case '\\':
+                               pc = pgetc();
+                               if (pc == '\n') {
+                                       g_parsefile->linno++;
+                                       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:
+#if ENABLE_ASH_ALIAS
+                       case PEOA:
+#endif
+                               startlinno = g_parsefile->linno;
+                               raise_error_syntax("EOF in backquote substitution");
+
+                       case '\n':
+                               g_parsefile->linno++;
+                               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 = stzalloc(sizeof(**nlpp));
+       /* (*nlpp)->next = NULL; - stzalloc did it */
+       parsebackquote = oldstyle;
+
+       if (oldstyle) {
+               saveprompt = doprompt;
+               doprompt = 0;
+       }
+
+       n = list(2);
+
+       if (oldstyle)
+               doprompt = saveprompt;
+       else if (readtoken() != TRP)
+               raise_error_unexpected_syntax(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);
+               INT_OFF;
+               free(str);
+               str = NULL;
+               INT_ON;
+       }
+       parsebackquote = savepbq;
+       exception_handler = savehandler;
+       if (arinest || dblquote)
+               USTPUTC(CTLBACKQ | CTLQUOTE, out);
+       else
+               USTPUTC(CTLBACKQ, out);
+       if (oldstyle)
+               goto parsebackq_oldreturn;
+       goto parsebackq_newreturn;
+}
+
+#if ENABLE_SH_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 */
+
+/*
+ * 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] ALIGN1 = {
+       '\n', '(', ')', /* singles */
+       '&', '|', ';',  /* doubles */
+       0
+};
+
+#define xxreadtoken_singles 3
+#define xxreadtoken_doubles 3
+
+static const char xxreadtoken_tokens[] ALIGN1 = {
+       TNL, TLP, TRP,          /* only single occurrence allowed */
+       TBACKGND, TPIPE, TSEMI, /* if single occurrence */
+       TEOF,                   /* corresponds to trailing nul */
+       TAND, TOR, TENDCASE     /* if double occurrence */
+};
+
+static int
+xxreadtoken(void)
+{
+       int c;
+
+       if (tokpushback) {
+               tokpushback = 0;
+               return lasttoken;
+       }
+       if (needprompt) {
+               setprompt(2);
+       }
+       startlinno = g_parsefile->linno;
+       for (;;) {                      /* until token or start of word found */
+               c = pgetc_fast();
+               if (c == ' ' || c == '\t' USE_ASH_ALIAS( || c == PEOA))
+                       continue;
+
+               if (c == '#') {
+                       while ((c = pgetc()) != '\n' && c != PEOF)
+                               continue;
+                       pungetc();
+               } else if (c == '\\') {
+                       if (pgetc() != '\n') {
+                               pungetc();
+                               break; /* return readtoken1(...) */
+                       }
+                       startlinno = ++g_parsefile->linno;
+                       if (doprompt)
+                               setprompt(2);
+               } else {
+                       const char *p;
+
+                       p = xxreadtoken_chars + sizeof(xxreadtoken_chars) - 1;
+                       if (c != PEOF) {
+                               if (c == '\n') {
+                                       g_parsefile->linno++;
+                                       needprompt = doprompt;
+                               }
+
+                               p = strchr(xxreadtoken_chars, c);
+                               if (p == NULL)
+                                       break; /* return readtoken1(...) */
+
+                               if ((int)(p - xxreadtoken_chars) >= xxreadtoken_singles) {
+                                       int cc = pgetc();
+                                       if (cc == c) {    /* double occurrence? */
+                                               p += xxreadtoken_doubles + 1;
+                                       } else {
+                                               pungetc();
+#if ENABLE_ASH_BASH_COMPAT
+                                               if (c == '&' && cc == '>') /* &> */
+                                                       break; /* return readtoken1(...) */
+#endif
+                                       }
+                               }
+                       }
+                       lasttoken = xxreadtoken_tokens[p - xxreadtoken_chars];
+                       return lasttoken;
+               }
+       } /* for (;;) */
+
+       return readtoken1(c, BASESYNTAX, (char *) NULL, 0);
+}
+#else /* old xxreadtoken */
+#define RETURN(token)   return lasttoken = token
+static int
+xxreadtoken(void)
+{
+       int c;
+
+       if (tokpushback) {
+               tokpushback = 0;
+               return lasttoken;
+       }
+       if (needprompt) {
+               setprompt(2);
+       }
+       startlinno = g_parsefile->linno;
+       for (;;) {      /* until token or start of word found */
+               c = pgetc_fast();
+               switch (c) {
+               case ' ': case '\t':
+#if ENABLE_ASH_ALIAS
+               case PEOA:
+#endif
+                       continue;
+               case '#':
+                       while ((c = pgetc()) != '\n' && c != PEOF)
+                               continue;
+                       pungetc();
+                       continue;
+               case '\\':
+                       if (pgetc() == '\n') {
+                               startlinno = ++g_parsefile->linno;
+                               if (doprompt)
+                                       setprompt(2);
+                               continue;
+                       }
+                       pungetc();
+                       goto breakloop;
+               case '\n':
+                       g_parsefile->linno++;
+                       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 /* old xxreadtoken */
+
+static int
+readtoken(void)
+{
+       int t;
+#if DEBUG
+       smallint alreadyseen = tokpushback;
+#endif
+
+#if ENABLE_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;
+
+               pp = findkwd(wordtext);
+               if (pp) {
+                       lasttoken = t = pp - tokname_array;
+                       TRACE(("keyword %s recognized\n", tokname(t)));
+                       goto out;
+               }
+       }
+
+       if (checkkwd & CHKALIAS) {
+#if ENABLE_ASH_ALIAS
+               struct alias *ap;
+               ap = lookupalias(wordtext, 1);
+               if (ap != 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;
+}
+
+static char
+peektoken(void)
+{
+       int t;
+
+       t = readtoken();
+       tokpushback = 1;
+       return tokname_array[t][0];
+}
+
+/*
+ * Read and parse a command.  Returns NEOF on end of file.  (NULL is a
+ * valid parse tree indicating a blank line.)
+ */
+static 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 = 1;
+       return list(1);
+}
+
+/*
+ * Input any here documents.
+ */
+static void
+parseheredoc(void)
+{
+       struct heredoc *here;
+       union node *n;
+
+       here = heredoclist;
+       heredoclist = NULL;
+
+       while (here) {
+               if (needprompt) {
+                       setprompt(2);
+               }
+               readtoken1(pgetc(), here->here->type == NHERE? SQSYNTAX : DQSYNTAX,
+                               here->eofmark, here->striptabs);
+               n = stzalloc(sizeof(struct narg));
+               n->narg.type = NARG;
+               /*n->narg.next = NULL; - stzalloc did it */
+               n->narg.text = wordtext;
+               n->narg.backquote = backquotelist;
+               here->here->nhere.doc = n;
+               here = here->next;
+       }
+}
+
+
+/*
+ * called by editline -- any expansions to the prompt should be added here.
+ */
+#if ENABLE_ASH_EXPAND_PRMT
+static const char *
+expandstr(const char *ps)
+{
+       union node n;
+
+       /* XXX Fix (char *) cast. It _is_ a bug. ps is variable's value,
+        * and token processing _can_ alter it (delete NULs etc). */
+       setinputstring((char *)ps);
+       readtoken1(pgetc(), PSSYNTAX, 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
+
+/*
+ * 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;
+}
+
+/*
+ * The eval command.
+ */
+static int
+evalcmd(int argc UNUSED_PARAM, char **argv)
+{
+       char *p;
+       char *concat;
+
+       if (argv[1]) {
+               p = argv[1];
+               argv += 2;
+               if (argv[0]) {
+                       STARTSTACKSTR(concat);
+                       for (;;) {
+                               concat = stack_putstr(p, concat);
+                               p = *argv++;
+                               if (p == NULL)
+                                       break;
+                               STPUTC(' ', concat);
+                       }
+                       STPUTC('\0', concat);
+                       p = grabstackstr(concat);
+               }
+               evalstring(p, ~SKIPEVAL);
+
+       }
+       return exitstatus;
+}
+
+/*
+ * 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 (doing_jobctl)
+                       showjobs(stderr, SHOW_CHANGED);
+#endif
+               inter = 0;
+               if (iflag && top) {
+                       inter++;
+#if ENABLE_ASH_MAIL
+                       chkmail();
+#endif
+               }
+               n = parsecmd(inter);
+#if DEBUG
+               showtree(n);
+#endif
+               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 can only be 2,1,0. Here 2->1, 1/0->0 */
+                       job_warning >>= 1;
+                       numeof = 0;
+                       evaltree(n, 0);
+               }
+               popstackmark(&smark);
+               skip = evalskip;
+
+               if (skip) {
+                       evalskip = 0;
+                       return skip & SKIPEVAL;
+               }
+       }
+       return 0;
+}
+
+/*
+ * 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;
+
+       /* IIRC standards do not say whether . is to be searched.
+        * And it is even smaller this way, making it unconditional for now:
+        */
+       if (1) { /* ENABLE_ASH_BASH_COMPAT */
+               fullname = name;
+               goto try_cur_dir;
+       }
+
+       while ((fullname = padvance(&path, name)) != NULL) {
+ try_cur_dir:
+               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;
+               }
+               if (fullname != name)
+                       stunalloc(fullname);
+       }
+
+       /* not found in the PATH */
+       ash_msg_and_raise_error("%s: not found", 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(ckstrdup(sp->text), VSTRFIXED | VTEXTFIXED);
+
+       if (argv[1]) {        /* That's what SVR2 does */
+               char *fullname = find_dot_file(argv[1]);
+               argv += 2;
+               argc -= 2;
+               if (argc) { /* argc > 0, argv[0] != NULL */
+                       saveparam = shellparam;
+                       shellparam.malloced = 0;
+                       shellparam.nparam = argc;
+                       shellparam.p = argv;
+               };
+
+               setinputfile(fullname, INPUT_PUSH_FILE);
+               commandname = fullname;
+               cmdloop(0);
+               popfile();
+
+               if (argc) {
+                       freeparam(&shellparam);
+                       shellparam = saveparam;
+               };
+               status = exitstatus;
+       }
+       return status;
+}
+
+static int
+exitcmd(int argc UNUSED_PARAM, char **argv)
+{
+       if (stoppedjobs())
+               return 0;
+       if (argv[1])
+               exitstatus = number(argv[1]);
+       raise_exception(EXEXIT);
+       /* NOTREACHED */
+}
+
+/*
+ * Read a file containing shell functions.
+ */
+static void
+readcmdfile(char *name)
+{
+       setinputfile(name, INPUT_PUSH_FILE);
+       cmdloop(0);
+       popfile();
+}
+
+
+/* ============ find_command inplementation */
+
+/*
+ * 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;
+       }
+
+/* #if ENABLE_FEATURE_SH_STANDALONE... moved after builtin check */
+
+       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 */
+       cmdp = cmdlookup(name, 0);
+       if (cmdp != 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) {
+               if (IS_BUILTIN_REGULAR(bcmd))
+                       goto builtin_success;
+               if (act & DO_ALTPATH) {
+                       if (!(act & DO_ALTBLTIN))
+                               goto builtin_success;
+               } else if (builtinloc <= 0) {
+                       goto builtin_success;
+               }
+       }
+
+#if ENABLE_FEATURE_SH_STANDALONE
+       {
+               int applet_no = find_applet_by_name(name);
+               if (applet_no >= 0) {
+                       entry->cmdtype = CMDNORMAL;
+                       entry->u.index = -2 - applet_no;
+                       return;
+               }
+       }
+#endif
+
+       /* 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);
+               /* NB: code below will still use fullname
+                * despite it being "unallocated" */
+               idx++;
+               if (pathopt) {
+                       if (prefix(pathopt, "builtin")) {
+                               if (bcmd)
+                                       goto builtin_success;
+                               continue;
+                       }
+                       if ((act & DO_NOFUNC)
+                        || !prefix(pathopt, "func")
+                       ) {     /* 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);
+                       /* NB: stalloc will return space pointed by fullname
+                        * (because we don't have any intervening allocations
+                        * between stunalloc above and this stalloc) */
+                       readcmdfile(fullname);
+                       cmdp = cmdlookup(name, 0);
+                       if (cmdp == NULL || cmdp->cmdtype != CMDFUNCTION)
+                               ash_msg_and_raise_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;
+               }
+               INT_OFF;
+               cmdp = cmdlookup(name, 1);
+               cmdp->cmdtype = CMDNORMAL;
+               cmdp->param.index = idx;
+               INT_ON;
+               goto success;
+       }
+
+       /* We failed.  If there was an entry for this command, delete it */
+       if (cmdp && updatetbl)
+               delete_cmd_entry();
+       if (act & DO_ERR)
+               ash_msg("%s: %s", name, errmsg(e, "not found"));
+       entry->cmdtype = CMDUNKNOWN;
+       return;
+
+ builtin_success:
+       if (!updatetbl) {
+               entry->cmdtype = CMDBUILTIN;
+               entry->u.cmd = bcmd;
+               return;
+       }
+       INT_OFF;
+       cmdp = cmdlookup(name, 1);
+       cmdp->cmdtype = CMDBUILTIN;
+       cmdp->param.cmd = bcmd;
+       INT_ON;
+ success:
+       cmdp->rehash = 0;
+       entry->cmdtype = cmdp->cmdtype;
+       entry->u = cmdp->param;
+}
+
+
+/* ============ trap.c */
+
+/*
+ * The trap builtin.
+ */
+static int
+trapcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
+{
+       char *action;
+       char **ap;
+       int signo;
+
+       nextopt(nullstr);
+       ap = argptr;
+       if (!*ap) {
+               for (signo = 0; signo < NSIG; signo++) {
+                       if (trap[signo] != NULL) {
+                               out1fmt("trap -- %s %s\n",
+                                               single_quote(trap[signo]),
+                                               get_signame(signo));
+                       }
+               }
+               return 0;
+       }
+       action = NULL;
+       if (ap[1])
+               action = *ap++;
+       while (*ap) {
+               signo = get_signum(*ap);
+               if (signo < 0)
+                       ash_msg_and_raise_error("%s: bad trap", *ap);
+               INT_OFF;
+               if (action) {
+                       if (LONE_DASH(action))
+                               action = NULL;
+                       else
+                               action = ckstrdup(action);
+               }
+               free(trap[signo]);
+               trap[signo] = action;
+               if (signo != 0)
+                       setsignal(signo);
+               INT_ON;
+               ap++;
+       }
+       return 0;
+}
+
+
+/* ============ Builtins */
+
+#if !ENABLE_FEATURE_SH_EXTRA_QUIET
+/*
+ * Lists available builtins
+ */
+static int
+helpcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
+{
+       unsigned col;
+       unsigned i;
+
+       out1fmt("\n"
+               "Built-in commands:\n"
+               "------------------\n");
+       for (col = 0, i = 0; i < ARRAY_SIZE(builtintab); i++) {
+               col += out1fmt("%c%s", ((col == 0) ? '\t' : ' '),
+                                       builtintab[i].name + 1);
+               if (col > 60) {
+                       out1fmt("\n");
+                       col = 0;
+               }
+       }
+#if ENABLE_FEATURE_SH_STANDALONE
+       {
+               const char *a = applet_names;
+               while (*a) {
+                       col += out1fmt("%c%s", ((col == 0) ? '\t' : ' '), a);
+                       if (col > 60) {
+                               out1fmt("\n");
+                               col = 0;
+                       }
+                       a += strlen(a) + 1;
+               }
+       }
+#endif
+       out1fmt("\n\n");
+       return EXIT_SUCCESS;
+}
+#endif /* FEATURE_SH_EXTRA_QUIET */
+
+/*
+ * The export and readonly commands.
+ */
+static int
+exportcmd(int argc UNUSED_PARAM, char **argv)
+{
+       struct var *vp;
+       char *name;
+       const char *p;
+       char **aptr;
+       int flag = argv[0][0] == 'r' ? VREADONLY : VEXPORT;
+
+       if (nextopt("p") != 'p') {
+               aptr = argptr;
+               name = *aptr;
+               if (name) {
+                       do {
+                               p = strchr(name, '=');
+                               if (p != NULL) {
+                                       p++;
+                               } else {
+                                       vp = *findvar(hashvar(name), name);
+                                       if (vp) {
+                                               vp->flags |= flag;
+                                               continue;
+                                       }
+                               }
+                               setvar(name, p, flag);
+                       } while ((name = *++aptr) != NULL);
+                       return 0;
+               }
+       }
+       showvars(argv[0], flag, 0);
+       return 0;
+}
+
+/*
+ * Delete a function if it exists.
+ */
+static void
+unsetfunc(const char *name)
+{
+       struct tblentry *cmdp;
+
+       cmdp = cmdlookup(name, 0);
+       if (cmdp!= NULL && cmdp->cmdtype == CMDFUNCTION)
+               delete_cmd_entry();
+}
+
+/*
+ * 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.
+ */
+static int
+unsetcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
+{
+       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;
+}
+
+
+/*      setmode.c      */
+
+#include <sys/times.h>
+
+static const unsigned char timescmd_str[] ALIGN1 = {
+       ' ',  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 argc UNUSED_PARAM, char **argv UNUSED_PARAM)
+{
+       long 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;
+}
+
+#if ENABLE_SH_MATH_SUPPORT
+/*
+ * 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 UNUSED_PARAM, char **argv)
+{
+       arith_t i;
+
+       argv++;
+       if (!*argv)
+               ash_msg_and_raise_error("expression expected");
+       do {
+               i = ash_arith(*argv);
+       } while (*++argv);
+
+       return !i;
+}
+#endif /* SH_MATH_SUPPORT */
+
+
+/* ============ miscbltin.c
+ *
+ * Miscellaneous builtins.
+ */
+
+#undef rflag
+
+#if defined(__GLIBC__) && __GLIBC__ == 2 && __GLIBC_MINOR__ < 1
+typedef enum __rlimit_resource rlim_t;
+#endif
+
+/*
+ * The read builtin. Options:
+ *      -r              Do not interpret '\' specially
+ *      -s              Turn off echo (tty only)
+ *      -n NCHARS       Read NCHARS max
+ *      -p PROMPT       Display PROMPT on stderr (if input is from tty)
+ *      -t SECONDS      Timeout after SECONDS (tty or pipe only)
+ *      -u FD           Read from given FD instead of fd 0
+ * This uses unbuffered input, which may be avoidable in some cases.
+ * TODO: bash also has:
+ *      -a ARRAY        Read into array[0],[1],etc
+ *      -d DELIM        End on DELIM char, not newline
+ *      -e              Use line editing (tty only)
+ */
+static int
+readcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
+{
+       static const char *const arg_REPLY[] = { "REPLY", NULL };
+
+       char **ap;
+       int backslash;
+       char c;
+       int rflag;
+       char *prompt;
+       const char *ifs;
+       char *p;
+       int startword;
+       int status;
+       int i;
+       int fd = 0;
+#if ENABLE_ASH_READ_NCHARS
+       int nchars = 0; /* if != 0, -n is in effect */
+       int silent = 0;
+       struct termios tty, old_tty;
+#endif
+#if ENABLE_ASH_READ_TIMEOUT
+       unsigned end_ms = 0;
+       unsigned timeout = 0;
+#endif
+
+       rflag = 0;
+       prompt = NULL;
+       while ((i = nextopt("p:u:r"
+               USE_ASH_READ_TIMEOUT("t:")
+               USE_ASH_READ_NCHARS("n:s")
+       )) != '\0') {
+               switch (i) {
+               case 'p':
+                       prompt = optionarg;
+                       break;
+#if ENABLE_ASH_READ_NCHARS
+               case 'n':
+                       nchars = bb_strtou(optionarg, NULL, 10);
+                       if (nchars < 0 || errno)
+                               ash_msg_and_raise_error("invalid count");
+                       /* nchars == 0: off (bash 3.2 does this too) */
+                       break;
+               case 's':
+                       silent = 1;
+                       break;
+#endif
+#if ENABLE_ASH_READ_TIMEOUT
+               case 't':
+                       timeout = bb_strtou(optionarg, NULL, 10);
+                       if (errno || timeout > UINT_MAX / 2048)
+                               ash_msg_and_raise_error("invalid timeout");
+                       timeout *= 1000;
+#if 0 /* even bash have no -t N.NNN support */
+                       ts.tv_sec = bb_strtou(optionarg, &p, 10);
+                       ts.tv_usec = 0;
+                       /* EINVAL means number is ok, but not terminated by NUL */
+                       if (*p == '.' && errno == EINVAL) {
+                               char *p2;
+                               if (*++p) {
+                                       int scale;
+                                       ts.tv_usec = bb_strtou(p, &p2, 10);
+                                       if (errno)
+                                               ash_msg_and_raise_error("invalid timeout");
+                                       scale = p2 - p;
+                                       /* normalize to usec */
+                                       if (scale > 6)
+                                               ash_msg_and_raise_error("invalid timeout");
+                                       while (scale++ < 6)
+                                               ts.tv_usec *= 10;
+                               }
+                       } else if (ts.tv_sec < 0 || errno) {
+                               ash_msg_and_raise_error("invalid timeout");
+                       }
+                       if (!(ts.tv_sec | ts.tv_usec)) { /* both are 0? */
+                               ash_msg_and_raise_error("invalid timeout");
+                       }
+#endif /* if 0 */
+                       break;
+#endif
+               case 'r':
+                       rflag = 1;
+                       break;
+               case 'u':
+                       fd = bb_strtou(optionarg, NULL, 10);
+                       if (fd < 0 || errno)
+                               ash_msg_and_raise_error("invalid file descriptor");
+                       break;
+               default:
+                       break;
+               }
+       }
+       if (prompt && isatty(fd)) {
+               out2str(prompt);
+       }
+       ap = argptr;
+       if (*ap == NULL)
+               ap = (char**)arg_REPLY;
+       ifs = bltinlookup("IFS");
+       if (ifs == NULL)
+               ifs = defifs;
+#if ENABLE_ASH_READ_NCHARS
+       tcgetattr(fd, &tty);
+       old_tty = tty;
+       if (nchars || silent) {
+               if (nchars) {
+                       tty.c_lflag &= ~ICANON;
+                       tty.c_cc[VMIN] = nchars < 256 ? nchars : 255;
+               }
+               if (silent) {
+                       tty.c_lflag &= ~(ECHO | ECHOK | ECHONL);
+               }
+               /* if tcgetattr failed, tcsetattr will fail too.
+                * Ignoring, it's harmless. */
+               tcsetattr(fd, TCSANOW, &tty);
+       }
+#endif
+
+       status = 0;
+       startword = 2;
+       backslash = 0;
+#if ENABLE_ASH_READ_TIMEOUT
+       if (timeout) /* NB: ensuring end_ms is nonzero */
+               end_ms = ((unsigned)(monotonic_us() / 1000) + timeout) | 1;
+#endif
+       STARTSTACKSTR(p);
+       do {
+               const char *is_ifs;
+
+#if ENABLE_ASH_READ_TIMEOUT
+               if (end_ms) {
+                       struct pollfd pfd[1];
+                       pfd[0].fd = fd;
+                       pfd[0].events = POLLIN;
+                       timeout = end_ms - (unsigned)(monotonic_us() / 1000);
+                       if ((int)timeout <= 0 /* already late? */
+                        || safe_poll(pfd, 1, timeout) != 1 /* no? wait... */
+                       ) { /* timed out! */
+#if ENABLE_ASH_READ_NCHARS
+                               tcsetattr(fd, TCSANOW, &old_tty);
+#endif
+                               return 1;
+                       }
+               }
+#endif
+               if (nonblock_safe_read(fd, &c, 1) != 1) {
+                       status = 1;
+                       break;
+               }
+               if (c == '\0')
+                       continue;
+               if (backslash) {
+                       backslash = 0;
+                       if (c != '\n')
+                               goto put;
+                       continue;
+               }
+               if (!rflag && c == '\\') {
+                       backslash = 1;
+                       continue;
+               }
+               if (c == '\n')
+                       break;
+               /* $IFS splitting */
+/* http://www.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_06_05 */
+               is_ifs = strchr(ifs, c);
+               if (startword && is_ifs) {
+                       if (isspace(c))
+                               continue;
+                       /* it is a non-space ifs char */
+                       startword--;
+                       if (startword == 1) /* first one? */
+                               continue; /* yes, it is not next word yet */
+               }
+               startword = 0;
+               if (ap[1] != NULL && is_ifs) {
+                       const char *beg;
+                       STACKSTRNUL(p);
+                       beg = stackblock();
+                       setvar(*ap, beg, 0);
+                       ap++;
+                       /* can we skip one non-space ifs char? (2: yes) */
+                       startword = isspace(c) ? 2 : 1;
+                       STARTSTACKSTR(p);
+                       continue;
+               }
+ put:
+               STPUTC(c, p);
+       }
+/* end of do {} while: */
+#if ENABLE_ASH_READ_NCHARS
+       while (--nchars);
+#else
+       while (1);
+#endif
+
+#if ENABLE_ASH_READ_NCHARS
+       tcsetattr(fd, TCSANOW, &old_tty);
+#endif
+
+       STACKSTRNUL(p);
+       /* Remove trailing space ifs chars */
+       while ((char *)stackblock() <= --p && isspace(*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 UNUSED_PARAM, char **argv)
+{
+       static const char permuser[3] ALIGN1 = "ugo";
+       static const char permmode[3] ALIGN1 = "rwx";
+       static const short permmask[] ALIGN2 = {
+               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;
+       }
+
+       INT_OFF;
+       mask = umask(0);
+       umask(mask);
+       INT_ON;
+
+       ap = *argptr;
+       if (ap == 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 (isdigit((unsigned char) *ap)) {
+                       mask = 0;
+                       do {
+                               if (*ap >= '8' || *ap < '0')
+                                       ash_msg_and_raise_error(illnum, argv[1]);
+                               mask = (mask << 3) + (*ap - '0');
+                       } while (*++ap != '\0');
+                       umask(mask);
+               } else {
+                       mask = ~mask & 0777;
+                       if (!bb_parse_mode(ap, &mask)) {
+                               ash_msg_and_raise_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 {
+       uint8_t cmd;          /* RLIMIT_xxx fit into it */
+       uint8_t factor_shift; /* shift by to get rlim_{cur,max} values */
+       char    option;
+};
+
+static const struct limits limits_tbl[] = {
+#ifdef RLIMIT_CPU
+       { RLIMIT_CPU,        0, 't' },
+#endif
+#ifdef RLIMIT_FSIZE
+       { RLIMIT_FSIZE,      9, 'f' },
+#endif
+#ifdef RLIMIT_DATA
+       { RLIMIT_DATA,      10, 'd' },
+#endif
+#ifdef RLIMIT_STACK
+       { RLIMIT_STACK,     10, 's' },
+#endif
+#ifdef RLIMIT_CORE
+       { RLIMIT_CORE,       9, 'c' },
+#endif
+#ifdef RLIMIT_RSS
+       { RLIMIT_RSS,       10, 'm' },
+#endif
+#ifdef RLIMIT_MEMLOCK
+       { RLIMIT_MEMLOCK,   10, 'l' },
+#endif
+#ifdef RLIMIT_NPROC
+       { RLIMIT_NPROC,      0, 'p' },
+#endif
+#ifdef RLIMIT_NOFILE
+       { RLIMIT_NOFILE,     0, 'n' },
+#endif
+#ifdef RLIMIT_AS
+       { RLIMIT_AS,        10, 'v' },
+#endif
+#ifdef RLIMIT_LOCKS
+       { RLIMIT_LOCKS,      0, 'w' },
+#endif
+};
+static const char limits_name[] =
+#ifdef RLIMIT_CPU
+       "time(seconds)" "\0"
+#endif
+#ifdef RLIMIT_FSIZE
+       "file(blocks)" "\0"
+#endif
+#ifdef RLIMIT_DATA
+       "data(kb)" "\0"
+#endif
+#ifdef RLIMIT_STACK
+       "stack(kb)" "\0"
+#endif
+#ifdef RLIMIT_CORE
+       "coredump(blocks)" "\0"
+#endif
+#ifdef RLIMIT_RSS
+       "memory(kb)" "\0"
+#endif
+#ifdef RLIMIT_MEMLOCK
+       "locked memory(kb)" "\0"
+#endif
+#ifdef RLIMIT_NPROC
+       "process" "\0"
+#endif
+#ifdef RLIMIT_NOFILE
+       "nofiles" "\0"
+#endif
+#ifdef RLIMIT_AS
+       "vmemory(kb)" "\0"
+#endif
+#ifdef RLIMIT_LOCKS
+       "locks" "\0"
+#endif
+;
+
+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_shift;
+               out1fmt("%lld\n", (long long) val);
+       }
+}
+
+static int
+ulimitcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
+{
+       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_tbl; l->option != what; l++)
+               continue;
+
+       set = *argptr ? 1 : 0;
+       if (set) {
+               char *p = *argptr;
+
+               if (all || argptr[1])
+                       ash_msg_and_raise_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');
+                               // val is actually 'unsigned long int' and can't get < 0
+                               if (val < (rlim_t) 0)
+                                       break;
+                       }
+                       if (c)
+                               ash_msg_and_raise_error("bad number");
+                       val <<= l->factor_shift;
+               }
+       }
+       if (all) {
+               const char *lname = limits_name;
+               for (l = limits_tbl; l != &limits_tbl[ARRAY_SIZE(limits_tbl)]; l++) {
+                       getrlimit(l->cmd, &limit);
+                       out1fmt("%-20s ", lname);
+                       lname += strlen(lname) + 1;
+                       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)
+                       ash_msg_and_raise_error("error setting limit (%m)");
+       } else {
+               printlim(how, &limit, l);
+       }
+       return 0;
+}
+
+/* ============ main() and helpers */
+
+/*
+ * Called to exit the shell.
+ */
+static void exitshell(void) NORETURN;
+static 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_type == EXEXIT)
+/* dash bug: it just does _exit(exitstatus) here
+ * but we have to do setjobctl(0) first!
+ * (bug is still not fixed in dash-0.5.3 - if you run dash
+ * under Midnight Commander, on exit from dash MC is backgrounded) */
+                       status = exitstatus;
+               goto out;
+       }
+       exception_handler = &loc;
+       p = trap[0];
+       if (p) {
+               trap[0] = NULL;
+               evalstring(p, 0);
+       }
+       flush_stdout_stderr();
+ out:
+       setjobctl(0);
+       _exit(status);
+       /* NOTREACHED */
+}
+
+static void
+init(void)
+{
+       /* from input.c: */
+       basepf.next_to_pgetc = basepf.buf = basebuf;
+
+       /* from trap.c: */
+       signal(SIGCHLD, SIG_DFL);
+
+       /* from var.c: */
+       {
+               char **envp;
+               char ppid[sizeof(int)*3 + 1];
+               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), "%u", (unsigned) 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);
+       }
+}
+
+/*
+ * Process the shell command line arguments.
+ */
+static void
+procargs(char **argv)
+{
+       int i;
+       const char *xminusc;
+       char **xargv;
+
+       xargv = argv;
+       arg0 = xargv[0];
+       /* if (xargv[0]) - mmm, this is always true! */
+               xargv++;
+       for (i = 0; i < NOPTS; i++)
+               optlist[i] = 2;
+       argptr = xargv;
+       if (options(1)) {
+               /* it already printed err message */
+               raise_exception(EXERROR);
+       }
+       xargv = argptr;
+       xminusc = minusc;
+       if (*xargv == NULL) {
+               if (xminusc)
+                       ash_msg_and_raise_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;
+#if ENABLE_ASH_GETOPTS
+       shellparam.optind = 1;
+       shellparam.optoff = -1;
+#endif
+       /* assert(shellparam.malloced == 0 && shellparam.nparam == 0); */
+       while (*xargv) {
+               shellparam.nparam++;
+               xargv++;
+       }
+       optschanged();
+}
+
+/*
+ * Read /etc/profile or .profile.
+ */
+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();
+}
+
+/*
+ * 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: */
+       g_parsefile->left_in_buffer = 0;
+       g_parsefile->left_in_line = 0;      /* clear input buffer */
+       popallfiles();
+       /* from parser.c: */
+       tokpushback = 0;
+       checkkwd = 0;
+       /* from redir.c: */
+       clearredir(/*drop:*/ 0);
+}
+
+#if PROFILE
+static short profile_buf[16384];
+extern int etext();
+#endif
+
+/*
+ * 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) MAIN_EXTERNALLY_VISIBLE;
+int ash_main(int argc UNUSED_PARAM, char **argv)
+{
+       const char *shinit;
+       volatile smallint state;
+       struct jmploc jmploc;
+       struct stackmark smark;
+
+       /* Initialize global data */
+       INIT_G_misc();
+       INIT_G_memstack();
+       INIT_G_var();
+#if ENABLE_ASH_ALIAS
+       INIT_G_alias();
+#endif
+       INIT_G_cmdtable();
+
+#if PROFILE
+       monitor(4, etext, profile_buf, sizeof(profile_buf), 50);
+#endif
+
+#if ENABLE_FEATURE_EDITING
+       line_input_state = new_line_input_t(FOR_SHELL | WITH_PATH_LOOKUP);
+#endif
+       state = 0;
+       if (setjmp(jmploc.loc)) {
+               smallint e;
+               smallint s;
+
+               reset();
+
+               e = exception_type;
+               if (e == EXERROR)
+                       exitstatus = 2;
+               s = state;
+               if (e == EXEXIT || s == 0 || iflag == 0 || shlvl)
+                       exitshell();
+               if (e == EXINT)
+                       outcslow('\n', stderr);
+
+               popstackmark(&smark);
+               FORCE_INT_ON; /* enable interrupts */
+               if (s == 1)
+                       goto state1;
+               if (s == 2)
+                       goto state2;
+               if (s == 3)
+                       goto state3;
+               goto state4;
+       }
+       exception_handler = &jmploc;
+#if DEBUG
+       opentrace();
+       TRACE(("Shell args: "));
+       trace_puts_args(argv);
+#endif
+       rootpid = getpid();
+
+#if ENABLE_ASH_RANDOM_SUPPORT
+       /* Can use monotonic_ns() for better randomness but for now it is
+        * not used anywhere else in busybox... so avoid bloat */
+       random_galois_LFSR = random_LCG = rootpid + monotonic_us();
+#endif
+       init();
+       setstackmark(&smark);
+       procargs(argv);
+
+#if ENABLE_FEATURE_EDITING_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
+       ) {
+               shinit = lookupvar("ENV");
+               if (shinit != NULL && *shinit != '\0') {
+                       read_profile(shinit);
+               }
+       }
+ state3:
+       state = 4;
+       if (minusc) {
+               /* evalstring pushes parsefile stack.
+                * Ensure we don't falsely claim that 0 (stdin)
+                * is one of stacked source fds.
+                * Testcase: ash -c 'exec 1>&0' must not complain. */
+               if (!sflag)
+                       g_parsefile->fd = -1;
+               evalstring(minusc, 0);
+       }
+
+       if (sflag || minusc == NULL) {
+#if ENABLE_FEATURE_EDITING_SAVEHISTORY
+               if (iflag) {
+                       const char *hp = lookupvar("HISTFILE");
+                       if (hp)
+                               line_input_state->hist_file = 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 */
+}
+
+
+/*-
+ * 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/ash_doc.txt b/shell/ash_doc.txt
new file mode 100644 (file)
index 0000000..2aa4443
--- /dev/null
@@ -0,0 +1,122 @@
+       Wait + signals
+
+We had some bugs here which are hard to test in testsuite.
+
+Bug 1280 (http://busybox.net/bugs/view.php?id=1280):
+was misbehaving in interactive ash. Correct behavior:
+
+$ sleep 20 &
+$ wait
+^C
+$ wait
+^C
+$ wait
+^C
+...
+
+
+Bug 1984 (http://busybox.net/bugs/view.php?id=1984):
+traps were not triggering:
+
+trap_handler_usr () {
+    echo trap usr
+}
+trap_handler_int () {
+    echo trap int
+}
+trap trap_handler_usr USR1
+trap trap_handler_int INT
+sleep 3600 &
+echo "Please do: kill -USR1 $$"
+echo "or: kill -INT $$"
+while true; do wait; echo wait interrupted; done
+
+
+Bug 189 (https://bugs.busybox.net/show_bug.cgi?id=189)
+
+func() {
+    sleep 1
+}
+while (true); do
+    func
+    echo Looping
+done
+
+^C was observed to make ash processes geometrically multiply (!) instead
+of exiting. (true) in subshell does not seem to matter, as another user
+reports the same with:
+
+trap "echo USR1" USR1
+while true; do
+    echo Sleeping
+    sleep 5
+done
+
+Compat note.
+Bash version 3.2.0(1) exits this script at the receipt of SIGINT
+_only_ if it had two last children die from it.
+The following trace was obtained while periodically running
+"killall -SIGINT sleep; sleep 0.1; kill -SIGINT <bash_PID>":
+
+23:48:32.376707 clone(...) = 13528
+23:48:32.388706 waitpid(-1, 0xffc832ec, 0) = ? ERESTARTSYS (To be restarted)
+23:48:32.459761 --- SIGINT (Interrupt) @ 0 (0) ---
+    kill -SIGINT <bash_PID> is ignored, back to waiting:
+23:48:32.463706 waitpid(-1, [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], 0) = 13528
+    sleep exited with 0
+23:48:37.377557 --- SIGCHLD (Child exited) @ 0 (0) ---
+23:48:37.378451 clone(...) = 13538
+23:48:37.390708 waitpid(-1, [{WIFSIGNALED(s) && WTERMSIG(s) == SIGINT}], 0) = 13538
+    sleep was killed by "killall -SIGINT sleep"
+23:48:38.523944 --- SIGCHLD (Child exited) @ 0 (0) ---
+23:48:38.524861 clone(...) = 13542
+23:48:38.538706 waitpid(-1, 0xffc832ec, 0) = ? ERESTARTSYS (To be restarted)
+23:48:38.624761 --- SIGINT (Interrupt) @ 0 (0) ---
+    kill -SIGINT <bash_PID> is ignored, back to waiting:
+23:48:38.628706 waitpid(-1, [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], 0) = 13542
+    sleep exited with 0
+23:48:43.525674 --- SIGCHLD (Child exited) @ 0 (0) ---
+23:48:43.526563 clone(...) = 13545
+23:48:43.538709 waitpid(-1, [{WIFSIGNALED(s) && WTERMSIG(s) == SIGINT}], 0) = 13545
+    sleep was killed by "killall -SIGINT sleep"
+23:48:44.466848 --- SIGCHLD (Child exited) @ 0 (0) ---
+23:48:44.467735 clone(...) = 13549
+23:48:44.481706 waitpid(-1, 0xffc832ec, 0) = ? ERESTARTSYS (To be restarted)
+23:48:44.567757 --- SIGINT (Interrupt) @ 0 (0) ---
+    kill -SIGINT <bash_PID> is ignored, back to waiting:
+23:48:44.571706 waitpid(-1, [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], 0) = 13549
+    sleep exited with 0
+23:48:49.468553 --- SIGCHLD (Child exited) @ 0 (0) ---
+23:48:49.469445 clone(...) = 13551
+23:48:49.481708 waitpid(-1, [{WIFSIGNALED(s) && WTERMSIG(s) == SIGINT}], 0) = 13551
+    sleep was killed by "killall -SIGINT sleep"
+23:48:50.515837 --- SIGCHLD (Child exited) @ 0 (0) ---
+23:48:50.516718 clone(...) = 13555
+23:48:50.530706 waitpid(-1, 0xffc832ec, 0) = ? ERESTARTSYS (To be restarted)
+23:48:50.615761 --- SIGINT (Interrupt) @ 0 (0) ---
+    kill -SIGINT <bash_PID> is ignored, back to waiting:
+23:48:50.619705 waitpid(-1, [{WIFSIGNALED(s) && WTERMSIG(s) == SIGINT}], 0) = 13555
+    sleep was killed by "killall -SIGINT sleep".
+    This is the second one in a row. Kill ourself:
+23:48:51.504604 kill(13515, SIGINT)     = 0
+23:48:51.504689 --- SIGINT (Interrupt) @ 0 (0) ---
+23:48:51.504915 +++ killed by SIGINT +++
+
+As long as there is at least one "sleep 5" which exited successfully
+(not killed by SIGINT), bash continues. This is not documented anywhere
+AFAIKS.
+
+Why keyboard ^C acts differently?
+
+00:08:07.655985 clone(...) = 14270
+00:08:07.669707 waitpid(-1, [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], 0) = 14270
+00:08:12.656872 --- SIGCHLD (Child exited) @ 0 (0) ---
+00:08:12.657743 clone(...) = 14273
+00:08:12.671708 waitpid(-1, [{WIFSIGNALED(s) && WTERMSIG(s) == SIGINT}], 0) = 14273
+00:08:13.810778 --- SIGINT (Interrupt) @ 0 (0) ---
+00:08:13.818705 kill(14269, SIGINT)     = 0
+00:08:13.820103 --- SIGINT (Interrupt) @ 0 (0) ---
+00:08:13.820925 +++ killed by SIGINT +++
+
+Perhaps because at the moment bash got SIGINT it had no children?
+(it did not manage to spawn new sleep yet, see the trace)
diff --git a/shell/ash_ptr_hack.c b/shell/ash_ptr_hack.c
new file mode 100644 (file)
index 0000000..68d9072
--- /dev/null
@@ -0,0 +1,29 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Copyright (C) 2008 by Denys Vlasenko <vda.linux@googlemail.com>
+ *
+ * Licensed under GPLv2, see file LICENSE in this tarball for details.
+ */
+
+struct globals_misc;
+struct globals_memstack;
+struct globals_var;
+
+#ifndef GCC_COMBINE
+
+/* We cheat here. They are declared as const ptr in ash.c,
+ * but here we make them live in R/W memory */
+struct globals_misc     *ash_ptr_to_globals_misc;
+struct globals_memstack *ash_ptr_to_globals_memstack;
+struct globals_var      *ash_ptr_to_globals_var;
+
+#else
+
+/* gcc -combine will see through and complain */
+/* Using alternative method which is more likely to break
+ * on weird architectures, compilers, linkers and so on */
+struct globals_misc     *const ash_ptr_to_globals_misc __attribute__ ((section (".data")));
+struct globals_memstack *const ash_ptr_to_globals_memstack __attribute__ ((section (".data")));
+struct globals_var      *const ash_ptr_to_globals_var __attribute__ ((section (".data")));
+
+#endif
diff --git a/shell/ash_test/ash-alias/alias.right b/shell/ash_test/ash-alias/alias.right
new file mode 100644 (file)
index 0000000..0667b21
--- /dev/null
@@ -0,0 +1,4 @@
+alias: 0
+alias: 0
+./alias.tests: line 25: qfoo: not found
+quux
diff --git a/shell/ash_test/ash-alias/alias.tests b/shell/ash_test/ash-alias/alias.tests
new file mode 100755 (executable)
index 0000000..8d07b0b
--- /dev/null
@@ -0,0 +1,37 @@
+# place holder for future alias testing
+#ash# shopt -s expand_aliases
+
+# alias/unalias tests originally in builtins.tests
+
+unalias -a
+# this should return success, according to POSIX.2
+alias
+echo alias: $?
+alias foo=bar
+unalias foo
+# this had better return success, according to POSIX.2
+alias
+echo alias: $?
+
+# bug in all versions through bash-2.05b
+
+unalias qfoo qbar qbaz quux 2>/dev/null
+
+alias qfoo=qbar
+alias qbar=qbaz
+alias qbaz=quux
+alias quux=qfoo
+
+qfoo
+
+unalias qfoo qbar qbaz quux
+
+unalias -a
+
+alias foo='echo '
+alias bar=baz
+alias baz=quux
+
+foo bar
+
+unalias foo bar baz
diff --git a/shell/ash_test/ash-arith/README.ash b/shell/ash_test/ash-arith/README.ash
new file mode 100644 (file)
index 0000000..7da22ef
--- /dev/null
@@ -0,0 +1 @@
+there is no support for (( )) constructs in ash
diff --git a/shell/ash_test/ash-arith/arith-bash1.right b/shell/ash_test/ash-arith/arith-bash1.right
new file mode 100644 (file)
index 0000000..b261da1
--- /dev/null
@@ -0,0 +1,2 @@
+1
+0
diff --git a/shell/ash_test/ash-arith/arith-bash1.tests b/shell/ash_test/ash-arith/arith-bash1.tests
new file mode 100755 (executable)
index 0000000..b37b730
--- /dev/null
@@ -0,0 +1,5 @@
+# checks for [[ ]]
+
+# && and ||
+[[ a && "" ]]; echo $?
+[[ a || "" ]]; echo $?
diff --git a/shell/ash_test/ash-arith/arith-for.right b/shell/ash_test/ash-arith/arith-for.right
new file mode 100644 (file)
index 0000000..88dbc15
--- /dev/null
@@ -0,0 +1,74 @@
+0
+1
+2
+0
+1
+2
+0
+1
+2
+0
+2
+4
+fx is a function
+fx ()
+{
+    i=0;
+    for ((1; i < 3; i++ ))
+    do
+        echo $i;
+    done;
+    for ((i=0; 1; i++ ))
+    do
+        if (( i >= 3 )); then
+            break;
+        fi;
+        echo $i;
+    done;
+    for ((i=0; i<3; 1))
+    do
+        echo $i;
+        (( i++ ));
+    done;
+    i=0;
+    for ((1; 1; 1))
+    do
+        if (( i > 2 )); then
+            break;
+        fi;
+        echo $i;
+        (( i++ ));
+    done;
+    i=0;
+    for ((1; 1; 1))
+    do
+        if (( i > 2 )); then
+            break;
+        fi;
+        echo $i;
+        (( i++ ));
+    done
+}
+0
+1
+2
+0
+1
+2
+0
+1
+2
+0
+1
+2
+0
+1
+2
+./arith-for.tests: line 77: syntax error: arithmetic expression required
+./arith-for.tests: line 77: syntax error: `(( i=0; "i < 3" ))'
+2
+./arith-for.tests: line 83: syntax error: `;' unexpected
+./arith-for.tests: line 83: syntax error: `(( i=0; i < 3; i++; 7 ))'
+2
+20
+20
diff --git a/shell/ash_test/ash-arith/arith-for.testsx b/shell/ash_test/ash-arith/arith-for.testsx
new file mode 100755 (executable)
index 0000000..4fa30ff
--- /dev/null
@@ -0,0 +1,94 @@
+fx()
+{
+i=0
+for (( ; i < 3; i++ ))
+do
+       echo $i
+done
+
+for (( i=0; ; i++ ))
+do
+       if (( i >= 3 )); then
+               break;
+       fi
+       echo $i
+done
+
+for (( i=0; i<3; ))
+do
+       echo $i
+       (( i++ ))
+done
+
+i=0
+for (( ; ; ))
+do
+       if (( i > 2 )); then
+               break;
+       fi
+       echo $i;
+       (( i++ ))
+done
+
+i=0
+for ((;;))
+do
+       if (( i > 2 )); then
+               break;
+       fi
+       echo $i;
+       (( i++ ))
+done
+}
+
+for (( i=0; "i < 3" ; i++ ))
+do
+       echo $i
+done
+
+i=0
+for (( ; "i < 3"; i++ ))
+do
+       echo $i
+done
+
+for (( i=0; ; i++ ))
+do
+       if (( i >= 3 )); then
+               break;
+       fi
+       echo $i
+done
+
+for ((i = 0; ;i++ ))
+do
+       echo $i
+       if (( i < 3 )); then
+               (( i++ ))
+               continue;
+       fi
+       break
+done
+
+type fx
+fx
+
+# errors
+for (( i=0; "i < 3" ))
+do
+       echo $i
+done
+echo $?
+
+for (( i=0; i < 3; i++; 7 ))
+do
+       echo $i
+done
+echo $?
+
+# one-liners added in post-bash-2.04
+for     ((i=0; i < 20; i++)) do : ; done
+echo $i
+
+for     ((i=0; i < 20; i++)) { : ; }
+echo $i
diff --git a/shell/ash_test/ash-arith/arith.right b/shell/ash_test/ash-arith/arith.right
new file mode 100644 (file)
index 0000000..3ea7ce6
--- /dev/null
@@ -0,0 +1,138 @@
+Format: 'expected actual'
+163 163
+4 4
+16 16
+8 8
+2 2
+4 4
+2 2
+2 2
+1 1
+0 0
+0 0
+0 0
+1 1
+1 1
+2 2
+-3 -3
+-2 -2
+1 1
+0 0
+2 2
+131072 131072
+29 29
+33 33
+49 49
+1 1
+1 1
+0 0
+0 0
+1 1
+1 1
+1 1
+2 2
+3 3
+1 1
+58 58
+2 2
+60 60
+1 1
+256 256
+16 16
+62 62
+4 4
+29 29
+5 5
+-4 -4
+4 4
+1 1
+32 32
+32 32
+1 1
+1 1
+32 32
+20 20
+30 30
+20 20
+30 30
+./arith.tests: line 117: syntax error: 1 ? 20 : x+=2
+6 6
+6,5,3 6,5,3
+263 263
+255 255
+40 40
+./arith.tests: line 163: syntax error:  7 = 43 
+./arith.tests: line 165: divide by zero
+./arith.tests: let: line 166: syntax error: jv += $iv
+./arith.tests: line 167: syntax error:  jv += $iv 
+./arith.tests: let: line 168: syntax error: rv = 7 + (43 * 6
+abc
+def
+ghi
+./arith.tests: line 191: syntax error:  ( 4 + A ) + 4 
+16 16
+./arith.tests: line 196: syntax error:  4 ? : 3 + 5 
+./arith.tests: line 197: syntax error:  1 ? 20 
+./arith.tests: line 198: syntax error:  4 ? 20 : 
+9 9
+./arith.tests: line 205: syntax error:  0 && B=42 
+./arith.tests: line 208: syntax error:  1 || B=88 
+9 9
+9 9
+9 9
+7 7
+7
+4 4
+32767 32767
+32768 32768
+131072 131072
+2147483647 2147483647
+1 1
+4 4
+4 4
+5 5
+5 5
+4 4
+3 3
+3 3
+4 4
+4 4
+./arith.tests: line 257: syntax error:  7-- 
+./arith.tests: line 259: syntax error:  --x=7 
+./arith.tests: line 260: syntax error:  ++x=7 
+./arith.tests: line 262: syntax error:  x++=7 
+./arith.tests: line 263: syntax error:  x--=7 
+4 4
+7 7
+-7 -7
+./arith1.sub: line 2: syntax error:  4-- 
+./arith1.sub: line 3: syntax error:  4++ 
+./arith1.sub: line 4: syntax error:  4 -- 
+./arith1.sub: line 5: syntax error:  4 ++ 
+6 6
+3 3
+7 7
+4 4
+0 0
+3 3
+7 7
+2 2
+-2 -2
+1 1
+./arith1.sub: line 37: syntax error:  +++7 
+./arith2.sub: line 2: syntax error:  --7 
+./arith2.sub: line 3: syntax error:  ++7 
+./arith2.sub: line 4: syntax error:  -- 7 
+./arith2.sub: line 5: syntax error:  ++ 7 
+5 5
+1 1
+4 4
+0 0
+./arith2.sub: line 42: syntax error:  -- - 7 
+./arith2.sub: line 47: syntax error:  ++ + 7 
+8 12
+./arith.tests: line 290: syntax error: a b
+42
+42
+42
+./arith.tests: line 302: a[b[c]d]=e: not found
diff --git a/shell/ash_test/ash-arith/arith.tests b/shell/ash_test/ash-arith/arith.tests
new file mode 100755 (executable)
index 0000000..d65758e
--- /dev/null
@@ -0,0 +1,302 @@
+#ash# set +o posix
+#ash# declare -i iv jv
+
+echo "Format: 'expected actual'"
+
+iv=$(( 3 + 5 * 32 ))
+echo 163 $iv
+#ash# iv=iv+3
+#ash# echo 166 $iv
+iv=2
+jv=iv
+
+let "jv *= 2"
+echo 4 $jv
+jv=$(( $jv << 2 ))
+echo 16 $jv
+
+let jv="$jv / 2"
+echo 8 $jv
+#ash# jv="jv >> 2"
+      let jv="jv >> 2"
+echo 2 $jv
+
+iv=$((iv+ $jv))
+echo 4 $iv
+echo 2 $((iv -= jv))
+echo 2 $iv
+echo 1 $(( iv == jv ))
+echo 0 $(( iv != $jv ))
+echo 0 $(( iv < jv ))
+echo 0 $(( $iv > $jv ))
+echo 1 $(( iv <= $jv ))
+echo 1 $(( $iv >= jv ))
+
+echo 2 $jv
+echo -3 $(( ~$jv ))
+echo -2 $(( ~1 ))
+echo 1 $(( ! 0 ))
+
+echo 0 $(( jv % 2 ))
+echo 2 $(( $iv % 4 ))
+
+echo 131072 $(( iv <<= 16 ))
+echo 29 $(( iv %= 33 ))
+
+echo 33 $(( 33 & 55 ))
+echo 49 $(( 33 | 17 ))
+
+echo 1 $(( iv && $jv ))
+echo 1 $(( $iv || jv ))
+
+echo 0 $(( iv && 0 ))
+echo 0 $(( iv & 0 ))
+echo 1 $(( iv && 1 ))
+echo 1 $(( iv & 1 ))
+
+echo 1 $(( $jv || 0 ))
+echo 2 $(( jv | 0 ))
+echo 3 $(( jv | 1 ))
+echo 1 $(( $jv || 1 ))
+
+let 'iv *= jv'
+echo 58 $iv
+echo 2 $jv
+let "jv += $iv"
+echo 60 $jv
+
+echo 1 $(( jv /= iv ))
+echo 256 $(( jv <<= 8 ))
+echo 16 $(( jv >>= 4 ))
+
+echo 62 $(( iv |= 4 ))
+echo 4 $(( iv &= 4 ))
+
+echo 29 $(( iv += (jv + 9)))
+echo 5 $(( (iv + 4) % 7 ))
+
+# unary plus, minus
+echo -4 $(( +4 - 8 ))
+echo 4 $(( -4 + 8 ))
+
+# conditional expressions
+echo 1 $(( 4<5 ? 1 : 32))
+echo 32 $(( 4>5 ? 1 : 32))
+echo 32 $(( 4>(2+3) ? 1 : 32))
+echo 1 $(( 4<(2+3) ? 1 : 32))
+echo 1 $(( (2+2)<(2+3) ? 1 : 32))
+echo 32 $(( (2+2)>(2+3) ? 1 : 32))
+
+# check that the unevaluated part of the ternary operator does not do
+# evaluation or assignment
+x=i+=2
+y=j+=2
+#ash# declare -i i=1 j=1
+      i=1
+      j=1
+echo 20 $((1 ? 20 : (x+=2)))
+#ash# echo $i,$x             # ash mishandles this
+echo 30 $((0 ? (y+=2) : 30))
+#ash# echo $j,$y             # ash mishandles this
+
+x=i+=2
+y=j+=2
+#ash# declare -i i=1 j=1
+      i=1
+      j=1
+echo 20 $((1 ? 20 : (x+=2)))
+#ash# echo $i,$x             # ash mishandles this
+echo 30 $((0 ? (y+=2) : 30))
+#ash# echo $i,$y             # ash mishandles this
+
+# check precedence of assignment vs. conditional operator
+# should be an error
+#ash# declare -i x=2
+      x=2
+#ashnote# bash reports error but continues, ash aborts - using subshell to 'emulate' bash:
+(  y=$((1 ? 20 : x+=2))  )
+
+# check precedence of assignment vs. conditional operator
+#ash# declare -i x=2
+      x=2
+# ash says "line NNN: syntax error: 0 ? x+=2 : 20"
+#ash# echo 20 $((0 ? x+=2 : 20))
+
+# associativity of assignment-operator operator
+#ash# declare -i i=1 j=2 k=3
+i=1
+j=2
+k=3
+echo 6 $((i += j += k))
+echo 6,5,3 $i,$j,$k
+
+# octal, hex
+echo 263 $(( 0x100 | 007 ))
+echo 255 $(( 0xff ))
+#ash# echo 255 $(( 16#ff ))
+#ash# echo 127 $(( 16#FF/2 ))
+#ash# echo 36 $(( 8#44 ))
+
+echo 40 $(( 8 ^ 32 ))
+
+#ash# # other bases
+#ash# echo 10 $(( 16#a ))
+#ash# echo 10 $(( 32#a ))
+#ash# echo 10 $(( 56#a ))
+#ash# echo 10 $(( 64#a ))
+#ash#
+#ash# echo 10 $(( 16#A ))
+#ash# echo 10 $(( 32#A ))
+#ash# echo 36 $(( 56#A ))
+#ash# echo 36 $(( 64#A ))
+#ash#
+#ash# echo 62 $(( 64#@ ))
+#ash# echo 63 $(( 64#_ ))
+
+#ash# # weird bases (error)
+#ash# echo $(( 3425#56 ))
+
+#ash# # missing number after base
+#ash# echo 0 $(( 2# ))
+
+# these should generate errors
+(  echo $(( 7 = 43 ))      )
+#ash# echo $(( 2#44 ))
+(  echo $(( 44 / 0 ))      )
+(  let 'jv += $iv'         )
+(  echo $(( jv += \$iv ))  )
+(  let 'rv = 7 + (43 * 6'  )
+
+#ash# # more errors
+#ash# declare -i i
+#ash# i=0#4
+#ash# i=2#110#11
+
+((echo abc; echo def;); echo ghi)
+
+#ash# if (((4+4) + (4 + 7))); then
+#ash#  echo ok
+#ash# fi
+
+#ash# (())     # make sure the null expression works OK
+
+#ash# a=(0 2 4 6)
+#ash# echo 6 $(( a[1] + a[2] ))
+#ash# echo 1 $(( (a[1] + a[2]) == a[3] ))
+#ash# (( (a[1] + a[2]) == a[3] )) ; echo 0 $?
+
+# test pushing and popping the expression stack
+unset A
+A="4 + "
+(  echo A $(( ( 4 + A ) + 4 ))  )
+A="3 + 5"
+echo 16 $(( ( 4 + A ) + 4 ))
+
+# badly-formed conditional expressions
+(  echo $(( 4 ? : $A ))  )
+(  echo $(( 1 ? 20 ))    )
+(  echo $(( 4 ? 20 : ))  )
+
+# precedence and short-circuit evaluation
+B=9
+echo 9 $B
+
+# error
+(  echo $(( 0 && B=42 )); echo 9 $B  )
+
+# error
+(  echo $(( 1 || B=88 )); echo 9 $B  )
+
+# ash mistakenly evaluates B=... below
+#ash# echo 0 $(( 0 && (B=42) ))
+echo 9 $B
+#ash# echo 0 $(( (${$} - $$) && (B=42) ))
+echo 9 $B
+#ash# echo 1 $(( 1 || (B=88) ))
+echo 9 $B
+
+
+# until command with (( )) command
+x=7
+
+echo 7 $x
+#ash# until (( x == 4 ))
+      until test "$x" = 4
+do
+       echo $x
+       x=4
+done
+
+echo 4 $x
+
+# exponentiation
+echo 32767 $(( 2**15 - 1))
+echo 32768 $(( 2**(16-1)))
+echo 131072 $(( 2**16*2 ))
+echo 2147483647 $(( 2**31-1))
+echo 1 $(( 2**0 ))
+
+# {pre,post}-{inc,dec}rement and associated errors
+
+x=4
+
+echo 4 $x
+echo 4 $(( x++ ))
+echo 5 $x
+echo 5 $(( x-- ))
+echo 4 $x
+
+echo 3 $(( --x ))
+echo 3 $x
+
+echo 4 $(( ++x ))
+echo 4 $x
+
+# bash 3.2 apparently thinks that ++7 is 7
+#ash# echo 7 $(( ++7 ))
+(  echo $(( 7-- ))    )
+
+(  echo $(( --x=7 ))  )
+(  echo $(( ++x=7 ))  )
+
+(  echo $(( x++=7 ))  )
+(  echo $(( x--=7 ))  )
+
+echo 4 $x
+
+echo 7 $(( +7 ))
+echo -7 $(( -7 ))
+
+# bash 3.2 apparently thinks that ++7 is 7
+#ash# echo $(( ++7 ))
+#ash# echo $(( --7 ))
+
+${THIS_SH} ./arith1.sub
+${THIS_SH} ./arith2.sub
+
+x=4
+y=7
+
+#ash# (( x=8 , y=12 ))
+      x=8
+      y=12
+echo $x $y
+
+#ash# # should be an error
+#ash# (( x=9 y=41 ))
+
+# These are errors
+unset b
+(  echo $((a b))  )
+#ash# ((a b))
+
+n=42
+printf "%d\n" $n
+printf "%i\n" $n
+#ash# echo $(( 8#$(printf "%o\n" $n) ))
+printf "%u\n" $n
+#ash# echo $(( 16#$(printf "%x\n" $n) ))
+#ash# echo $(( 16#$(printf "%X\n" $n) ))
+
+# causes longjmp botches through bash-2.05b
+a[b[c]d]=e
diff --git a/shell/ash_test/ash-arith/arith1.sub b/shell/ash_test/ash-arith/arith1.sub
new file mode 100755 (executable)
index 0000000..80aa999
--- /dev/null
@@ -0,0 +1,40 @@
+# test of redone post-increment and post-decrement code
+(  echo $(( 4-- ))   )
+(  echo $(( 4++ ))   )
+(  echo $(( 4 -- ))  )
+(  echo $(( 4 ++ ))  )
+
+#ash# (( array[0]++ ))
+#ash# echo ${array}
+
+#ash# (( array[0] ++ ))
+#ash# echo ${array}
+
+#ash# (( a++ ))
+#ash# echo $a
+#ash# (( a ++ ))
+#ash# echo $a
+      a=2
+
+echo 6 $(( a ++ + 4 ))
+echo 3 $a
+
+echo 7 $(( a+++4 ))
+echo 4 $a
+
+echo 0 $(( a---4 ))
+echo 3 $a
+
+echo 7 $(( a -- + 4 ))
+echo 2 $a
+
+echo -2 $(( a -- - 4 ))
+echo 1 $a
+
+#ash# (( ++ + 7 ))
+
+#ash# (( ++ ))
+(  echo $(( +++7 ))  )
+# bash 3.2 apparently thinks that ++ +7 is 7
+#ash# echo $(( ++ + 7 ))
+#ash# (( -- ))
diff --git a/shell/ash_test/ash-arith/arith2.sub b/shell/ash_test/ash-arith/arith2.sub
new file mode 100755 (executable)
index 0000000..f7e3c92
--- /dev/null
@@ -0,0 +1,57 @@
+# bash 3.2 apparently thinks that ++7 is 7 etc
+(  echo $(( --7 ))   )
+(  echo $(( ++7 ))   )
+(  echo $(( -- 7 ))  )
+(  echo $(( ++ 7 ))  )
+
+#ash# ((++array[0] ))
+#ash# echo 1 $array
+#ash# (( ++ array[0] ))
+#ash# echo 2 $array
+
+#ash# (( ++a ))
+#ash# echo 1 $a
+#ash# (( ++ a ))
+#ash# echo 2 $a
+
+#ash# (( --a ))
+#ash# echo 1 $a
+#ash# (( -- a ))
+#ash# echo 0 $a
+      a=0
+
+echo 5 $(( 4 + ++a ))
+echo 1 $a
+
+# ash doesn't handle it right...
+#ash# echo 6 $(( 4+++a ))
+#ash# echo 2 $a
+      a=2
+
+# ash doesn't handle it right...
+#ash# echo 3 $(( 4---a ))
+#ash# echo 1 $a
+      a=1
+
+echo 4 $(( 4 - -- a ))
+echo 0 $a
+
+#ash# (( -- ))
+# bash 3.2 apparently thinks that ---7 is -7
+#ash# echo $(( ---7 ))
+(  echo $(( -- - 7 ))  )
+
+#ash# (( ++ ))
+# bash 3.2: 7
+#ash# echo 7 $(( ++7 ))
+(  echo $(( ++ + 7 ))  )
+
+# bash 3.2: -7
+#ash# echo -7 $(( ++-7 ))
+# bash 3.2: -7
+#ash# echo -7 $(( ++ - 7 ))
+
+# bash 3.2: 7
+#ash# echo 7 $(( +--7 ))
+# bash 3.2: 7
+#ash# echo 7 $(( -- + 7 ))
diff --git a/shell/ash_test/ash-heredoc/heredoc.right b/shell/ash_test/ash-heredoc/heredoc.right
new file mode 100644 (file)
index 0000000..baf1151
--- /dev/null
@@ -0,0 +1,21 @@
+there
+one - alpha
+two - beta
+three - gamma
+hi\
+there$a
+stuff
+hi\
+there
+EO\
+F
+hi
+tab 1
+tab 2
+tab 3
+abc
+def ghi
+jkl mno
+fff is a shell function
+hi
+there
diff --git a/shell/ash_test/ash-heredoc/heredoc.tests b/shell/ash_test/ash-heredoc/heredoc.tests
new file mode 100755 (executable)
index 0000000..b3cdc3f
--- /dev/null
@@ -0,0 +1,94 @@
+# check order and content of multiple here docs
+
+cat << EOF1 << EOF2
+hi
+EOF1
+there
+EOF2
+
+while read line1; do
+       read line2 <&3
+       echo $line1 - $line2
+done <<EOF1 3<<EOF2
+one
+two
+three
+EOF1
+alpha
+beta
+gamma
+EOF2
+
+
+# check quoted here-doc is protected
+
+a=foo
+cat << 'EOF'
+hi\
+there$a
+stuff
+EOF
+
+# check that quoted here-documents don't have \newline processing done
+
+cat << 'EOF'
+hi\
+there
+EO\
+F
+EOF
+true
+
+# check that \newline is removed at start of here-doc
+cat << EO\
+F
+hi
+EOF
+
+#ash# # check that \newline removal works for here-doc delimiter
+#ash# cat << EOF
+#ash# hi
+#ash# EO\
+#ash# F
+
+# check operation of tab removal in here documents
+cat <<- EOF
+       tab 1
+       tab 2
+       tab 3
+       EOF
+
+# check appending of text to file from here document
+rm -f /tmp/bash-zzz
+cat > /tmp/bash-zzz << EOF
+abc
+EOF
+cat >> /tmp/bash-zzz << EOF
+def ghi
+jkl mno
+EOF
+cat /tmp/bash-zzz
+rm -f /tmp/bash-zzz
+
+# make sure command printing puts the here-document as the last redirection
+# on the line, and the function export code preserves syntactic correctness
+fff()
+{
+  ed /tmp/foo <<ENDOFINPUT >/dev/null
+/^name/d
+w
+q
+ENDOFINPUT
+aa=1
+}
+
+type fff
+#ash# export -f fff
+#ash# ${THIS_SH} -c 'type fff'
+
+# check that end of file delimits a here-document
+# THIS MUST BE LAST!
+
+cat << EOF
+hi
+there
diff --git a/shell/ash_test/ash-invert/invert.right b/shell/ash_test/ash-invert/invert.right
new file mode 100644 (file)
index 0000000..5a9239a
--- /dev/null
@@ -0,0 +1,10 @@
+1
+1
+1
+0
+0
+1
+0
+1
+0
+1
diff --git a/shell/ash_test/ash-invert/invert.tests b/shell/ash_test/ash-invert/invert.tests
new file mode 100755 (executable)
index 0000000..8393d95
--- /dev/null
@@ -0,0 +1,19 @@
+# tests of return value inversion
+# placeholder for future expansion
+
+# user subshells (...) did this wrong in bash versions before 2.04
+
+! ( echo hello | grep h >/dev/null 2>&1 ); echo $?
+! echo hello | grep h >/dev/null 2>&1 ; echo $?
+
+! true ; echo $?
+! false; echo $?
+
+! (false) ; echo $?
+! (true); echo $?
+
+! true | false ; echo $?
+! false | true ; echo $?
+
+! (true | false) ; echo $?
+! (false | true) ; echo $?
diff --git a/shell/ash_test/ash-misc/last_amp.right b/shell/ash_test/ash-misc/last_amp.right
new file mode 100644 (file)
index 0000000..3da21ae
--- /dev/null
@@ -0,0 +1,2 @@
+3
+End
diff --git a/shell/ash_test/ash-misc/last_amp.tests b/shell/ash_test/ash-misc/last_amp.tests
new file mode 100755 (executable)
index 0000000..1609376
--- /dev/null
@@ -0,0 +1,8 @@
+$THIS_SH -c 'echo 3&'
+d=`date`
+while test "`date`" = "$d"; do true; done
+d1=`date`
+$THIS_SH -c 'sleep 1&'
+d2=`date`
+test "$d1" = "$d2" || echo BAD
+echo End
diff --git a/shell/ash_test/ash-misc/shift1.right b/shell/ash_test/ash-misc/shift1.right
new file mode 100644 (file)
index 0000000..b53453c
--- /dev/null
@@ -0,0 +1,9 @@
+2 3 4
+0: shift: line 1: Illegal number: -1
+1 2 3 4
+2 3 4
+3 4
+4
+
+1 2 3 4
+1 2 3 4
diff --git a/shell/ash_test/ash-misc/shift1.tests b/shell/ash_test/ash-misc/shift1.tests
new file mode 100755 (executable)
index 0000000..0992d9b
--- /dev/null
@@ -0,0 +1,10 @@
+$THIS_SH -c 'shift;    echo "$@"' 0 1 2 3 4
+#We do abort on -1, but then we abort. bash executes echo.
+$THIS_SH -c 'shift -1; echo "$@"' 0 1 2 3 4
+$THIS_SH -c 'shift  0; echo "$@"' 0 1 2 3 4
+$THIS_SH -c 'shift  1; echo "$@"' 0 1 2 3 4
+$THIS_SH -c 'shift  2; echo "$@"' 0 1 2 3 4
+$THIS_SH -c 'shift  3; echo "$@"' 0 1 2 3 4
+$THIS_SH -c 'shift  4; echo "$@"' 0 1 2 3 4
+$THIS_SH -c 'shift  5; echo "$@"' 0 1 2 3 4
+$THIS_SH -c 'shift  6; echo "$@"' 0 1 2 3 4
diff --git a/shell/ash_test/ash-quoting/dollar_squote_bash1.right b/shell/ash_test/ash-quoting/dollar_squote_bash1.right
new file mode 100644 (file)
index 0000000..57536b1
--- /dev/null
@@ -0,0 +1,9 @@
+a      b
+a
+b c
+def
+a'b c"d e\f
+a3b c3b e33f
+a\80b c08b
+a3b c30b
+x      y
diff --git a/shell/ash_test/ash-quoting/dollar_squote_bash1.tests b/shell/ash_test/ash-quoting/dollar_squote_bash1.tests
new file mode 100755 (executable)
index 0000000..93a56ca
--- /dev/null
@@ -0,0 +1,7 @@
+echo $'a\tb'
+echo $'a\nb' $'c\nd''ef'
+echo $'a\'b' $'c\"d' $'e\\f'
+echo $'a\63b' $'c\063b' $'e\0633f'
+echo $'a\80b' $'c\608b'
+echo $'a\x33b' $'c\x330b'
+echo $'x\x9y'
diff --git a/shell/ash_test/ash-read/read_ifs.right b/shell/ash_test/ash-read/read_ifs.right
new file mode 100644 (file)
index 0000000..027ecd1
--- /dev/null
@@ -0,0 +1,7 @@
+.a. .b. .c.
+.a. .b. .c.
+.a. .. .b,c.
+.a. .. .b,c.
+.a. .. .c.
+.a. .. .c. .d.
+.a. .. .b,c,d  ,  ,.
diff --git a/shell/ash_test/ash-read/read_ifs.tests b/shell/ash_test/ash-read/read_ifs.tests
new file mode 100755 (executable)
index 0000000..cf7cd93
--- /dev/null
@@ -0,0 +1,7 @@
+printf 'a\t\tb\tc\n' | ( IFS=$(printf "\t") read a b c; echo ".$a. .$b. .$c." )
+printf 'a\t\tb\tc\n' | ( IFS=$(printf " \t") read a b c; echo ".$a. .$b. .$c." )
+printf 'a,,b,c\n'    | ( IFS="," read a b c; echo ".$a. .$b. .$c." )
+printf 'a,,b,c\n'    | ( IFS=" ," read a b c; echo ".$a. .$b. .$c." )
+printf 'a ,, c\n'    | ( IFS=" ," read a b c; echo ".$a. .$b. .$c." )
+printf 'a ,, c d\n'  | ( IFS=" ," read a b c d; echo ".$a. .$b. .$c. .$d." )
+printf ' a,,b,c,d  ,  ,\n' | ( IFS=" ," read a b c; echo ".$a. .$b. .$c." )
diff --git a/shell/ash_test/ash-read/read_n.right b/shell/ash_test/ash-read/read_n.right
new file mode 100644 (file)
index 0000000..1f81af0
--- /dev/null
@@ -0,0 +1,3 @@
+test
+tes
+tes
diff --git a/shell/ash_test/ash-read/read_n.tests b/shell/ash_test/ash-read/read_n.tests
new file mode 100755 (executable)
index 0000000..12423ba
--- /dev/null
@@ -0,0 +1,3 @@
+echo 'test' | (read reply; echo "$reply")
+echo 'test' | (read -n 3 reply; echo "$reply")
+echo 'test' | (read -n3 reply; echo "$reply")
diff --git a/shell/ash_test/ash-read/read_r.right b/shell/ash_test/ash-read/read_r.right
new file mode 100644 (file)
index 0000000..3536bf7
--- /dev/null
@@ -0,0 +1,2 @@
+testbest
+test\
diff --git a/shell/ash_test/ash-read/read_r.tests b/shell/ash_test/ash-read/read_r.tests
new file mode 100755 (executable)
index 0000000..2c4cc61
--- /dev/null
@@ -0,0 +1,2 @@
+echo -e 'test\\\nbest' | (read reply; echo "$reply")
+echo -e 'test\\\nbest' | (read -r reply; echo "$reply")
diff --git a/shell/ash_test/ash-read/read_t.right b/shell/ash_test/ash-read/read_t.right
new file mode 100644 (file)
index 0000000..04126cb
--- /dev/null
@@ -0,0 +1,4 @@
+><
+><
+>test<
+>test<
diff --git a/shell/ash_test/ash-read/read_t.tests b/shell/ash_test/ash-read/read_t.tests
new file mode 100755 (executable)
index 0000000..d65f1ae
--- /dev/null
@@ -0,0 +1,10 @@
+# bash 3.2 outputs:
+
+# ><
+{ echo -n 'te'; sleep 2; echo 'st'; }   | (read -t 1 reply; echo ">$reply<")
+# ><
+{               sleep 2; echo 'test'; } | (read -t 1 reply; echo ">$reply<")
+# >test<
+{ echo -n 'te'; sleep 1; echo 'st'; }   | (read -t 2 reply; echo ">$reply<")
+# >test<
+{               sleep 1; echo 'test'; } | (read -t 2 reply; echo ">$reply<")
diff --git a/shell/ash_test/ash-redir/redir.right b/shell/ash_test/ash-redir/redir.right
new file mode 100644 (file)
index 0000000..2a02d41
--- /dev/null
@@ -0,0 +1 @@
+TEST
diff --git a/shell/ash_test/ash-redir/redir.tests b/shell/ash_test/ash-redir/redir.tests
new file mode 100755 (executable)
index 0000000..7a1a668
--- /dev/null
@@ -0,0 +1,6 @@
+# test: closed fds should stay closed
+exec 1>&-
+echo TEST >TEST
+echo JUNK # lost: stdout is closed
+cat TEST >&2
+rm TEST
diff --git a/shell/ash_test/ash-redir/redir2.right b/shell/ash_test/ash-redir/redir2.right
new file mode 100644 (file)
index 0000000..d86bac9
--- /dev/null
@@ -0,0 +1 @@
+OK
diff --git a/shell/ash_test/ash-redir/redir2.tests b/shell/ash_test/ash-redir/redir2.tests
new file mode 100755 (executable)
index 0000000..61ccea3
--- /dev/null
@@ -0,0 +1,5 @@
+# ash once couldn't redirect above fd#9
+exec 1>/dev/null
+(echo LOST1 >&22) 22>&1
+(echo LOST2 >&22) 22>&1
+(echo OK >&22) 22>&2
diff --git a/shell/ash_test/ash-redir/redir3.right b/shell/ash_test/ash-redir/redir3.right
new file mode 100644 (file)
index 0000000..fd641a8
--- /dev/null
@@ -0,0 +1,3 @@
+TEST
+./redir3.tests: line 4: 9: Bad file descriptor
+Output to fd#9: 1
diff --git a/shell/ash_test/ash-redir/redir3.tests b/shell/ash_test/ash-redir/redir3.tests
new file mode 100755 (executable)
index 0000000..f50a767
--- /dev/null
@@ -0,0 +1,5 @@
+# redirects to closed descriptors should not leave these descriptors"
+# open afterwards
+echo TEST 9>/dev/null
+echo MUST ERROR OUT >&9
+echo "Output to fd#9: $?"
diff --git a/shell/ash_test/ash-redir/redir4.right b/shell/ash_test/ash-redir/redir4.right
new file mode 100644 (file)
index 0000000..d86bac9
--- /dev/null
@@ -0,0 +1 @@
+OK
diff --git a/shell/ash_test/ash-redir/redir4.tests b/shell/ash_test/ash-redir/redir4.tests
new file mode 100755 (executable)
index 0000000..4bdf5ae
--- /dev/null
@@ -0,0 +1,72 @@
+# ash uses fd 10 (usually) for reading the script
+exec 13>&-
+exec 12>&-
+exec 11>&-
+exec 10>&-
+# some amount of input is prefetched.
+# make sure final echo is far enough to not be prefetched.
+###############################################################
+###############################################################
+###############################################################
+###############################################################
+###############################################################
+###############################################################
+###############################################################
+###############################################################
+###############################################################
+###############################################################
+###############################################################
+###############################################################
+###############################################################
+###############################################################
+###############################################################
+###############################################################
+###############################################################
+###############################################################
+###############################################################
+###############################################################
+###############################################################
+###############################################################
+###############################################################
+###############################################################
+###############################################################
+###############################################################
+###############################################################
+###############################################################
+###############################################################
+###############################################################
+###############################################################
+###############################################################
+###############################################################
+###############################################################
+###############################################################
+###############################################################
+###############################################################
+###############################################################
+###############################################################
+###############################################################
+###############################################################
+###############################################################
+###############################################################
+###############################################################
+###############################################################
+###############################################################
+###############################################################
+###############################################################
+###############################################################
+###############################################################
+###############################################################
+###############################################################
+###############################################################
+###############################################################
+###############################################################
+###############################################################
+###############################################################
+###############################################################
+###############################################################
+###############################################################
+###############################################################
+###############################################################
+###############################################################
+###############################################################
+echo "OK"
diff --git a/shell/ash_test/ash-redir/redir5.right b/shell/ash_test/ash-redir/redir5.right
new file mode 100644 (file)
index 0000000..9d08777
--- /dev/null
@@ -0,0 +1,2 @@
+./redir5.tests: line 2: 10: Bad file descriptor
+OK
diff --git a/shell/ash_test/ash-redir/redir5.tests b/shell/ash_test/ash-redir/redir5.tests
new file mode 100755 (executable)
index 0000000..91b0c1f
--- /dev/null
@@ -0,0 +1,3 @@
+# ash uses fd 10 (usually) for reading the script
+echo LOST >&10
+echo OK
diff --git a/shell/ash_test/ash-redir/redir6.right b/shell/ash_test/ash-redir/redir6.right
new file mode 100644 (file)
index 0000000..ed754df
--- /dev/null
@@ -0,0 +1,2 @@
+Hello
+OK
diff --git a/shell/ash_test/ash-redir/redir6.tests b/shell/ash_test/ash-redir/redir6.tests
new file mode 100755 (executable)
index 0000000..33b6d4c
--- /dev/null
@@ -0,0 +1,3 @@
+# we had a bug where this would hang
+(head -n 1 <redir6.right)
+echo OK
diff --git a/shell/ash_test/ash-signals/reap1.right b/shell/ash_test/ash-signals/reap1.right
new file mode 100644 (file)
index 0000000..7326d96
--- /dev/null
@@ -0,0 +1 @@
+Ok
diff --git a/shell/ash_test/ash-signals/reap1.tests b/shell/ash_test/ash-signals/reap1.tests
new file mode 100755 (executable)
index 0000000..bf1a1f9
--- /dev/null
@@ -0,0 +1,14 @@
+#!/bin/sh
+
+# Must not find us alive
+{ sleep 2; kill -9 $$; } 2>/dev/null &
+
+sleep 1 &
+PID=$!
+
+# We must exit the loop in one second.
+# We had bug 5304: builtins never waited for exited children
+while kill -0 $PID >/dev/null 2>&1; do
+    true
+done
+echo Ok
diff --git a/shell/ash_test/ash-signals/signal1.right b/shell/ash_test/ash-signals/signal1.right
new file mode 100644 (file)
index 0000000..cf403ac
--- /dev/null
@@ -0,0 +1,20 @@
+got signal
+trap -- 'echo got signal' USR1
+sent 1 signal
+got signal
+wait interrupted
+trap -- 'echo got signal' USR1
+sent 2 signal
+got signal
+wait interrupted
+trap -- 'echo got signal' USR1
+sent 3 signal
+got signal
+wait interrupted
+trap -- 'echo got signal' USR1
+sent 4 signal
+got signal
+wait interrupted
+trap -- 'echo got signal' USR1
+sent 5 signal
+sleep completed
diff --git a/shell/ash_test/ash-signals/signal1.tests b/shell/ash_test/ash-signals/signal1.tests
new file mode 100755 (executable)
index 0000000..098d21f
--- /dev/null
@@ -0,0 +1,23 @@
+trap "echo got signal" USR1
+
+for try in 1 2 3 4 5; do
+    kill -USR1 $$
+    sleep 0.2
+    echo "sent $try signal"
+done &
+
+sleep 2 &
+
+sleeping=true
+while $sleeping; do
+    trap
+    if wait %%; then
+        echo "sleep completed"
+        sleeping=false
+    elif [ $? == 127 ]; then
+        echo "BUG: no processes to wait for?!"
+        sleeping=false
+    else
+        echo "wait interrupted"
+    fi
+done
diff --git a/shell/ash_test/ash-signals/signal2.right b/shell/ash_test/ash-signals/signal2.right
new file mode 100644 (file)
index 0000000..a2af919
--- /dev/null
@@ -0,0 +1,3 @@
+child sleeps
+child exits as expected
+parent exits
diff --git a/shell/ash_test/ash-signals/signal2.tests b/shell/ash_test/ash-signals/signal2.tests
new file mode 100755 (executable)
index 0000000..df639ca
--- /dev/null
@@ -0,0 +1,18 @@
+#!/bin/sh
+
+$THIS_SH -c '
+cleanup() {
+    echo "child exits as expected"
+    exit
+}
+trap cleanup HUP
+echo "child sleeps"
+sleep 1
+echo "BAD exit from child!"
+' &
+
+child=$!
+sleep 0.1 # let child install handler first
+kill -HUP $child
+wait
+echo "parent exits"
diff --git a/shell/ash_test/ash-signals/signal3.right b/shell/ash_test/ash-signals/signal3.right
new file mode 100644 (file)
index 0000000..3113ba5
--- /dev/null
@@ -0,0 +1,4 @@
+child sleeps
+child got HUP
+child exits
+parent exits
diff --git a/shell/ash_test/ash-signals/signal3.tests b/shell/ash_test/ash-signals/signal3.tests
new file mode 100755 (executable)
index 0000000..b56c2d9
--- /dev/null
@@ -0,0 +1,17 @@
+#!/bin/sh
+
+$THIS_SH -c '
+hup() {
+    echo "child got HUP"
+}
+trap hup HUP
+echo "child sleeps"
+sleep 1
+echo "child exits"
+' &
+
+child=$!
+sleep 0.1 # let child install handler first
+kill -HUP $child
+wait
+echo "parent exits"
diff --git a/shell/ash_test/ash-standalone/noexec_gets_no_env.right b/shell/ash_test/ash-standalone/noexec_gets_no_env.right
new file mode 100644 (file)
index 0000000..8522dff
--- /dev/null
@@ -0,0 +1,4 @@
+VAR7=VAL
+0
+VAR8=VAL
+0
diff --git a/shell/ash_test/ash-standalone/noexec_gets_no_env.tests b/shell/ash_test/ash-standalone/noexec_gets_no_env.tests
new file mode 100755 (executable)
index 0000000..0d347bd
--- /dev/null
@@ -0,0 +1,5 @@
+export VAR7=VAL
+env | grep ^VAR7=
+echo $?
+VAR8=VAL env | grep ^VAR8=
+echo $?
diff --git a/shell/ash_test/ash-standalone/nofork_trashes_getopt.right b/shell/ash_test/ash-standalone/nofork_trashes_getopt.right
new file mode 100644 (file)
index 0000000..573541a
--- /dev/null
@@ -0,0 +1 @@
+0
diff --git a/shell/ash_test/ash-standalone/nofork_trashes_getopt.tests b/shell/ash_test/ash-standalone/nofork_trashes_getopt.tests
new file mode 100755 (executable)
index 0000000..f42c507
--- /dev/null
@@ -0,0 +1,6 @@
+# In this test, rm is NOFORK and it modifies getopt internal state
+rm -f non_existent_file
+# Subsequent hexdump is run as NOEXEC, and thus still uses this state
+hexdump </dev/null
+# Did hexdump segfault etc?
+echo $?
diff --git a/shell/ash_test/ash-vars/var1.right b/shell/ash_test/ash-vars/var1.right
new file mode 100644 (file)
index 0000000..2a01291
--- /dev/null
@@ -0,0 +1,6 @@
+a=a A=a
+a=a A=a
+a= A=
+a= A=
+a=a A=a
+a=a A=a
diff --git a/shell/ash_test/ash-vars/var1.tests b/shell/ash_test/ash-vars/var1.tests
new file mode 100755 (executable)
index 0000000..802e489
--- /dev/null
@@ -0,0 +1,14 @@
+# check that first assignment has proper effect on second one
+
+(
+a=a A=$a
+echo a=$a A=$A
+)
+(a=a A=$a; echo a=$a A=$A)
+(a=a A=$a echo a=$a A=$A)
+(a=a A=$a /bin/echo a=$a A=$A)
+
+f() { echo a=$a A=$A; }
+
+(a=a A=$a f)
+(a=a A=$a; f)
diff --git a/shell/ash_test/ash-vars/var2.right b/shell/ash_test/ash-vars/var2.right
new file mode 100644 (file)
index 0000000..8fed138
--- /dev/null
@@ -0,0 +1 @@
+bus/usb/1/2
diff --git a/shell/ash_test/ash-vars/var2.tests b/shell/ash_test/ash-vars/var2.tests
new file mode 100755 (executable)
index 0000000..07feaeb
--- /dev/null
@@ -0,0 +1 @@
+X=usbdev1.2 X=${X#usbdev} B=${X%%.*} D=${X#*.}; echo bus/usb/$B/$D
diff --git a/shell/ash_test/ash-vars/var_bash1.right b/shell/ash_test/ash-vars/var_bash1.right
new file mode 100644 (file)
index 0000000..c0a0769
--- /dev/null
@@ -0,0 +1,14 @@
+
+
+f
+bcdef
+abcdef
+abcdef
+bcde
+abcd
+abcd
+abcdef
+bcdef
+abcdef
+abcdef
+abcdef
diff --git a/shell/ash_test/ash-vars/var_bash1.tests b/shell/ash_test/ash-vars/var_bash1.tests
new file mode 100755 (executable)
index 0000000..24d3c9a
--- /dev/null
@@ -0,0 +1,18 @@
+var=abcdef
+
+echo ${var:7}
+echo ${var:6}
+echo ${var:5}
+echo ${var:1}
+echo ${var:0}
+echo ${var:-1}
+
+echo ${var:1:4}
+echo ${var:0:4}
+echo ${var::4}
+echo ${var:-1:4}
+
+echo ${var:1:7}
+echo ${var:0:7}
+echo ${var::7}
+echo ${var:-1:7}
diff --git a/shell/ash_test/ash-vars/var_bash2.right b/shell/ash_test/ash-vars/var_bash2.right
new file mode 100644 (file)
index 0000000..acba5c6
--- /dev/null
@@ -0,0 +1,10 @@
+abc123xcba123
+abx123dcba123
+abx123dxba123
+abcx23dcba123
+abcxxxdcbaxxx
+abx
+xba123
+abx23
+abc23dcba123
+abcdcba
diff --git a/shell/ash_test/ash-vars/var_bash2.tests b/shell/ash_test/ash-vars/var_bash2.tests
new file mode 100755 (executable)
index 0000000..29c526c
--- /dev/null
@@ -0,0 +1,24 @@
+var=abc123dcba123
+
+echo ${var/d/x}
+echo ${var/c/x}
+echo ${var//c/x}
+echo ${var/[123]/x}
+echo ${var//[123]/x}
+echo ${var/c*/x}
+echo ${var/*c/x}
+
+# must match longest match: result is "abx23"
+echo ${var/c*1/x}
+
+# empty replacement - 2nd slash can be omitted
+echo ${var/[123]}
+echo ${var//[123]}
+
+### ash doesn't support
+### # match only at the beginning:
+### echo ${var/#a/x}
+### echo ${var/#b/x} # should not match
+### echo ${var//#b/x} # should not match
+### # match only at the end:
+### echo ${var/%3/x}
diff --git a/shell/ash_test/ash-vars/var_bash3.right b/shell/ash_test/ash-vars/var_bash3.right
new file mode 100644 (file)
index 0000000..f7f1479
--- /dev/null
@@ -0,0 +1,20 @@
+a041#c
+a041#c
+a\041#c
+a\041#c
+a\041#c
+a\041#c
+a\041#c
+a\041#c
+a\041#c
+a\c
+a\c
+a\c
+a\\c
+a\\c
+a\\c
+a\tc
+a\tc
+a\tc
+atc
+a\tc
diff --git a/shell/ash_test/ash-vars/var_bash3.tests b/shell/ash_test/ash-vars/var_bash3.tests
new file mode 100755 (executable)
index 0000000..b905027
--- /dev/null
@@ -0,0 +1,41 @@
+a='abc'
+r=${a//b/\041#}
+echo $r
+echo ${a//b/\041#}
+echo "${a//b/\041#}"
+
+a='abc'
+r=${a//b/\\041#}
+echo $r
+echo ${a//b/\\041#}
+echo "${a//b/\\041#}"
+
+a='abc'
+b='\041#'
+r=${a//b/$b}
+echo $r
+echo ${a//b/$b}
+echo "${a//b/$b}"
+
+a='abc'
+b='\'
+r="${a//b/$b}"
+echo $r
+echo ${a//b/$b}
+echo "${a//b/$b}"
+
+a='abc'
+b='\\'
+r="${a//b/$b}"
+echo $r
+echo ${a//b/$b}
+echo "${a//b/$b}"
+
+a='abc'
+b='\t'
+r="${a//b/$b}"
+echo $r
+echo ${a//b/$b}
+echo "${a//b/$b}"
+echo ${a//b/\t}
+echo "${a//b/\t}"
diff --git a/shell/ash_test/ash-vars/var_leak.right b/shell/ash_test/ash-vars/var_leak.right
new file mode 100644 (file)
index 0000000..45c5458
--- /dev/null
@@ -0,0 +1,2 @@
+should be empty: ''
+should be empty: ''
diff --git a/shell/ash_test/ash-vars/var_leak.tests b/shell/ash_test/ash-vars/var_leak.tests
new file mode 100755 (executable)
index 0000000..1b1123f
--- /dev/null
@@ -0,0 +1,9 @@
+# This currently fails with CONFIG_FEATURE_SH_NOFORK=y
+VAR=''
+VAR=qwe true
+echo "should be empty: '$VAR'"
+
+# This fails (always)
+VAR=''
+VAR=qwe exec 2>&1
+echo "should be empty: '$VAR'"
diff --git a/shell/ash_test/ash-vars/var_posix1.right b/shell/ash_test/ash-vars/var_posix1.right
new file mode 100644 (file)
index 0000000..55f3579
--- /dev/null
@@ -0,0 +1,17 @@
+abcdcd
+abcdcd
+abcdcd
+cdcd
+babcdcd
+babcdcd
+ababcdcd
+
+ababcd
+ababcd
+ababcd
+abab
+ababcdc
+ababcdc
+ababcdcd
+
+end
diff --git a/shell/ash_test/ash-vars/var_posix1.tests b/shell/ash_test/ash-vars/var_posix1.tests
new file mode 100755 (executable)
index 0000000..4139e2c
--- /dev/null
@@ -0,0 +1,21 @@
+var=ababcdcd
+
+echo ${var#ab}
+echo ${var##ab}
+echo ${var#a*b}
+echo ${var##a*b}
+echo ${var#?}
+echo ${var##?}
+echo ${var#*}
+echo ${var##*}
+
+echo ${var%cd}
+echo ${var%%cd}
+echo ${var%c*d}
+echo ${var%%c*d}
+echo ${var%?}
+echo ${var%%?}
+echo ${var%*}
+echo ${var%%*}
+
+echo end
diff --git a/shell/ash_test/printenv.c b/shell/ash_test/printenv.c
new file mode 100644 (file)
index 0000000..c4ccda8
--- /dev/null
@@ -0,0 +1,67 @@
+/* printenv -- minimal clone of BSD printenv(1).
+
+   usage: printenv [varname]
+
+   Chet Ramey
+   chet@po.cwru.edu
+*/
+
+/* Copyright (C) 1997-2002 Free Software Foundation, Inc.
+
+   This file is part of GNU Bash, the Bourne Again SHell.
+
+   Bash 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.
+
+   Bash 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 Bash; see the file COPYING.  If not, write to the Free Software
+   Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */
+
+#include <stdlib.h>
+#include <string.h>
+
+extern char **environ;
+
+int
+main (argc, argv)
+     int argc;
+     char **argv;
+{
+  register char **envp, *eval;
+  int len;
+
+  argv++;
+  argc--;
+
+  /* printenv */
+  if (argc == 0)
+    {
+      for (envp = environ; *envp; envp++)
+       puts (*envp);
+      exit(EXIT_SUCCESS);
+    }
+
+  /* printenv varname */
+  len = strlen (*argv);
+  for (envp = environ; *envp; envp++)
+    {
+      if (**argv == **envp && strncmp (*envp, *argv, len) == 0)
+       {
+         eval = *envp + len;
+         /* If the environment variable doesn't have an `=', ignore it. */
+         if (*eval == '=')
+           {
+             puts (eval + 1);
+             exit(EXIT_SUCCESS);
+           }
+       }
+    }
+  exit(EXIT_FAILURE);
+}
diff --git a/shell/ash_test/recho.c b/shell/ash_test/recho.c
new file mode 100644 (file)
index 0000000..fb48d9c
--- /dev/null
@@ -0,0 +1,63 @@
+/*
+   recho -- really echo args, bracketed with <> and with invisible chars
+           made visible.
+
+   Chet Ramey
+   chet@po.cwru.edu
+*/
+
+/* Copyright (C) 2002-2005 Free Software Foundation, Inc.
+
+   This file is part of GNU Bash, the Bourne Again SHell.
+
+   Bash 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.
+
+   Bash 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 Bash; see the file COPYING.  If not, write to the Free Software
+   Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+void strprint();
+
+int
+main(argc, argv)
+int    argc;
+char   **argv;
+{
+       register int    i;
+
+       for (i = 1; i < argc; i++) {
+               printf("argv[%d] = <", i);
+               strprint(argv[i]);
+               printf(">\n");
+       }
+       exit(EXIT_SUCCESS);
+}
+
+void
+strprint(str)
+char   *str;
+{
+       register unsigned char *s;
+
+       for (s = (unsigned char *)str; s && *s; s++) {
+               if (*s < ' ') {
+                       putchar('^');
+                       putchar(*s+64);
+               } else if (*s == 127) {
+                       putchar('^');
+                       putchar('?');
+               } else
+                       putchar(*s);
+       }
+}
diff --git a/shell/ash_test/run-all b/shell/ash_test/run-all
new file mode 100755 (executable)
index 0000000..4d0f39a
--- /dev/null
@@ -0,0 +1,74 @@
+#!/bin/sh
+
+TOPDIR=$PWD
+
+test -x ash || {
+    echo "No ./ash - creating a link to ../../busybox"
+    ln -s ../../busybox ash
+}
+test -x printenv || gcc -O2 -o printenv printenv.c || exit $?
+test -x recho    || gcc -O2 -o recho    recho.c    || exit $?
+test -x zecho    || gcc -O2 -o zecho    zecho.c    || exit $?
+
+PATH="$PWD:$PATH" # for ash and recho/zecho/printenv
+export PATH
+
+THIS_SH="$PWD/ash"
+export THIS_SH
+
+do_test()
+{
+    test -d "$1" || return 0
+    echo do_test "$1"
+    # $1 but with / replaced by # so that it can be used as filename part
+    noslash=`echo "$1" | sed 's:/:#:g'`
+    (
+    cd "$1" || { echo "cannot cd $1!"; exit 1; }
+    for x in run-*; do
+       test -f "$x" || continue
+       case "$x" in
+           "$0"|run-minimal|run-gprof) ;;
+           *.orig|*~) ;;
+           #*) echo $x ; sh $x ;;
+           *)
+           sh "$x" >"$TOPDIR/$noslash-$x.fail" 2>&1 && \
+           { echo "$1/$x: ok"; rm "$TOPDIR/$noslash-$x.fail"; } || echo "$1/$x: fail";
+           ;;
+       esac
+    done
+    # Many bash run-XXX scripts just do this,
+    # no point in duplication it all over the place
+    for x in *.tests; do
+       test -x "$x" || continue
+       name="${x%%.tests}"
+       test -f "$name.right" || continue
+       {
+           "$THIS_SH" "./$x" >"$name.xx" 2>&1
+           diff -u "$name.xx" "$name.right" >"$TOPDIR/$noslash-$x.fail" \
+           && rm -f "$name.xx" "$TOPDIR/$noslash-$x.fail"
+       } && echo "$1/$x: ok" || echo "$1/$x: fail"
+    done
+    )
+}
+
+# main part of this script
+# Usage: run-all [directories]
+
+if [ $# -lt 1 ]; then
+    # All sub directories
+    modules=`ls -d ash-*`
+    # If you want to test ash against hush and msh testsuites
+    # (have to copy hush_test and msh_test dirs to current dir first):
+    #modules=`ls -d ash-* hush_test/hush-* msh_test/msh-*`
+
+    for module in $modules; do
+       do_test $module
+    done
+else
+    while [ $# -ge 1 ]; do
+       if [ -d $1 ]; then
+           do_test $1
+       fi
+       shift
+    done
+fi
diff --git a/shell/ash_test/zecho.c b/shell/ash_test/zecho.c
new file mode 100644 (file)
index 0000000..bf876f6
--- /dev/null
@@ -0,0 +1,39 @@
+/* zecho - bare-bones echo */
+
+/* Copyright (C) 1996-2002 Free Software Foundation, Inc.
+
+   This file is part of GNU Bash, the Bourne Again SHell.
+
+   Bash 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.
+
+   Bash 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 Bash; see the file COPYING.  If not, write to the Free Software
+   Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+int
+main(argc, argv)
+int    argc;
+char   **argv;
+{
+       argv++;
+
+       while (*argv) {
+               (void)printf("%s", *argv);
+               if (*++argv)
+                       putchar(' ');
+       }
+
+       putchar('\n');
+       exit(EXIT_SUCCESS);
+}
diff --git a/shell/bbsh.c b/shell/bbsh.c
new file mode 100644 (file)
index 0000000..897c022
--- /dev/null
@@ -0,0 +1,223 @@
+/* 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 "libbb.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;
+       end = skip_non_whitespace(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_and_exit(cmd->argv[0],cmd->argc,cmd->argv);
+                       execvp(cmd->argv[0],cmd->argv);
+                       printf("No %s", cmd->argv[0]);
+                       exit(EXIT_FAILURE);
+               } 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) MAIN_EXTERNALLY_VISIBLE;
+int bbsh_main(int argc, char **argv)
+{
+       char *command=NULL;
+       FILE *f;
+
+       getopt32(argv, "c:", &command);
+
+       f = argv[optind] ? xfopen_for_read(argv[optind]) : 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/cttyhack.c b/shell/cttyhack.c
new file mode 100644 (file)
index 0000000..0aa4b8a
--- /dev/null
@@ -0,0 +1,77 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Licensed under GPLv2
+ *
+ * Copyright (c) 2007 Denys Vlasenko <vda.linux@googlemail.com>
+ */
+#include "libbb.h"
+
+/* From <linux/vt.h> */
+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 <linux/serial.h> */
+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];
+};
+
+int cttyhack_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int cttyhack_main(int argc UNUSED_PARAM, char **argv)
+{
+       int fd;
+       char console[sizeof(int)*3 + 16];
+       union {
+               struct vt_stat vt;
+               struct serial_struct sr;
+               char paranoia[sizeof(struct serial_struct) * 3];
+       } u;
+
+       if (!*++argv) {
+               bb_show_usage();
+       }
+
+       strcpy(console, "/dev/tty");
+       if (ioctl(0, TIOCGSERIAL, &u.sr) == 0) {
+               /* this is a serial console */
+               sprintf(console + 8, "S%d", u.sr.line);
+       } else if (ioctl(0, VT_GETSTATE, &u.vt) == 0) {
+               /* this is linux virtual tty */
+               sprintf(console + 8, "S%d" + 1, u.vt.v_active);
+       }
+
+       if (console[8]) {
+               fd = xopen(console, O_RDWR);
+               //bb_error_msg("switching to '%s'", console);
+               dup2(fd, 0);
+               dup2(fd, 1);
+               dup2(fd, 2);
+               while (fd > 2) close(fd--);
+               /* Some other session may have it as ctty. Steal it from them */
+               ioctl(0, TIOCSCTTY, 1);
+       }
+
+       BB_EXECVP(argv[0], argv);
+       bb_perror_msg_and_die("cannot exec '%s'", argv[0]);
+}
diff --git a/shell/hush.c b/shell/hush.c
new file mode 100644 (file)
index 0000000..8351590
--- /dev/null
@@ -0,0 +1,7247 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * 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>
+ * Copyright (C) 2008,2009  Denys Vlasenko <vda.linux@googlemail.com>
+ *
+ * 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:
+ *      o_addchr derived from similar w_addchar function in glibc-2.2.
+ *      parse_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.
+ *
+ * POSIX syntax not implemented:
+ *      aliases
+ *      <(list) and >(list) Process Substitution
+ *      Tilde Expansion
+ *
+ * Bash stuff (maybe optionally enable?):
+ *      &> and >& redirection of stdout+stderr
+ *      Brace expansion
+ *      reserved words: [[ ]] function select
+ *      substrings ${var:1:5}
+ *
+ * TODOs:
+ *      grep for "TODO" and fix (some of them are easy)
+ *      builtins: ulimit
+ *      follow IFS rules more precisely, including update semantics
+ *
+ * Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
+ */
+#include "busybox.h"  /* for APPLET_IS_NOFORK/NOEXEC */
+#include <glob.h>
+/* #include <dmalloc.h> */
+#if ENABLE_HUSH_CASE
+# include <fnmatch.h>
+#endif
+#include "math.h"
+#include "match.h"
+#ifndef PIPE_BUF
+# define PIPE_BUF 4096  /* amount of buffering in a pipe */
+#endif
+
+
+/* Build knobs */
+#define LEAK_HUNTING 0
+#define BUILD_AS_NOMMU 0
+/* Enable/disable sanity checks. Ok to enable in production,
+ * only adds a bit of bloat. Set to >1 to get non-production level verbosity.
+ * Keeping 1 for now even in released versions.
+ */
+#define HUSH_DEBUG 1
+/* Slightly bigger (+200 bytes), but faster hush.
+ * So far it only enables a trick with counting SIGCHLDs and forks,
+ * which allows us to do fewer waitpid's.
+ * (we can detect a case where neither forks were done nor SIGCHLDs happened
+ * and therefore waitpid will return the same result as last time)
+ */
+#define ENABLE_HUSH_FAST 0
+
+
+#if BUILD_AS_NOMMU
+# undef BB_MMU
+# undef USE_FOR_NOMMU
+# undef USE_FOR_MMU
+# define BB_MMU 0
+# define USE_FOR_NOMMU(...) __VA_ARGS__
+# define USE_FOR_MMU(...)
+#endif
+
+#if defined SINGLE_APPLET_MAIN
+/* STANDALONE does not make sense, and won't compile */
+# undef CONFIG_FEATURE_SH_STANDALONE
+# undef ENABLE_FEATURE_SH_STANDALONE
+# undef USE_FEATURE_SH_STANDALONE
+# define USE_FEATURE_SH_STANDALONE(...)
+# define SKIP_FEATURE_SH_STANDALONE(...) __VA_ARGS__
+# define ENABLE_FEATURE_SH_STANDALONE 0
+#endif
+
+#if !ENABLE_HUSH_INTERACTIVE
+# undef ENABLE_FEATURE_EDITING
+# define ENABLE_FEATURE_EDITING 0
+# undef ENABLE_FEATURE_EDITING_FANCY_PROMPT
+# define ENABLE_FEATURE_EDITING_FANCY_PROMPT 0
+#endif
+
+/* Do we support ANY keywords? */
+#if ENABLE_HUSH_IF || ENABLE_HUSH_LOOPS || ENABLE_HUSH_CASE
+# define HAS_KEYWORDS 1
+# define IF_HAS_KEYWORDS(...) __VA_ARGS__
+# define IF_HAS_NO_KEYWORDS(...)
+#else
+# define HAS_KEYWORDS 0
+# define IF_HAS_KEYWORDS(...)
+# define IF_HAS_NO_KEYWORDS(...) __VA_ARGS__
+#endif
+
+/* If you comment out one of these below, it will be #defined later
+ * to perform debug printfs to stderr: */
+#define debug_printf(...)        do {} while (0)
+/* Finer-grained debug switches */
+#define debug_printf_parse(...)  do {} while (0)
+#define debug_print_tree(a, b)   do {} while (0)
+#define debug_printf_exec(...)   do {} while (0)
+#define debug_printf_env(...)    do {} while (0)
+#define debug_printf_jobs(...)   do {} while (0)
+#define debug_printf_expand(...) do {} while (0)
+#define debug_printf_glob(...)   do {} while (0)
+#define debug_printf_list(...)   do {} while (0)
+#define debug_printf_subst(...)  do {} while (0)
+#define debug_printf_clean(...)  do {} while (0)
+
+#define ERR_PTR ((void*)(long)1)
+
+#define JOB_STATUS_FORMAT "[%d] %-22s %.40s\n"
+
+#define SPECIAL_VAR_SYMBOL 3
+
+struct variable;
+
+static const char hush_version_str[] ALIGN1 = "HUSH_VERSION="BB_VER;
+
+/* This supports saving pointers malloced in vfork child,
+ * to be freed in the parent.
+ */
+#if !BB_MMU
+typedef struct nommu_save_t {
+       char **new_env;
+       struct variable *old_vars;
+       char **argv;
+       char **argv_from_re_execing;
+} nommu_save_t;
+#endif
+
+/* The descrip member of this structure is only used to make
+ * debugging output pretty */
+static const struct {
+       int mode;
+       signed char default_fd;
+       char descrip[3];
+} redir_table[] = {
+       { 0,                         0, "??" },
+       { O_RDONLY,                  0, "<"  },
+       { O_CREAT|O_TRUNC|O_WRONLY,  1, ">"  },
+       { O_CREAT|O_APPEND|O_WRONLY, 1, ">>" },
+       { O_RDONLY,                  0, "<<" },
+       { O_CREAT|O_RDWR,            1, "<>" },
+/* Should not be needed. Bogus default_fd helps in debugging */
+/*     { O_RDONLY,                 77, "<<" }, */
+};
+
+typedef enum reserved_style {
+       RES_NONE  = 0,
+#if ENABLE_HUSH_IF
+       RES_IF    ,
+       RES_THEN  ,
+       RES_ELIF  ,
+       RES_ELSE  ,
+       RES_FI    ,
+#endif
+#if ENABLE_HUSH_LOOPS
+       RES_FOR   ,
+       RES_WHILE ,
+       RES_UNTIL ,
+       RES_DO    ,
+       RES_DONE  ,
+#endif
+#if ENABLE_HUSH_LOOPS || ENABLE_HUSH_CASE
+       RES_IN    ,
+#endif
+#if ENABLE_HUSH_CASE
+       RES_CASE  ,
+       /* three pseudo-keywords support contrived "case" syntax: */
+       RES_CASE_IN,   /* "case ... IN", turns into RES_MATCH when IN is observed */
+       RES_MATCH ,    /* "word)" */
+       RES_CASE_BODY, /* "this command is inside CASE" */
+       RES_ESAC  ,
+#endif
+       RES_XXXX  ,
+       RES_SNTX
+} reserved_style;
+
+typedef struct o_string {
+       char *data;
+       int length; /* position where data is appended */
+       int maxlen;
+       /* Protect newly added chars against globbing
+        * (by prepending \ to *, ?, [, \) */
+       smallint o_escape;
+       smallint o_glob;
+       /* At least some part of the string was inside '' or "",
+        * possibly empty one: word"", wo''rd etc. */
+       smallint o_quoted;
+       smallint has_empty_slot;
+       smallint o_assignment; /* 0:maybe, 1:yes, 2:no */
+} o_string;
+enum {
+       MAYBE_ASSIGNMENT = 0,
+       DEFINITELY_ASSIGNMENT = 1,
+       NOT_ASSIGNMENT = 2,
+       WORD_IS_KEYWORD = 3, /* not assigment, but next word may be: "if v=xyz cmd;" */
+};
+/* Used for initialization: o_string foo = NULL_O_STRING; */
+#define NULL_O_STRING { NULL }
+
+/* I can almost use ordinary FILE*.  Is open_memstream() universally
+ * available?  Where is it documented? */
+typedef struct in_str {
+       const char *p;
+       /* eof_flag=1: last char in ->p is really an EOF */
+       char eof_flag; /* meaningless if ->p == NULL */
+       char peek_buf[2];
+#if ENABLE_HUSH_INTERACTIVE
+       smallint promptme;
+       smallint promptmode; /* 0: PS1, 1: PS2 */
+#endif
+       FILE *file;
+       int (*get) (struct in_str *);
+       int (*peek) (struct in_str *);
+} in_str;
+#define i_getch(input) ((input)->get(input))
+#define i_peek(input) ((input)->peek(input))
+
+struct redir_struct {
+       struct redir_struct *next;
+       char *rd_filename;          /* filename */
+       int rd_fd;                  /* fd to redirect */
+       /* fd to redirect to, or -3 if rd_fd is to be closed (n>&-) */
+       int rd_dup;
+       smallint rd_type;           /* (enum redir_type) */
+       /* note: for heredocs, rd_filename contains heredoc delimiter,
+        * and subsequently heredoc itself; and rd_dup is a bitmask:
+        * 1: do we need to trim leading tabs?
+        * 2: is heredoc quoted (<<'delim' syntax) ?
+        */
+};
+typedef enum redir_type {
+       REDIRECT_INVALID   = 0,
+       REDIRECT_INPUT     = 1,
+       REDIRECT_OVERWRITE = 2,
+       REDIRECT_APPEND    = 3,
+       REDIRECT_HEREDOC   = 4,
+       REDIRECT_IO        = 5,
+       REDIRECT_HEREDOC2  = 6, /* REDIRECT_HEREDOC after heredoc is loaded */
+
+       REDIRFD_CLOSE      = -3,
+       REDIRFD_SYNTAX_ERR = -2,
+       REDIRFD_TO_FILE    = -1,
+       /* otherwise, rd_fd is redirected to rd_dup */
+
+       HEREDOC_SKIPTABS = 1,
+       HEREDOC_QUOTED   = 2,
+} redir_type;
+
+
+struct command {
+       pid_t pid;                  /* 0 if exited */
+       int assignment_cnt;         /* how many argv[i] are assignments? */
+       smallint is_stopped;        /* is the command currently running? */
+       smallint grp_type;          /* GRP_xxx */
+#define GRP_NORMAL   0
+#define GRP_SUBSHELL 1
+#if ENABLE_HUSH_FUNCTIONS
+# define GRP_FUNCTION 2
+#endif
+       /* if non-NULL, this "command" is { list }, ( list ), or a compound statement */
+       struct pipe *group;
+#if !BB_MMU
+       char *group_as_string;
+#endif
+#if ENABLE_HUSH_FUNCTIONS
+       struct function *child_func;
+/* This field is used to prevent a bug here:
+ * while...do f1() {a;}; f1; f1 {b;}; f1; done
+ * When we execute "f1() {a;}" cmd, we create new function and clear
+ * cmd->group, cmd->group_as_string, cmd->argv[0].
+ * when we execute "f1 {b;}", we notice that f1 exists,
+ * and that it's "parent cmd" struct is still "alive",
+ * we put those fields back into cmd->xxx
+ * (struct function has ->parent_cmd ptr to facilitate that).
+ * When we loop back, we can execute "f1() {a;}" again and set f1 correctly.
+ * Without this trick, loop would execute a;b;b;b;...
+ * instead of correct sequence a;b;a;b;...
+ * When command is freed, it severs the link
+ * (sets ->child_func->parent_cmd to NULL).
+ */
+#endif
+       char **argv;                /* command name and arguments */
+/* argv vector may contain variable references (^Cvar^C, ^C0^C etc)
+ * and on execution these are substituted with their values.
+ * Substitution can make _several_ words out of one argv[n]!
+ * Example: argv[0]=='.^C*^C.' here: echo .$*.
+ * References of the form ^C`cmd arg^C are `cmd arg` substitutions.
+ */
+       struct redir_struct *redirects; /* I/O redirections */
+};
+/* Is there anything in this command at all? */
+#define IS_NULL_CMD(cmd) \
+       (!(cmd)->group && !(cmd)->argv && !(cmd)->redirects)
+
+
+struct pipe {
+       struct pipe *next;
+       int num_cmds;               /* total number of commands in pipe */
+       int alive_cmds;             /* number of commands running (not exited) */
+       int stopped_cmds;           /* number of commands alive, but stopped */
+#if ENABLE_HUSH_JOB
+       int jobid;                  /* job number */
+       pid_t pgrp;                 /* process group ID for the job */
+       char *cmdtext;              /* name of job */
+#endif
+       struct command *cmds;       /* array of commands in pipe */
+       smallint followup;          /* PIPE_BG, PIPE_SEQ, PIPE_OR, PIPE_AND */
+       IF_HAS_KEYWORDS(smallint pi_inverted;) /* "! cmd | cmd" */
+       IF_HAS_KEYWORDS(smallint res_word;) /* needed for if, for, while, until... */
+};
+typedef enum pipe_style {
+       PIPE_SEQ = 1,
+       PIPE_AND = 2,
+       PIPE_OR  = 3,
+       PIPE_BG  = 4,
+} pipe_style;
+/* Is there anything in this pipe at all? */
+#define IS_NULL_PIPE(pi) \
+       ((pi)->num_cmds == 0 IF_HAS_KEYWORDS( && (pi)->res_word == RES_NONE))
+
+/* This holds pointers to the various results of parsing */
+struct parse_context {
+       /* linked list of pipes */
+       struct pipe *list_head;
+       /* last pipe (being constructed right now) */
+       struct pipe *pipe;
+       /* last command in pipe (being constructed right now) */
+       struct command *command;
+       /* last redirect in command->redirects list */
+       struct redir_struct *pending_redirect;
+#if !BB_MMU
+       o_string as_string;
+#endif
+#if HAS_KEYWORDS
+       smallint ctx_res_w;
+       smallint ctx_inverted; /* "! cmd | cmd" */
+#if ENABLE_HUSH_CASE
+       smallint ctx_dsemicolon; /* ";;" seen */
+#endif
+       /* bitmask of FLAG_xxx, for figuring out valid reserved words */
+       int old_flag;
+       /* group we are enclosed in:
+        * example: "if pipe1; pipe2; then pipe3; fi"
+        * when we see "if" or "then", we malloc and copy current context,
+        * and make ->stack point to it. then we parse pipeN.
+        * when closing "then" / fi" / whatever is found,
+        * we move list_head into ->stack->command->group,
+        * copy ->stack into current context, and delete ->stack.
+        * (parsing of { list } and ( list ) doesn't use this method)
+        */
+       struct parse_context *stack;
+#endif
+};
+
+/* On program start, environ points to initial environment.
+ * putenv adds new pointers into it, unsetenv removes them.
+ * Neither of these (de)allocates the strings.
+ * setenv allocates new strings in malloc space and does putenv,
+ * and thus setenv is unusable (leaky) for shell's purposes */
+#define setenv(...) setenv_is_leaky_dont_use()
+struct variable {
+       struct variable *next;
+       char *varstr;        /* points to "name=" portion */
+       int max_len;         /* if > 0, name is part of initial env; else name is malloced */
+       smallint flg_export; /* putenv should be done on this var */
+       smallint flg_read_only;
+};
+
+enum {
+       BC_BREAK = 1,
+       BC_CONTINUE = 2,
+};
+
+#if ENABLE_HUSH_FUNCTIONS
+struct function {
+       struct function *next;
+       char *name;
+       struct command *parent_cmd;
+       struct pipe *body;
+#if !BB_MMU
+       char *body_as_string;
+#endif
+};
+#endif
+
+
+/* "Globals" within this file */
+/* Sorted roughly by size (smaller offsets == smaller code) */
+struct globals {
+       /* interactive_fd != 0 means we are an interactive shell.
+        * If we are, then saved_tty_pgrp can also be != 0, meaning
+        * that controlling tty is available. With saved_tty_pgrp == 0,
+        * job control still works, but terminal signals
+        * (^C, ^Z, ^Y, ^\) won't work at all, and background
+        * process groups can only be created with "cmd &".
+        * With saved_tty_pgrp != 0, hush will use tcsetpgrp()
+        * to give tty to the foreground process group,
+        * and will take it back when the group is stopped (^Z)
+        * or killed (^C).
+        */
+#if ENABLE_HUSH_INTERACTIVE
+       /* 'interactive_fd' is a fd# open to ctty, if we have one
+        * _AND_ if we decided to act interactively */
+       int interactive_fd;
+       const char *PS1;
+       const char *PS2;
+# define G_interactive_fd (G.interactive_fd)
+#else
+# define G_interactive_fd 0
+#endif
+#if ENABLE_FEATURE_EDITING
+       line_input_t *line_input_state;
+#endif
+       pid_t root_pid;
+       pid_t last_bg_pid;
+#if ENABLE_HUSH_JOB
+       int run_list_level;
+       int last_jobid;
+       pid_t saved_tty_pgrp;
+       struct pipe *job_list;
+# define G_saved_tty_pgrp (G.saved_tty_pgrp)
+#else
+# define G_saved_tty_pgrp 0
+#endif
+       smallint flag_SIGINT;
+#if ENABLE_HUSH_LOOPS
+       smallint flag_break_continue;
+#endif
+#if ENABLE_HUSH_FUNCTIONS
+       /* 0: outside of a function (or sourced file)
+        * -1: inside of a function, ok to use return builtin
+        * 1: return is invoked, skip all till end of func
+        */
+       smallint flag_return_in_progress;
+#endif
+       smallint fake_mode;
+       smallint exiting; /* used to prevent EXIT trap recursion */
+       /* These four support $?, $#, and $1 */
+       smalluint last_exitcode;
+       /* are global_argv and global_argv[1..n] malloced? (note: not [0]) */
+       smalluint global_args_malloced;
+       /* how many non-NULL argv's we have. NB: $# + 1 */
+       int global_argc;
+       char **global_argv;
+#if !BB_MMU
+       char *argv0_for_re_execing;
+#endif
+#if ENABLE_HUSH_LOOPS
+       unsigned depth_break_continue;
+       unsigned depth_of_loop;
+#endif
+       const char *ifs;
+       const char *cwd;
+       struct variable *top_var; /* = &G.shell_ver (set in main()) */
+       struct variable shell_ver;
+#if ENABLE_HUSH_FUNCTIONS
+       struct function *top_func;
+#endif
+       /* Signal and trap handling */
+#if ENABLE_HUSH_FAST
+       unsigned count_SIGCHLD;
+       unsigned handled_SIGCHLD;
+       smallint we_have_children;
+#endif
+       /* which signals have non-DFL handler (even with no traps set)? */
+       unsigned non_DFL_mask;
+       char **traps; /* char *traps[NSIG] */
+       sigset_t blocked_set;
+       sigset_t inherited_set;
+#if HUSH_DEBUG
+       unsigned long memleak_value;
+       int debug_indent;
+#endif
+       char user_input_buf[ENABLE_FEATURE_EDITING ? BUFSIZ : 2];
+};
+#define G (*ptr_to_globals)
+/* Not #defining name to G.name - this quickly gets unwieldy
+ * (too many defines). Also, I actually prefer to see when a variable
+ * is global, thus "G." prefix is a useful hint */
+#define INIT_G() do { \
+       SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
+} while (0)
+
+
+/* Function prototypes for builtins */
+static int builtin_cd(char **argv);
+static int builtin_echo(char **argv);
+static int builtin_eval(char **argv);
+static int builtin_exec(char **argv);
+static int builtin_exit(char **argv);
+static int builtin_export(char **argv);
+#if ENABLE_HUSH_JOB
+static int builtin_fg_bg(char **argv);
+static int builtin_jobs(char **argv);
+#endif
+#if ENABLE_HUSH_HELP
+static int builtin_help(char **argv);
+#endif
+#if HUSH_DEBUG
+static int builtin_memleak(char **argv);
+#endif
+static int builtin_pwd(char **argv);
+static int builtin_read(char **argv);
+static int builtin_set(char **argv);
+static int builtin_shift(char **argv);
+static int builtin_source(char **argv);
+static int builtin_test(char **argv);
+static int builtin_trap(char **argv);
+static int builtin_true(char **argv);
+static int builtin_umask(char **argv);
+static int builtin_unset(char **argv);
+static int builtin_wait(char **argv);
+#if ENABLE_HUSH_LOOPS
+static int builtin_break(char **argv);
+static int builtin_continue(char **argv);
+#endif
+#if ENABLE_HUSH_FUNCTIONS
+static int builtin_return(char **argv);
+#endif
+
+/* 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. */
+struct built_in_command {
+       const char *cmd;
+       int (*function)(char **argv);
+#if ENABLE_HUSH_HELP
+       const char *descr;
+#define BLTIN(cmd, func, help) { cmd, func, help }
+#else
+#define BLTIN(cmd, func, help) { cmd, func }
+#endif
+};
+
+/* For now, echo and test are unconditionally enabled.
+ * Maybe make it configurable? */
+static const struct built_in_command bltins[] = {
+       BLTIN("."       , builtin_source  , "Run commands in a file"),
+       BLTIN(":"       , builtin_true    , "No-op"),
+       BLTIN("["       , builtin_test    , "Test condition"),
+#if ENABLE_HUSH_JOB
+       BLTIN("bg"      , builtin_fg_bg   , "Resume a job in the background"),
+#endif
+#if ENABLE_HUSH_LOOPS
+       BLTIN("break"   , builtin_break   , "Exit from a loop"),
+#endif
+       BLTIN("cd"      , builtin_cd      , "Change directory"),
+#if ENABLE_HUSH_LOOPS
+       BLTIN("continue", builtin_continue, "Start new loop iteration"),
+#endif
+       BLTIN("echo"    , builtin_echo    , "Write to stdout"),
+       BLTIN("eval"    , builtin_eval    , "Construct and run shell command"),
+       BLTIN("exec"    , builtin_exec    , "Execute command, don't return to shell"),
+       BLTIN("exit"    , builtin_exit    , "Exit"),
+       BLTIN("export"  , builtin_export  , "Set environment variable"),
+#if ENABLE_HUSH_JOB
+       BLTIN("fg"      , builtin_fg_bg   , "Bring job into the foreground"),
+#endif
+#if ENABLE_HUSH_HELP
+       BLTIN("help"    , builtin_help    , "List shell built-in commands"),
+#endif
+#if ENABLE_HUSH_JOB
+       BLTIN("jobs"    , builtin_jobs    , "List active jobs"),
+#endif
+#if HUSH_DEBUG
+       BLTIN("memleak" , builtin_memleak , "Debug tool"),
+#endif
+       BLTIN("pwd"     , builtin_pwd     , "Print current directory"),
+       BLTIN("read"    , builtin_read    , "Input environment variable"),
+#if ENABLE_HUSH_FUNCTIONS
+       BLTIN("return"  , builtin_return  , "Return from a function"),
+#endif
+       BLTIN("set"     , builtin_set     , "Set/unset shell local variables"),
+       BLTIN("shift"   , builtin_shift   , "Shift positional parameters"),
+       BLTIN("test"    , builtin_test    , "Test condition"),
+       BLTIN("trap"    , builtin_trap    , "Trap signals"),
+//     BLTIN("ulimit"  , builtin_return  , "Control resource limits"),
+       BLTIN("umask"   , builtin_umask   , "Set file creation mask"),
+       BLTIN("unset"   , builtin_unset   , "Unset environment variable"),
+       BLTIN("wait"    , builtin_wait    , "Wait for process"),
+};
+
+
+/* Debug printouts.
+ */
+#if HUSH_DEBUG
+/* prevent disasters with G.debug_indent < 0 */
+# define indent() fprintf(stderr, "%*s", (G.debug_indent * 2) & 0xff, "")
+# define debug_enter() (G.debug_indent++)
+# define debug_leave() (G.debug_indent--)
+#else
+# define indent()    ((void)0)
+# define debug_enter() ((void)0)
+# define debug_leave() ((void)0)
+#endif
+
+#ifndef debug_printf
+# define debug_printf(...) (indent(), fprintf(stderr, __VA_ARGS__))
+#endif
+
+#ifndef debug_printf_parse
+# define debug_printf_parse(...) (indent(), fprintf(stderr, __VA_ARGS__))
+#endif
+
+#ifndef debug_printf_exec
+#define debug_printf_exec(...) (indent(), fprintf(stderr, __VA_ARGS__))
+#endif
+
+#ifndef debug_printf_env
+# define debug_printf_env(...) (indent(), fprintf(stderr, __VA_ARGS__))
+#endif
+
+#ifndef debug_printf_jobs
+# define debug_printf_jobs(...) (indent(), fprintf(stderr, __VA_ARGS__))
+# define DEBUG_JOBS 1
+#else
+# define DEBUG_JOBS 0
+#endif
+
+#ifndef debug_printf_expand
+# define debug_printf_expand(...) (indent(), fprintf(stderr, __VA_ARGS__))
+# define DEBUG_EXPAND 1
+#else
+# define DEBUG_EXPAND 0
+#endif
+
+#ifndef debug_printf_glob
+# define debug_printf_glob(...) (indent(), fprintf(stderr, __VA_ARGS__))
+# define DEBUG_GLOB 1
+#else
+# define DEBUG_GLOB 0
+#endif
+
+#ifndef debug_printf_list
+# define debug_printf_list(...) (indent(), fprintf(stderr, __VA_ARGS__))
+#endif
+
+#ifndef debug_printf_subst
+# define debug_printf_subst(...) (indent(), fprintf(stderr, __VA_ARGS__))
+#endif
+
+#ifndef debug_printf_clean
+# define debug_printf_clean(...) (indent(), fprintf(stderr, __VA_ARGS__))
+# define DEBUG_CLEAN 1
+#else
+# define DEBUG_CLEAN 0
+#endif
+
+#if DEBUG_EXPAND
+static void debug_print_strings(const char *prefix, char **vv)
+{
+       indent();
+       fprintf(stderr, "%s:\n", prefix);
+       while (*vv)
+               fprintf(stderr, " '%s'\n", *vv++);
+}
+#else
+#define debug_print_strings(prefix, vv) ((void)0)
+#endif
+
+
+/* Leak hunting. Use hush_leaktool.sh for post-processing.
+ */
+#if LEAK_HUNTING
+static void *xxmalloc(int lineno, size_t size)
+{
+       void *ptr = xmalloc((size + 0xff) & ~0xff);
+       fdprintf(2, "line %d: malloc %p\n", lineno, ptr);
+       return ptr;
+}
+static void *xxrealloc(int lineno, void *ptr, size_t size)
+{
+       ptr = xrealloc(ptr, (size + 0xff) & ~0xff);
+       fdprintf(2, "line %d: realloc %p\n", lineno, ptr);
+       return ptr;
+}
+static char *xxstrdup(int lineno, const char *str)
+{
+       char *ptr = xstrdup(str);
+       fdprintf(2, "line %d: strdup %p\n", lineno, ptr);
+       return ptr;
+}
+static void xxfree(void *ptr)
+{
+       fdprintf(2, "free %p\n", ptr);
+       free(ptr);
+}
+#define xmalloc(s)     xxmalloc(__LINE__, s)
+#define xrealloc(p, s) xxrealloc(__LINE__, p, s)
+#define xstrdup(s)     xxstrdup(__LINE__, s)
+#define free(p)        xxfree(p)
+#endif
+
+
+/* Syntax and runtime errors. They always abort scripts.
+ * In interactive use they usually discard unparsed and/or unexecuted commands
+ * and return to the prompt.
+ * HUSH_DEBUG >= 2 prints line number in this file where it was detected.
+ */
+#if HUSH_DEBUG < 2
+# define die_if_script(lineno, fmt...)          die_if_script(fmt)
+# define syntax_error(lineno, msg)              syntax_error(msg)
+# define syntax_error_at(lineno, msg)           syntax_error_at(msg)
+# define syntax_error_unterm_ch(lineno, ch)     syntax_error_unterm_ch(ch)
+# define syntax_error_unterm_str(lineno, s)     syntax_error_unterm_str(s)
+# define syntax_error_unexpected_ch(lineno, ch) syntax_error_unexpected_ch(ch)
+#endif
+
+static void die_if_script(unsigned lineno, const char *fmt, ...)
+{
+       va_list p;
+
+#if HUSH_DEBUG >= 2
+       bb_error_msg("hush.c:%u", lineno);
+#endif
+       va_start(p, fmt);
+       bb_verror_msg(fmt, p, NULL);
+       va_end(p);
+       if (!G_interactive_fd)
+               xfunc_die();
+}
+
+static void syntax_error(unsigned lineno, const char *msg)
+{
+       if (msg)
+               die_if_script(lineno, "syntax error: %s", msg);
+       else
+               die_if_script(lineno, "syntax error", NULL);
+}
+
+static void syntax_error_at(unsigned lineno, const char *msg)
+{
+       die_if_script(lineno, "syntax error at '%s'", msg);
+}
+
+/* It so happens that all such cases are totally fatal
+ * even if shell is interactive: EOF while looking for closing
+ * delimiter. There is nowhere to read stuff from after that,
+ * it's EOF! The only choice is to terminate.
+ */
+static void syntax_error_unterm_ch(unsigned lineno, char ch) NORETURN;
+static void syntax_error_unterm_ch(unsigned lineno, char ch)
+{
+       char msg[2];
+       msg[0] = ch;
+       msg[1] = '\0';
+       die_if_script(lineno, "syntax error: unterminated %s", msg);
+       xfunc_die();
+}
+
+static void syntax_error_unterm_str(unsigned lineno, const char *s)
+{
+       die_if_script(lineno, "syntax error: unterminated %s", s);
+}
+
+static void syntax_error_unexpected_ch(unsigned lineno, int ch)
+{
+       char msg[2];
+       msg[0] = ch;
+       msg[1] = '\0';
+       die_if_script(lineno, "syntax error: unexpected %s", ch == EOF ? "EOF" : msg);
+}
+
+#if HUSH_DEBUG < 2
+# undef die_if_script
+# undef syntax_error
+# undef syntax_error_at
+# undef syntax_error_unterm_ch
+# undef syntax_error_unterm_str
+# undef syntax_error_unexpected_ch
+#else
+# define die_if_script(fmt...)          die_if_script(__LINE__, fmt)
+# define syntax_error(msg)              syntax_error(__LINE__, msg)
+# define syntax_error_at(msg)           syntax_error_at(__LINE__, msg)
+# define syntax_error_unterm_ch(ch)     syntax_error_unterm_ch(__LINE__, ch)
+# define syntax_error_unterm_str(s)     syntax_error_unterm_str(__LINE__, s)
+# define syntax_error_unexpected_ch(ch) syntax_error_unexpected_ch(__LINE__, ch)
+#endif
+
+
+#if ENABLE_HUSH_INTERACTIVE
+static void cmdedit_update_prompt(void);
+#else
+# define cmdedit_update_prompt()
+#endif
+
+
+/* Utility functions
+ */
+static int glob_needed(const char *s)
+{
+       while (*s) {
+               if (*s == '\\')
+                       s++;
+               if (*s == '*' || *s == '[' || *s == '?')
+                       return 1;
+               s++;
+       }
+       return 0;
+}
+
+static int is_well_formed_var_name(const char *s, char terminator)
+{
+       if (!s || !(isalpha(*s) || *s == '_'))
+               return 0;
+       s++;
+       while (isalnum(*s) || *s == '_')
+               s++;
+       return *s == terminator;
+}
+
+/* Replace each \x with x in place, return ptr past NUL. */
+static char *unbackslash(char *src)
+{
+       char *dst = src;
+       while (1) {
+               if (*src == '\\')
+                       src++;
+               if ((*dst++ = *src++) == '\0')
+                       break;
+       }
+       return dst;
+}
+
+static char **add_strings_to_strings(char **strings, char **add, int need_to_dup)
+{
+       int i;
+       unsigned count1;
+       unsigned count2;
+       char **v;
+
+       v = strings;
+       count1 = 0;
+       if (v) {
+               while (*v) {
+                       count1++;
+                       v++;
+               }
+       }
+       count2 = 0;
+       v = add;
+       while (*v) {
+               count2++;
+               v++;
+       }
+       v = xrealloc(strings, (count1 + count2 + 1) * sizeof(char*));
+       v[count1 + count2] = NULL;
+       i = count2;
+       while (--i >= 0)
+               v[count1 + i] = (need_to_dup ? xstrdup(add[i]) : add[i]);
+       return v;
+}
+#if LEAK_HUNTING
+static char **xx_add_strings_to_strings(int lineno, char **strings, char **add, int need_to_dup)
+{
+       char **ptr = add_strings_to_strings(strings, add, need_to_dup);
+       fdprintf(2, "line %d: add_strings_to_strings %p\n", lineno, ptr);
+       return ptr;
+}
+#define add_strings_to_strings(strings, add, need_to_dup) \
+       xx_add_strings_to_strings(__LINE__, strings, add, need_to_dup)
+#endif
+
+/* Note: takes ownership of "add" ptr (it is not strdup'ed) */
+static char **add_string_to_strings(char **strings, char *add)
+{
+       char *v[2];
+       v[0] = add;
+       v[1] = NULL;
+       return add_strings_to_strings(strings, v, /*dup:*/ 0);
+}
+#if LEAK_HUNTING
+static char **xx_add_string_to_strings(int lineno, char **strings, char *add)
+{
+       char **ptr = add_string_to_strings(strings, add);
+       fdprintf(2, "line %d: add_string_to_strings %p\n", lineno, ptr);
+       return ptr;
+}
+#define add_string_to_strings(strings, add) \
+       xx_add_string_to_strings(__LINE__, strings, add)
+#endif
+
+static void free_strings(char **strings)
+{
+       char **v;
+
+       if (!strings)
+               return;
+       v = strings;
+       while (*v) {
+               free(*v);
+               v++;
+       }
+       free(strings);
+}
+
+
+/* Helpers for setting new $n and restoring them back
+ */
+typedef struct save_arg_t {
+       char *sv_argv0;
+       char **sv_g_argv;
+       int sv_g_argc;
+       smallint sv_g_malloced;
+} save_arg_t;
+
+static void save_and_replace_G_args(save_arg_t *sv, char **argv)
+{
+       int n;
+
+       sv->sv_argv0 = argv[0];
+       sv->sv_g_argv = G.global_argv;
+       sv->sv_g_argc = G.global_argc;
+       sv->sv_g_malloced = G.global_args_malloced;
+
+       argv[0] = G.global_argv[0]; /* retain $0 */
+       G.global_argv = argv;
+       G.global_args_malloced = 0;
+
+       n = 1;
+       while (*++argv)
+               n++;
+       G.global_argc = n;
+}
+
+static void restore_G_args(save_arg_t *sv, char **argv)
+{
+       char **pp;
+
+       if (G.global_args_malloced) {
+               /* someone ran "set -- arg1 arg2 ...", undo */
+               pp = G.global_argv;
+               while (*++pp) /* note: does not free $0 */
+                       free(*pp);
+               free(G.global_argv);
+       }
+       argv[0] = sv->sv_argv0;
+       G.global_argv = sv->sv_g_argv;
+       G.global_argc = sv->sv_g_argc;
+       G.global_args_malloced = sv->sv_g_malloced;
+}
+
+
+/* Basic theory of signal handling in shell
+ * ========================================
+ * This does not describe what hush does, rather, it is current understanding
+ * what it _should_ do. If it doesn't, it's a bug.
+ * http://www.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#trap
+ *
+ * Signals are handled only after each pipe ("cmd | cmd | cmd" thing)
+ * is finished or backgrounded. It is the same in interactive and
+ * non-interactive shells, and is the same regardless of whether
+ * a user trap handler is installed or a shell special one is in effect.
+ * ^C or ^Z from keyboard seem to execute "at once" because it usually
+ * backgrounds (i.e. stops) or kills all members of currently running
+ * pipe.
+ *
+ * Wait builtin in interruptible by signals for which user trap is set
+ * or by SIGINT in interactive shell.
+ *
+ * Trap handlers will execute even within trap handlers. (right?)
+ *
+ * User trap handlers are forgotten when subshell ("(cmd)") is entered.
+ *
+ * If job control is off, backgrounded commands ("cmd &")
+ * have SIGINT, SIGQUIT set to SIG_IGN.
+ *
+ * Commands run in command substitution ("`cmd`")
+ * have SIGTTIN, SIGTTOU, SIGTSTP set to SIG_IGN.
+ *
+ * Ordinary commands have signals set to SIG_IGN/DFL set as inherited
+ * by the shell from its parent.
+ *
+ * Siganls which differ from SIG_DFL action
+ * (note: child (i.e., [v]forked) shell is not an interactive shell):
+ *
+ * SIGQUIT: ignore
+ * SIGTERM (interactive): ignore
+ * SIGHUP (interactive):
+ *    send SIGCONT to stopped jobs, send SIGHUP to all jobs and exit
+ * SIGTTIN, SIGTTOU, SIGTSTP (if job control is on): ignore
+ *    Note that ^Z is handled not by trapping SIGTSTP, but by seeing
+ *    that all pipe members are stopped. Try this in bash:
+ *    while :; do :; done - ^Z does not background it
+ *    (while :; do :; done) - ^Z backgrounds it
+ * SIGINT (interactive): wait for last pipe, ignore the rest
+ *    of the command line, show prompt. NB: ^C does not send SIGINT
+ *    to interactive shell while shell is waiting for a pipe,
+ *    since shell is bg'ed (is not in foreground process group).
+ *    Example 1: this waits 5 sec, but does not execute ls:
+ *    "echo $$; sleep 5; ls -l" + "kill -INT <pid>"
+ *    Example 2: this does not wait and does not execute ls:
+ *    "echo $$; sleep 5 & wait; ls -l" + "kill -INT <pid>"
+ *    Example 3: this does not wait 5 sec, but executes ls:
+ *    "sleep 5; ls -l" + press ^C
+ *
+ * (What happens to signals which are IGN on shell start?)
+ * (What happens with signal mask on shell start?)
+ *
+ * Implementation in hush
+ * ======================
+ * We use in-kernel pending signal mask to determine which signals were sent.
+ * We block all signals which we don't want to take action immediately,
+ * i.e. we block all signals which need to have special handling as described
+ * above, and all signals which have traps set.
+ * After each pipe execution, we extract any pending signals via sigtimedwait()
+ * and act on them.
+ *
+ * unsigned non_DFL_mask: a mask of such "special" signals
+ * sigset_t blocked_set:  current blocked signal set
+ *
+ * "trap - SIGxxx":
+ *    clear bit in blocked_set unless it is also in non_DFL_mask
+ * "trap 'cmd' SIGxxx":
+ *    set bit in blocked_set (even if 'cmd' is '')
+ * after [v]fork, if we plan to be a shell:
+ *    unblock signals with special interactive handling
+ *    (child shell is not interactive),
+ *    unset all traps (note: regardless of child shell's type - {}, (), etc)
+ * after [v]fork, if we plan to exec:
+ *    POSIX says pending signal mask is cleared in child - no need to clear it.
+ *    Restore blocked signal set to one inherited by shell just prior to exec.
+ *
+ * Note: as a result, we do not use signal handlers much. The only uses
+ * are to count SIGCHLDs
+ * and to restore tty pgrp on signal-induced exit.
+ */
+enum {
+       SPECIAL_INTERACTIVE_SIGS = 0
+               | (1 << SIGTERM)
+               | (1 << SIGINT)
+               | (1 << SIGHUP)
+               ,
+       SPECIAL_JOB_SIGS = 0
+#if ENABLE_HUSH_JOB
+               | (1 << SIGTTIN)
+               | (1 << SIGTTOU)
+               | (1 << SIGTSTP)
+#endif
+};
+
+#if ENABLE_HUSH_FAST
+static void SIGCHLD_handler(int sig UNUSED_PARAM)
+{
+       G.count_SIGCHLD++;
+//bb_error_msg("[%d] SIGCHLD_handler: G.count_SIGCHLD:%d G.handled_SIGCHLD:%d", getpid(), G.count_SIGCHLD, G.handled_SIGCHLD);
+}
+#endif
+
+#if ENABLE_HUSH_JOB
+
+/* After [v]fork, in child: do not restore tty pgrp on xfunc death */
+#define disable_restore_tty_pgrp_on_exit() (die_sleep = 0)
+/* After [v]fork, in parent: restore tty pgrp on xfunc death */
+#define enable_restore_tty_pgrp_on_exit()  (die_sleep = -1)
+
+/* Restores tty foreground process group, and exits.
+ * May be called as signal handler for fatal signal
+ * (will resend signal to itself, producing correct exit state)
+ * or called directly with -EXITCODE.
+ * We also call it if xfunc is exiting. */
+static void sigexit(int sig) NORETURN;
+static void sigexit(int sig)
+{
+       /* Disable all signals: job control, SIGPIPE, etc. */
+       sigprocmask_allsigs(SIG_BLOCK);
+
+       /* Careful: we can end up here after [v]fork. Do not restore
+        * tty pgrp then, only top-level shell process does that */
+       if (G_saved_tty_pgrp && getpid() == G.root_pid)
+               tcsetpgrp(G_interactive_fd, G_saved_tty_pgrp);
+
+       /* Not a signal, just exit */
+       if (sig <= 0)
+               _exit(- sig);
+
+       kill_myself_with_sig(sig); /* does not return */
+}
+#else
+
+#define disable_restore_tty_pgrp_on_exit() ((void)0)
+#define enable_restore_tty_pgrp_on_exit()  ((void)0)
+
+#endif
+
+/* Restores tty foreground process group, and exits. */
+static void hush_exit(int exitcode) NORETURN;
+static void hush_exit(int exitcode)
+{
+       if (G.exiting <= 0 && G.traps && G.traps[0] && G.traps[0][0]) {
+               /* Prevent recursion:
+                * trap "echo Hi; exit" EXIT; exit
+                */
+               char *argv[] = { NULL, G.traps[0], NULL };
+               G.traps[0] = NULL;
+               G.exiting = 1;
+               builtin_eval(argv);
+               free(argv[1]);
+       }
+
+#if ENABLE_HUSH_JOB
+       fflush(NULL); /* flush all streams */
+       sigexit(- (exitcode & 0xff));
+#else
+       exit(exitcode);
+#endif
+}
+
+static int check_and_run_traps(int sig)
+{
+       static const struct timespec zero_timespec = { 0, 0 };
+       smalluint save_rcode;
+       int last_sig = 0;
+
+       if (sig)
+               goto jump_in;
+       while (1) {
+               sig = sigtimedwait(&G.blocked_set, NULL, &zero_timespec);
+               if (sig <= 0)
+                       break;
+ jump_in:
+               last_sig = sig;
+               if (G.traps && G.traps[sig]) {
+                       if (G.traps[sig][0]) {
+                               /* We have user-defined handler */
+                               char *argv[] = { NULL, xstrdup(G.traps[sig]), NULL };
+                               save_rcode = G.last_exitcode;
+                               builtin_eval(argv);
+                               free(argv[1]);
+                               G.last_exitcode = save_rcode;
+                       } /* else: "" trap, ignoring signal */
+                       continue;
+               }
+               /* not a trap: special action */
+               switch (sig) {
+#if ENABLE_HUSH_FAST
+               case SIGCHLD:
+                       G.count_SIGCHLD++;
+//bb_error_msg("[%d] check_and_run_traps: G.count_SIGCHLD:%d G.handled_SIGCHLD:%d", getpid(), G.count_SIGCHLD, G.handled_SIGCHLD);
+                       break;
+#endif
+               case SIGINT:
+                       /* Builtin was ^C'ed, make it look prettier: */
+                       bb_putchar('\n');
+                       G.flag_SIGINT = 1;
+                       break;
+#if ENABLE_HUSH_JOB
+               case SIGHUP: {
+                       struct pipe *job;
+                       /* bash is observed to signal whole process groups,
+                        * not individual processes */
+                       for (job = G.job_list; job; job = job->next) {
+                               if (job->pgrp <= 0)
+                                       continue;
+                               debug_printf_exec("HUPing pgrp %d\n", job->pgrp);
+                               if (kill(- job->pgrp, SIGHUP) == 0)
+                                       kill(- job->pgrp, SIGCONT);
+                       }
+                       sigexit(SIGHUP);
+               }
+#endif
+               default: /* ignored: */
+                       /* SIGTERM, SIGQUIT, SIGTTIN, SIGTTOU, SIGTSTP */
+                       break;
+               }
+       }
+       return last_sig;
+}
+
+
+static const char *set_cwd(void)
+{
+       /* xrealloc_getcwd_or_warn(arg) calls free(arg),
+        * we must not try to free(bb_msg_unknown) */
+       if (G.cwd == bb_msg_unknown)
+               G.cwd = NULL;
+       G.cwd = xrealloc_getcwd_or_warn((char *)G.cwd);
+       if (!G.cwd)
+               G.cwd = bb_msg_unknown;
+       return G.cwd;
+}
+
+
+/*
+ * Shell and environment variable support
+ */
+static struct variable **get_ptr_to_local_var(const char *name)
+{
+       struct variable **pp;
+       struct variable *cur;
+       int len;
+
+       len = strlen(name);
+       pp = &G.top_var;
+       while ((cur = *pp) != NULL) {
+               if (strncmp(cur->varstr, name, len) == 0 && cur->varstr[len] == '=')
+                       return pp;
+               pp = &cur->next;
+       }
+       return NULL;
+}
+
+static struct variable *get_local_var(const char *name)
+{
+       struct variable **pp = get_ptr_to_local_var(name);
+       if (pp)
+               return *pp;
+       return NULL;
+}
+
+static const char *get_local_var_value(const char *name)
+{
+       struct variable **pp = get_ptr_to_local_var(name);
+       if (pp)
+               return strchr((*pp)->varstr, '=') + 1;
+       return NULL;
+}
+
+/* str holds "NAME=VAL" and is expected to be malloced.
+ * We take ownership of it.
+ * flg_export:
+ *  0: do not change export flag
+ *     (if creating new variable, flag will be 0)
+ *  1: set export flag and putenv the variable
+ * -1: clear export flag and unsetenv the variable
+ * flg_read_only is set only when we handle -R var=val
+ */
+#if BB_MMU
+#define set_local_var(str, flg_export, flg_read_only) \
+       set_local_var(str, flg_export)
+#endif
+static int set_local_var(char *str, int flg_export, int flg_read_only)
+{
+       struct variable *cur;
+       char *eq_sign;
+       int name_len;
+
+       eq_sign = strchr(str, '=');
+       if (!eq_sign) { /* not expected to ever happen? */
+               free(str);
+               return -1;
+       }
+
+       name_len = eq_sign - str + 1; /* including '=' */
+       cur = G.top_var; /* cannot be NULL (we have HUSH_VERSION and it's RO) */
+       while (1) {
+               if (strncmp(cur->varstr, str, name_len) != 0) {
+                       if (!cur->next) {
+                               /* Bail out. Note that now cur points
+                                * to last var in linked list */
+                               break;
+                       }
+                       cur = cur->next;
+                       continue;
+               }
+               /* We found an existing var with this name */
+               if (cur->flg_read_only) {
+#if !BB_MMU
+                       if (!flg_read_only)
+#endif
+                               bb_error_msg("%s: readonly variable", str);
+                       free(str);
+                       return -1;
+               }
+               if (flg_export == -1) {
+                       debug_printf_env("%s: unsetenv '%s'\n", __func__, str);
+                       *eq_sign = '\0';
+                       unsetenv(str);
+                       *eq_sign = '=';
+               }
+               if (strcmp(cur->varstr + name_len, eq_sign + 1) == 0) {
+ free_and_exp:
+                       free(str);
+                       goto exp;
+               }
+               if (cur->max_len >= strlen(str)) {
+                       /* This one is from startup env, reuse space */
+                       strcpy(cur->varstr, str);
+                       goto free_and_exp;
+               }
+               /* max_len == 0 signifies "malloced" var, which we can
+                * (and has to) free */
+               if (!cur->max_len)
+                       free(cur->varstr);
+               cur->max_len = 0;
+               goto set_str_and_exp;
+       }
+
+       /* Not found - create next variable struct */
+       cur->next = xzalloc(sizeof(*cur));
+       cur = cur->next;
+
+ set_str_and_exp:
+       cur->varstr = str;
+#if !BB_MMU
+       cur->flg_read_only = flg_read_only;
+#endif
+ exp:
+       if (flg_export == 1)
+               cur->flg_export = 1;
+       if (name_len == 4 && cur->varstr[0] == 'P' && cur->varstr[1] == 'S')
+               cmdedit_update_prompt();
+       if (cur->flg_export) {
+               if (flg_export == -1) {
+                       cur->flg_export = 0;
+                       /* unsetenv was already done */
+               } else {
+                       debug_printf_env("%s: putenv '%s'\n", __func__, cur->varstr);
+                       return putenv(cur->varstr);
+               }
+       }
+       return 0;
+}
+
+static int unset_local_var_len(const char *name, int name_len)
+{
+       struct variable *cur;
+       struct variable **var_pp;
+
+       if (!name)
+               return EXIT_SUCCESS;
+       var_pp = &G.top_var;
+       while ((cur = *var_pp) != NULL) {
+               if (strncmp(cur->varstr, name, name_len) == 0 && cur->varstr[name_len] == '=') {
+                       if (cur->flg_read_only) {
+                               bb_error_msg("%s: readonly variable", name);
+                               return EXIT_FAILURE;
+                       }
+                       *var_pp = cur->next;
+                       debug_printf_env("%s: unsetenv '%s'\n", __func__, cur->varstr);
+                       bb_unsetenv(cur->varstr);
+                       if (name_len == 3 && cur->varstr[0] == 'P' && cur->varstr[1] == 'S')
+                               cmdedit_update_prompt();
+                       if (!cur->max_len)
+                               free(cur->varstr);
+                       free(cur);
+                       return EXIT_SUCCESS;
+               }
+               var_pp = &cur->next;
+       }
+       return EXIT_SUCCESS;
+}
+
+static int unset_local_var(const char *name)
+{
+       return unset_local_var_len(name, strlen(name));
+}
+
+static void unset_vars(char **strings)
+{
+       char **v;
+
+       if (!strings)
+               return;
+       v = strings;
+       while (*v) {
+               const char *eq = strchrnul(*v, '=');
+               unset_local_var_len(*v, (int)(eq - *v));
+               v++;
+       }
+       free(strings);
+}
+
+#if ENABLE_SH_MATH_SUPPORT
+#define is_name(c)      ((c) == '_' || isalpha((unsigned char)(c)))
+#define is_in_name(c)   ((c) == '_' || isalnum((unsigned char)(c)))
+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;
+}
+
+static void arith_set_local_var(const char *name, const char *val, int flags)
+{
+       /* arith code doesnt malloc space, so do it for it */
+       char *var = xasprintf("%s=%s", name, val);
+       set_local_var(var, flags, 0);
+}
+#endif
+
+
+/*
+ * Helpers for "var1=val1 var2=val2 cmd" feature
+ */
+static void add_vars(struct variable *var)
+{
+       struct variable *next;
+
+       while (var) {
+               next = var->next;
+               var->next = G.top_var;
+               G.top_var = var;
+               if (var->flg_export) {
+                       debug_printf_env("%s: restoring exported '%s'\n", __func__, var->varstr);
+                       putenv(var->varstr);
+               } else {
+                       debug_printf_env("%s: restoring local '%s'\n", __func__, var->varstr);
+               }
+               var = next;
+       }
+}
+
+static struct variable *set_vars_and_save_old(char **strings)
+{
+       char **s;
+       struct variable *old = NULL;
+
+       if (!strings)
+               return old;
+       s = strings;
+       while (*s) {
+               struct variable *var_p;
+               struct variable **var_pp;
+               char *eq;
+
+               eq = strchr(*s, '=');
+               if (eq) {
+                       *eq = '\0';
+                       var_pp = get_ptr_to_local_var(*s);
+                       *eq = '=';
+                       if (var_pp) {
+                               /* Remove variable from global linked list */
+                               var_p = *var_pp;
+                               debug_printf_env("%s: removing '%s'\n", __func__, var_p->varstr);
+                               *var_pp = var_p->next;
+                               /* Add it to returned list */
+                               var_p->next = old;
+                               old = var_p;
+                       }
+                       set_local_var(*s, 1, 0);
+               }
+               s++;
+       }
+       return old;
+}
+
+
+/*
+ * in_str support
+ */
+static int static_get(struct in_str *i)
+{
+       int ch = *i->p++;
+       if (ch != '\0')
+               return ch;
+       i->p--;
+       return EOF;
+}
+
+static int static_peek(struct in_str *i)
+{
+       return *i->p;
+}
+
+#if ENABLE_HUSH_INTERACTIVE
+
+static void cmdedit_update_prompt(void)
+{
+       if (ENABLE_FEATURE_EDITING_FANCY_PROMPT) {
+               G.PS1 = get_local_var_value("PS1");
+               if (G.PS1 == NULL)
+                       G.PS1 = "\\w \\$ ";
+               G.PS2 = get_local_var_value("PS2");
+       } else {
+               G.PS1 = NULL;
+       }
+       if (G.PS2 == NULL)
+               G.PS2 = "> ";
+}
+
+static const char* setup_prompt_string(int promptmode)
+{
+       const char *prompt_str;
+       debug_printf("setup_prompt_string %d ", promptmode);
+       if (!ENABLE_FEATURE_EDITING_FANCY_PROMPT) {
+               /* Set up the prompt */
+               if (promptmode == 0) { /* PS1 */
+                       free((char*)G.PS1);
+                       G.PS1 = xasprintf("%s %c ", G.cwd, (geteuid() != 0) ? '$' : '#');
+                       prompt_str = G.PS1;
+               } else
+                       prompt_str = G.PS2;
+       } else
+               prompt_str = (promptmode == 0) ? G.PS1 : G.PS2;
+       debug_printf("result '%s'\n", prompt_str);
+       return prompt_str;
+}
+
+static void get_user_input(struct in_str *i)
+{
+       int r;
+       const char *prompt_str;
+
+       prompt_str = setup_prompt_string(i->promptmode);
+#if ENABLE_FEATURE_EDITING
+       /* Enable command line editing only while a command line
+        * is actually being read */
+       do {
+               G.flag_SIGINT = 0;
+               /* buglet: SIGINT will not make new prompt to appear _at once_,
+                * only after <Enter>. (^C will work) */
+               r = read_line_input(prompt_str, G.user_input_buf, BUFSIZ-1, G.line_input_state);
+               /* catch *SIGINT* etc (^C is handled by read_line_input) */
+               check_and_run_traps(0);
+       } while (r == 0 || G.flag_SIGINT); /* repeat if ^C or SIGINT */
+       i->eof_flag = (r < 0);
+       if (i->eof_flag) { /* EOF/error detected */
+               G.user_input_buf[0] = EOF; /* yes, it will be truncated, it's ok */
+               G.user_input_buf[1] = '\0';
+       }
+#else
+       do {
+               G.flag_SIGINT = 0;
+               fputs(prompt_str, stdout);
+               fflush(stdout);
+               G.user_input_buf[0] = r = fgetc(i->file);
+               /*G.user_input_buf[1] = '\0'; - already is and never changed */
+//do we need check_and_run_traps(0)? (maybe only if stdin)
+       } while (G.flag_SIGINT);
+       i->eof_flag = (r == EOF);
+#endif
+       i->p = G.user_input_buf;
+}
+
+#endif  /* INTERACTIVE */
+
+/* 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;
+
+       /* If there is data waiting, eat it up */
+       if (i->p && *i->p) {
+#if ENABLE_HUSH_INTERACTIVE
+ take_cached:
+#endif
+               ch = *i->p++;
+               if (i->eof_flag && !*i->p)
+                       ch = EOF;
+               /* note: ch is never NUL */
+       } else {
+               /* need to double check i->file because we might be doing something
+                * more complicated by now, like sourcing or substituting. */
+#if ENABLE_HUSH_INTERACTIVE
+               if (G_interactive_fd && i->promptme && i->file == stdin) {
+                       do {
+                               get_user_input(i);
+                       } while (!*i->p); /* need non-empty line */
+                       i->promptmode = 1; /* PS2 */
+                       i->promptme = 0;
+                       goto take_cached;
+               }
+#endif
+               do ch = fgetc(i->file); while (ch == '\0');
+       }
+       debug_printf("file_get: got '%c' %d\n", ch, ch);
+#if ENABLE_HUSH_INTERACTIVE
+       if (ch == '\n')
+               i->promptme = 1;
+#endif
+       return ch;
+}
+
+/* All 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)
+{
+       int ch;
+       if (i->p && *i->p) {
+               if (i->eof_flag && !i->p[1])
+                       return EOF;
+               return *i->p;
+               /* note: ch is never NUL */
+       }
+       do ch = fgetc(i->file); while (ch == '\0');
+       i->eof_flag = (ch == EOF);
+       i->peek_buf[0] = ch;
+       i->peek_buf[1] = '\0';
+       i->p = i->peek_buf;
+       debug_printf("file_peek: got '%c' %d\n", ch, ch);
+       return ch;
+}
+
+static void setup_file_in_str(struct in_str *i, FILE *f)
+{
+       i->peek = file_peek;
+       i->get = file_get;
+#if ENABLE_HUSH_INTERACTIVE
+       i->promptme = 1;
+       i->promptmode = 0; /* PS1 */
+#endif
+       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;
+#if ENABLE_HUSH_INTERACTIVE
+       i->promptme = 1;
+       i->promptmode = 0; /* PS1 */
+#endif
+       i->p = s;
+       i->eof_flag = 0;
+}
+
+
+/*
+ * o_string support
+ */
+#define B_CHUNK  (32 * sizeof(char*))
+
+static void o_reset_to_empty_unquoted(o_string *o)
+{
+       o->length = 0;
+       o->o_quoted = 0;
+       if (o->data)
+               o->data[0] = '\0';
+}
+
+static void o_free(o_string *o)
+{
+       free(o->data);
+       memset(o, 0, sizeof(*o));
+}
+
+static ALWAYS_INLINE void o_free_unsafe(o_string *o)
+{
+       free(o->data);
+}
+
+static void o_grow_by(o_string *o, int len)
+{
+       if (o->length + len > o->maxlen) {
+               o->maxlen += (2*len > B_CHUNK ? 2*len : B_CHUNK);
+               o->data = xrealloc(o->data, 1 + o->maxlen);
+       }
+}
+
+static void o_addchr(o_string *o, int ch)
+{
+       debug_printf("o_addchr: '%c' o->length=%d o=%p\n", ch, o->length, o);
+       o_grow_by(o, 1);
+       o->data[o->length] = ch;
+       o->length++;
+       o->data[o->length] = '\0';
+}
+
+static void o_addblock(o_string *o, const char *str, int len)
+{
+       o_grow_by(o, len);
+       memcpy(&o->data[o->length], str, len);
+       o->length += len;
+       o->data[o->length] = '\0';
+}
+
+#if !BB_MMU
+static void o_addstr(o_string *o, const char *str)
+{
+       o_addblock(o, str, strlen(str));
+}
+static void nommu_addchr(o_string *o, int ch)
+{
+       if (o)
+               o_addchr(o, ch);
+}
+#else
+#define nommu_addchr(o, str) ((void)0)
+#endif
+
+static void o_addstr_with_NUL(o_string *o, const char *str)
+{
+       o_addblock(o, str, strlen(str) + 1);
+}
+
+static void o_addblock_duplicate_backslash(o_string *o, const char *str, int len)
+{
+       while (len) {
+               o_addchr(o, *str);
+               if (*str++ == '\\'
+                && (*str != '*' && *str != '?' && *str != '[')
+               ) {
+                       o_addchr(o, '\\');
+               }
+               len--;
+       }
+}
+
+/* My analysis of quoting semantics tells me that state information
+ * is associated with a destination, not a source.
+ */
+static void o_addqchr(o_string *o, int ch)
+{
+       int sz = 1;
+       char *found = strchr("*?[\\", ch);
+       if (found)
+               sz++;
+       o_grow_by(o, sz);
+       if (found) {
+               o->data[o->length] = '\\';
+               o->length++;
+       }
+       o->data[o->length] = ch;
+       o->length++;
+       o->data[o->length] = '\0';
+}
+
+static void o_addQchr(o_string *o, int ch)
+{
+       int sz = 1;
+       if (o->o_escape && strchr("*?[\\", ch)) {
+               sz++;
+               o->data[o->length] = '\\';
+               o->length++;
+       }
+       o_grow_by(o, sz);
+       o->data[o->length] = ch;
+       o->length++;
+       o->data[o->length] = '\0';
+}
+
+static void o_addQstr(o_string *o, const char *str, int len)
+{
+       if (!o->o_escape) {
+               o_addblock(o, str, len);
+               return;
+       }
+       while (len) {
+               char ch;
+               int sz;
+               int ordinary_cnt = strcspn(str, "*?[\\");
+               if (ordinary_cnt > len) /* paranoia */
+                       ordinary_cnt = len;
+               o_addblock(o, str, ordinary_cnt);
+               if (ordinary_cnt == len)
+                       return;
+               str += ordinary_cnt;
+               len -= ordinary_cnt + 1; /* we are processing + 1 char below */
+
+               ch = *str++;
+               sz = 1;
+               if (ch) { /* it is necessarily one of "*?[\\" */
+                       sz++;
+                       o->data[o->length] = '\\';
+                       o->length++;
+               }
+               o_grow_by(o, sz);
+               o->data[o->length] = ch;
+               o->length++;
+               o->data[o->length] = '\0';
+       }
+}
+
+/* A special kind of o_string for $VAR and `cmd` expansion.
+ * It contains char* list[] at the beginning, which is grown in 16 element
+ * increments. Actual string data starts at the next multiple of 16 * (char*).
+ * list[i] contains an INDEX (int!) into this string data.
+ * It means that if list[] needs to grow, data needs to be moved higher up
+ * but list[i]'s need not be modified.
+ * NB: remembering how many list[i]'s you have there is crucial.
+ * o_finalize_list() operation post-processes this structure - calculates
+ * and stores actual char* ptrs in list[]. Oh, it NULL terminates it as well.
+ */
+#if DEBUG_EXPAND || DEBUG_GLOB
+static void debug_print_list(const char *prefix, o_string *o, int n)
+{
+       char **list = (char**)o->data;
+       int string_start = ((n + 0xf) & ~0xf) * sizeof(list[0]);
+       int i = 0;
+
+       indent();
+       fprintf(stderr, "%s: list:%p n:%d string_start:%d length:%d maxlen:%d\n",
+                       prefix, list, n, string_start, o->length, o->maxlen);
+       while (i < n) {
+               indent();
+               fprintf(stderr, " list[%d]=%d '%s' %p\n", i, (int)list[i],
+                               o->data + (int)list[i] + string_start,
+                               o->data + (int)list[i] + string_start);
+               i++;
+       }
+       if (n) {
+               const char *p = o->data + (int)list[n - 1] + string_start;
+               indent();
+               fprintf(stderr, " total_sz:%ld\n", (long)((p + strlen(p) + 1) - o->data));
+       }
+}
+#else
+#define debug_print_list(prefix, o, n) ((void)0)
+#endif
+
+/* n = o_save_ptr_helper(str, n) "starts new string" by storing an index value
+ * in list[n] so that it points past last stored byte so far.
+ * It returns n+1. */
+static int o_save_ptr_helper(o_string *o, int n)
+{
+       char **list = (char**)o->data;
+       int string_start;
+       int string_len;
+
+       if (!o->has_empty_slot) {
+               string_start = ((n + 0xf) & ~0xf) * sizeof(list[0]);
+               string_len = o->length - string_start;
+               if (!(n & 0xf)) { /* 0, 0x10, 0x20...? */
+                       debug_printf_list("list[%d]=%d string_start=%d (growing)\n", n, string_len, string_start);
+                       /* list[n] points to string_start, make space for 16 more pointers */
+                       o->maxlen += 0x10 * sizeof(list[0]);
+                       o->data = xrealloc(o->data, o->maxlen + 1);
+                       list = (char**)o->data;
+                       memmove(list + n + 0x10, list + n, string_len);
+                       o->length += 0x10 * sizeof(list[0]);
+               } else {
+                       debug_printf_list("list[%d]=%d string_start=%d\n",
+                                       n, string_len, string_start);
+               }
+       } else {
+               /* We have empty slot at list[n], reuse without growth */
+               string_start = ((n+1 + 0xf) & ~0xf) * sizeof(list[0]); /* NB: n+1! */
+               string_len = o->length - string_start;
+               debug_printf_list("list[%d]=%d string_start=%d (empty slot)\n",
+                               n, string_len, string_start);
+               o->has_empty_slot = 0;
+       }
+       list[n] = (char*)(ptrdiff_t)string_len;
+       return n + 1;
+}
+
+/* "What was our last o_save_ptr'ed position (byte offset relative o->data)?" */
+static int o_get_last_ptr(o_string *o, int n)
+{
+       char **list = (char**)o->data;
+       int string_start = ((n + 0xf) & ~0xf) * sizeof(list[0]);
+
+       return ((int)(ptrdiff_t)list[n-1]) + string_start;
+}
+
+/* o_glob performs globbing on last list[], saving each result
+ * as a new list[]. */
+static int o_glob(o_string *o, int n)
+{
+       glob_t globdata;
+       int gr;
+       char *pattern;
+
+       debug_printf_glob("start o_glob: n:%d o->data:%p\n", n, o->data);
+       if (!o->data)
+               return o_save_ptr_helper(o, n);
+       pattern = o->data + o_get_last_ptr(o, n);
+       debug_printf_glob("glob pattern '%s'\n", pattern);
+       if (!glob_needed(pattern)) {
+ literal:
+               o->length = unbackslash(pattern) - o->data;
+               debug_printf_glob("glob pattern '%s' is literal\n", pattern);
+               return o_save_ptr_helper(o, n);
+       }
+
+       memset(&globdata, 0, sizeof(globdata));
+       gr = glob(pattern, 0, NULL, &globdata);
+       debug_printf_glob("glob('%s'):%d\n", pattern, gr);
+       if (gr == GLOB_NOSPACE)
+               bb_error_msg_and_die("out of memory during glob");
+       if (gr == GLOB_NOMATCH) {
+               globfree(&globdata);
+               goto literal;
+       }
+       if (gr != 0) { /* GLOB_ABORTED ? */
+               /* TODO: testcase for bad glob pattern behavior */
+               bb_error_msg("glob(3) error %d on '%s'", gr, pattern);
+       }
+       if (globdata.gl_pathv && globdata.gl_pathv[0]) {
+               char **argv = globdata.gl_pathv;
+               o->length = pattern - o->data; /* "forget" pattern */
+               while (1) {
+                       o_addstr_with_NUL(o, *argv);
+                       n = o_save_ptr_helper(o, n);
+                       argv++;
+                       if (!*argv)
+                               break;
+               }
+       }
+       globfree(&globdata);
+       if (DEBUG_GLOB)
+               debug_print_list("o_glob returning", o, n);
+       return n;
+}
+
+/* If o->o_glob == 1, glob the string so far remembered.
+ * Otherwise, just finish current list[] and start new */
+static int o_save_ptr(o_string *o, int n)
+{
+       if (o->o_glob) { /* if globbing is requested */
+               /* If o->has_empty_slot, list[n] was already globbed
+                * (if it was requested back then when it was filled)
+                * so don't do that again! */
+               if (!o->has_empty_slot)
+                       return o_glob(o, n); /* o_save_ptr_helper is inside */
+       }
+       return o_save_ptr_helper(o, n);
+}
+
+/* "Please convert list[n] to real char* ptrs, and NULL terminate it." */
+static char **o_finalize_list(o_string *o, int n)
+{
+       char **list;
+       int string_start;
+
+       n = o_save_ptr(o, n); /* force growth for list[n] if necessary */
+       if (DEBUG_EXPAND)
+               debug_print_list("finalized", o, n);
+       debug_printf_expand("finalized n:%d\n", n);
+       list = (char**)o->data;
+       string_start = ((n + 0xf) & ~0xf) * sizeof(list[0]);
+       list[--n] = NULL;
+       while (n) {
+               n--;
+               list[n] = o->data + (int)(ptrdiff_t)list[n] + string_start;
+       }
+       return list;
+}
+
+
+/* Expansion can recurse */
+#if ENABLE_HUSH_TICK
+static int process_command_subs(o_string *dest, const char *s);
+#endif
+static char *expand_string_to_string(const char *str);
+#if BB_MMU
+#define parse_stream_dquoted(as_string, dest, input, dquote_end) \
+       parse_stream_dquoted(dest, input, dquote_end)
+#endif
+static int parse_stream_dquoted(o_string *as_string,
+               o_string *dest,
+               struct in_str *input,
+               int dquote_end);
+
+/* expand_strvec_to_strvec() takes a list of strings, expands
+ * all variable references within and returns a pointer to
+ * a list of expanded strings, possibly with larger number
+ * of strings. (Think VAR="a b"; echo $VAR).
+ * This new list is allocated as a single malloc block.
+ * NULL-terminated list of char* pointers is at the beginning of it,
+ * followed by strings themself.
+ * Caller can deallocate entire list by single free(list). */
+
+/* Store given string, finalizing the word and starting new one whenever
+ * we encounter IFS char(s). This is used for expanding variable values.
+ * End-of-string does NOT finalize word: think about 'echo -$VAR-' */
+static int expand_on_ifs(o_string *output, int n, const char *str)
+{
+       while (1) {
+               int word_len = strcspn(str, G.ifs);
+               if (word_len) {
+                       if (output->o_escape || !output->o_glob)
+                               o_addQstr(output, str, word_len);
+                       else /* protect backslashes against globbing up :) */
+                               o_addblock_duplicate_backslash(output, str, word_len);
+                       str += word_len;
+               }
+               if (!*str)  /* EOL - do not finalize word */
+                       break;
+               o_addchr(output, '\0');
+               debug_print_list("expand_on_ifs", output, n);
+               n = o_save_ptr(output, n);
+               str += strspn(str, G.ifs); /* skip ifs chars */
+       }
+       debug_print_list("expand_on_ifs[1]", output, n);
+       return n;
+}
+
+/* Helper to expand $((...)) and heredoc body. These act as if
+ * they are in double quotes, with the exception that they are not :).
+ * Just the rules are similar: "expand only $var and `cmd`"
+ *
+ * Returns malloced string.
+ * As an optimization, we return NULL if expansion is not needed.
+ */
+static char *expand_pseudo_dquoted(const char *str)
+{
+       char *exp_str;
+       struct in_str input;
+       o_string dest = NULL_O_STRING;
+
+       if (strchr(str, '$') == NULL
+#if ENABLE_HUSH_TICK
+        && strchr(str, '`') == NULL
+#endif
+       ) {
+               return NULL;
+       }
+
+       /* We need to expand. Example:
+        * echo $(($a + `echo 1`)) $((1 + $((2)) ))
+        */
+       setup_string_in_str(&input, str);
+       parse_stream_dquoted(NULL, &dest, &input, EOF);
+       //bb_error_msg("'%s' -> '%s'", str, dest.data);
+       exp_str = expand_string_to_string(dest.data);
+       //bb_error_msg("'%s' -> '%s'", dest.data, exp_str);
+       o_free_unsafe(&dest);
+       return exp_str;
+}
+
+/* Expand all variable references in given string, adding words to list[]
+ * at n, n+1,... positions. Return updated n (so that list[n] is next one
+ * to be filled). This routine is extremely tricky: has to deal with
+ * variables/parameters with whitespace, $* and $@, and constructs like
+ * 'echo -$*-'. If you play here, you must run testsuite afterwards! */
+static int expand_vars_to_list(o_string *output, int n, char *arg, char or_mask)
+{
+       /* or_mask is either 0 (normal case) or 0x80 -
+        * expansion of right-hand side of assignment == 1-element expand.
+        * It will also do no globbing, and thus we must not backslash-quote!
+        */
+       char ored_ch;
+       char *p;
+
+       ored_ch = 0;
+
+       debug_printf_expand("expand_vars_to_list: arg '%s'\n", arg);
+       debug_print_list("expand_vars_to_list", output, n);
+       n = o_save_ptr(output, n);
+       debug_print_list("expand_vars_to_list[0]", output, n);
+
+       while ((p = strchr(arg, SPECIAL_VAR_SYMBOL)) != NULL) {
+               char first_ch;
+               int i;
+               char *dyn_val = NULL;
+               const char *val = NULL;
+#if ENABLE_HUSH_TICK
+               o_string subst_result = NULL_O_STRING;
+#endif
+#if ENABLE_SH_MATH_SUPPORT
+               char arith_buf[sizeof(arith_t)*3 + 2];
+#endif
+               o_addblock(output, arg, p - arg);
+               debug_print_list("expand_vars_to_list[1]", output, n);
+               arg = ++p;
+               p = strchr(p, SPECIAL_VAR_SYMBOL);
+
+               first_ch = arg[0] | or_mask; /* forced to "quoted" if or_mask = 0x80 */
+               /* "$@" is special. Even if quoted, it can still
+                * expand to nothing (not even an empty string) */
+               if ((first_ch & 0x7f) != '@')
+                       ored_ch |= first_ch;
+
+               switch (first_ch & 0x7f) {
+               /* Highest bit in first_ch indicates that var is double-quoted */
+               case '$': /* pid */
+                       val = utoa(G.root_pid);
+                       break;
+               case '!': /* bg pid */
+                       val = G.last_bg_pid ? utoa(G.last_bg_pid) : (char*)"";
+                       break;
+               case '?': /* exitcode */
+                       val = utoa(G.last_exitcode);
+                       break;
+               case '#': /* argc */
+                       if (arg[1] != SPECIAL_VAR_SYMBOL)
+                               /* actually, it's a ${#var} */
+                               goto case_default;
+                       val = utoa(G.global_argc ? G.global_argc-1 : 0);
+                       break;
+               case '*':
+               case '@':
+                       i = 1;
+                       if (!G.global_argv[i])
+                               break;
+                       ored_ch |= first_ch; /* do it for "$@" _now_, when we know it's not empty */
+                       if (!(first_ch & 0x80)) { /* unquoted $* or $@ */
+                               smallint sv = output->o_escape;
+                               /* unquoted var's contents should be globbed, so don't escape */
+                               output->o_escape = 0;
+                               while (G.global_argv[i]) {
+                                       n = expand_on_ifs(output, n, G.global_argv[i]);
+                                       debug_printf_expand("expand_vars_to_list: argv %d (last %d)\n", i, G.global_argc - 1);
+                                       if (G.global_argv[i++][0] && G.global_argv[i]) {
+                                               /* this argv[] is not empty and not last:
+                                                * put terminating NUL, start new word */
+                                               o_addchr(output, '\0');
+                                               debug_print_list("expand_vars_to_list[2]", output, n);
+                                               n = o_save_ptr(output, n);
+                                               debug_print_list("expand_vars_to_list[3]", output, n);
+                                       }
+                               }
+                               output->o_escape = sv;
+                       } else
+                       /* If or_mask is nonzero, we handle assignment 'a=....$@.....'
+                        * and in this case should treat it like '$*' - see 'else...' below */
+                       if (first_ch == ('@'|0x80) && !or_mask) { /* quoted $@ */
+                               while (1) {
+                                       o_addQstr(output, G.global_argv[i], strlen(G.global_argv[i]));
+                                       if (++i >= G.global_argc)
+                                               break;
+                                       o_addchr(output, '\0');
+                                       debug_print_list("expand_vars_to_list[4]", output, n);
+                                       n = o_save_ptr(output, n);
+                               }
+                       } else { /* quoted $*: add as one word */
+                               while (1) {
+                                       o_addQstr(output, G.global_argv[i], strlen(G.global_argv[i]));
+                                       if (!G.global_argv[++i])
+                                               break;
+                                       if (G.ifs[0])
+                                               o_addchr(output, G.ifs[0]);
+                               }
+                       }
+                       break;
+               case SPECIAL_VAR_SYMBOL: /* <SPECIAL_VAR_SYMBOL><SPECIAL_VAR_SYMBOL> */
+                       /* "Empty variable", used to make "" etc to not disappear */
+                       arg++;
+                       ored_ch = 0x80;
+                       break;
+#if ENABLE_HUSH_TICK
+               case '`': /* <SPECIAL_VAR_SYMBOL>`cmd<SPECIAL_VAR_SYMBOL> */
+                       *p = '\0';
+                       arg++;
+                       /* Can't just stuff it into output o_string,
+                        * expanded result may need to be globbed
+                        * and $IFS-splitted */
+                       debug_printf_subst("SUBST '%s' first_ch %x\n", arg, first_ch);
+                       process_command_subs(&subst_result, arg);
+                       debug_printf_subst("SUBST RES '%s'\n", subst_result.data);
+                       val = subst_result.data;
+                       goto store_val;
+#endif
+#if ENABLE_SH_MATH_SUPPORT
+               case '+': { /* <SPECIAL_VAR_SYMBOL>+cmd<SPECIAL_VAR_SYMBOL> */
+                       arith_eval_hooks_t hooks;
+                       arith_t res;
+                       int errcode;
+                       char *exp_str;
+
+                       arg++; /* skip '+' */
+                       *p = '\0'; /* replace trailing <SPECIAL_VAR_SYMBOL> */
+                       debug_printf_subst("ARITH '%s' first_ch %x\n", arg, first_ch);
+
+                       exp_str = expand_pseudo_dquoted(arg);
+                       hooks.lookupvar = get_local_var_value;
+                       hooks.setvar = arith_set_local_var;
+                       hooks.endofname = endofname;
+                       res = arith(exp_str ? exp_str : arg, &errcode, &hooks);
+                       free(exp_str);
+
+                       if (errcode < 0) {
+                               const char *msg = "error in arithmetic";
+                               switch (errcode) {
+                               case -3:
+                                       msg = "exponent less than 0";
+                                       break;
+                               case -2:
+                                       msg = "divide by 0";
+                                       break;
+                               case -5:
+                                       msg = "expression recursion loop detected";
+                                       break;
+                               }
+                               die_if_script(msg);
+                       }
+                       debug_printf_subst("ARITH RES '"arith_t_fmt"'\n", res);
+                       sprintf(arith_buf, arith_t_fmt, res);
+                       val = arith_buf;
+                       break;
+               }
+#endif
+               default: /* <SPECIAL_VAR_SYMBOL>varname<SPECIAL_VAR_SYMBOL> */
+               case_default: {
+                       bool exp_len = false;
+                       bool exp_null = false;
+                       char *var = arg;
+                       char exp_save = exp_save; /* for compiler */
+                       char exp_op = exp_op; /* for compiler */
+                       char *exp_word = exp_word; /* for compiler */
+                       size_t exp_off = 0;
+
+                       *p = '\0';
+                       arg[0] = first_ch & 0x7f;
+
+                       /* prepare for expansions */
+                       if (var[0] == '#') {
+                               /* handle length expansion ${#var} */
+                               exp_len = true;
+                               ++var;
+                       } else {
+                               /* maybe handle parameter expansion */
+                               exp_off = strcspn(var, ":-=+?%#");
+                               if (!var[exp_off])
+                                       exp_off = 0;
+                               if (exp_off) {
+                                       exp_save = var[exp_off];
+                                       exp_null = exp_save == ':';
+                                       exp_word = var + exp_off;
+                                       if (exp_null)
+                                               ++exp_word;
+                                       exp_op = *exp_word++;
+                                       var[exp_off] = '\0';
+                               }
+                       }
+
+                       /* lookup the variable in question */
+                       if (isdigit(var[0])) {
+                               /* handle_dollar() should have vetted var for us */
+                               i = xatoi_u(var);
+                               if (i < G.global_argc)
+                                       val = G.global_argv[i];
+                               /* else val remains NULL: $N with too big N */
+                       } else
+                               val = get_local_var_value(var);
+
+                       /* handle any expansions */
+                       if (exp_len) {
+                               debug_printf_expand("expand: length of '%s' = ", val);
+                               val = utoa(val ? strlen(val) : 0);
+                               debug_printf_expand("%s\n", val);
+                       } else if (exp_off) {
+                               if (exp_op == '%' || exp_op == '#') {
+                                       if (val) {
+                                               /* we need to do a pattern match */
+                                               bool match_at_left;
+                                               char *loc;
+                                               scan_t scan = pick_scan(exp_op, *exp_word, &match_at_left);
+                                               if (exp_op == *exp_word)        /* ## or %% */
+                                                       ++exp_word;
+                                               val = dyn_val = xstrdup(val);
+                                               loc = scan(dyn_val, exp_word, match_at_left);
+                                               if (match_at_left) /* # or ## */
+                                                       val = loc;
+                                               else if (loc) /* % or %% and match was found */
+                                                       *loc = '\0';
+                                       }
+                               } else {
+                                       /* we need to do an expansion */
+                                       int exp_test = (!val || (exp_null && !val[0]));
+                                       if (exp_op == '+')
+                                               exp_test = !exp_test;
+                                       debug_printf_expand("expand: op:%c (null:%s) test:%i\n", exp_op,
+                                               exp_null ? "true" : "false", exp_test);
+                                       if (exp_test) {
+                                               if (exp_op == '?') {
+//TODO: how interactive bash aborts expansion mid-command?
+                                                       /* ${var?[error_msg_if_unset]} */
+                                                       /* ${var:?[error_msg_if_unset_or_null]} */
+                                                       /* mimic bash message */
+                                                       die_if_script("%s: %s",
+                                                               var,
+                                                               exp_word[0] ? exp_word : "parameter null or not set"
+                                                       );
+                                               } else {
+                                                       val = exp_word;
+                                               }
+
+                                               if (exp_op == '=') {
+                                                       /* ${var=[word]} or ${var:=[word]} */
+                                                       if (isdigit(var[0]) || var[0] == '#') {
+                                                               /* mimic bash message */
+                                                               die_if_script("$%s: cannot assign in this way", var);
+                                                               val = NULL;
+                                                       } else {
+                                                               char *new_var = xasprintf("%s=%s", var, val);
+                                                               set_local_var(new_var, 0, 0);
+                                                       }
+                                               }
+                                       }
+                               }
+
+                               var[exp_off] = exp_save;
+                       }
+
+                       arg[0] = first_ch;
+#if ENABLE_HUSH_TICK
+ store_val:
+#endif
+                       if (!(first_ch & 0x80)) { /* unquoted $VAR */
+                               debug_printf_expand("unquoted '%s', output->o_escape:%d\n", val, output->o_escape);
+                               if (val) {
+                                       /* unquoted var's contents should be globbed, so don't escape */
+                                       smallint sv = output->o_escape;
+                                       output->o_escape = 0;
+                                       n = expand_on_ifs(output, n, val);
+                                       val = NULL;
+                                       output->o_escape = sv;
+                               }
+                       } else { /* quoted $VAR, val will be appended below */
+                               debug_printf_expand("quoted '%s', output->o_escape:%d\n", val, output->o_escape);
+                       }
+               } /* default: */
+               } /* switch (char after <SPECIAL_VAR_SYMBOL>) */
+
+               if (val) {
+                       o_addQstr(output, val, strlen(val));
+               }
+               free(dyn_val);
+               /* Do the check to avoid writing to a const string */
+               if (*p != SPECIAL_VAR_SYMBOL)
+                       *p = SPECIAL_VAR_SYMBOL;
+
+#if ENABLE_HUSH_TICK
+               o_free(&subst_result);
+#endif
+               arg = ++p;
+       } /* end of "while (SPECIAL_VAR_SYMBOL is found) ..." */
+
+       if (arg[0]) {
+               debug_print_list("expand_vars_to_list[a]", output, n);
+               /* this part is literal, and it was already pre-quoted
+                * if needed (much earlier), do not use o_addQstr here! */
+               o_addstr_with_NUL(output, arg);
+               debug_print_list("expand_vars_to_list[b]", output, n);
+       } else if (output->length == o_get_last_ptr(output, n) /* expansion is empty */
+        && !(ored_ch & 0x80) /* and all vars were not quoted. */
+       ) {
+               n--;
+               /* allow to reuse list[n] later without re-growth */
+               output->has_empty_slot = 1;
+       } else {
+               o_addchr(output, '\0');
+       }
+       return n;
+}
+
+static char **expand_variables(char **argv, int or_mask)
+{
+       int n;
+       char **list;
+       char **v;
+       o_string output = NULL_O_STRING;
+
+       if (or_mask & 0x100) {
+               output.o_escape = 1; /* protect against globbing for "$var" */
+               /* (unquoted $var will temporarily switch it off) */
+               output.o_glob = 1;
+       }
+
+       n = 0;
+       v = argv;
+       while (*v) {
+               n = expand_vars_to_list(&output, n, *v, (char)or_mask);
+               v++;
+       }
+       debug_print_list("expand_variables", &output, n);
+
+       /* output.data (malloced in one block) gets returned in "list" */
+       list = o_finalize_list(&output, n);
+       debug_print_strings("expand_variables[1]", list);
+       return list;
+}
+
+static char **expand_strvec_to_strvec(char **argv)
+{
+       return expand_variables(argv, 0x100);
+}
+
+/* Used for expansion of right hand of assignments */
+/* NB: should NOT do globbing! "export v=/bin/c*; env | grep ^v=" outputs
+ * "v=/bin/c*" */
+static char *expand_string_to_string(const char *str)
+{
+       char *argv[2], **list;
+
+       argv[0] = (char*)str;
+       argv[1] = NULL;
+       list = expand_variables(argv, 0x80); /* 0x80: make one-element expansion */
+       if (HUSH_DEBUG)
+               if (!list[0] || list[1])
+                       bb_error_msg_and_die("BUG in varexp2");
+       /* actually, just move string 2*sizeof(char*) bytes back */
+       overlapping_strcpy((char*)list, list[0]);
+       unbackslash((char*)list);
+       debug_printf_expand("string_to_string='%s'\n", (char*)list);
+       return (char*)list;
+}
+
+/* Used for "eval" builtin */
+static char* expand_strvec_to_string(char **argv)
+{
+       char **list;
+
+       list = expand_variables(argv, 0x80);
+       /* Convert all NULs to spaces */
+       if (list[0]) {
+               int n = 1;
+               while (list[n]) {
+                       if (HUSH_DEBUG)
+                               if (list[n-1] + strlen(list[n-1]) + 1 != list[n])
+                                       bb_error_msg_and_die("BUG in varexp3");
+                       /* bash uses ' ' regardless of $IFS contents */
+                       list[n][-1] = ' ';
+                       n++;
+               }
+       }
+       overlapping_strcpy((char*)list, list[0]);
+       debug_printf_expand("strvec_to_string='%s'\n", (char*)list);
+       return (char*)list;
+}
+
+static char **expand_assignments(char **argv, int count)
+{
+       int i;
+       char **p = NULL;
+       /* Expand assignments into one string each */
+       for (i = 0; i < count; i++) {
+               p = add_string_to_strings(p, expand_string_to_string(argv[i]));
+       }
+       return p;
+}
+
+
+#if BB_MMU
+/* never called */
+void re_execute_shell(char ***to_free, const char *s, char *argv0, char **argv);
+
+static void reset_traps_to_defaults(void)
+{
+       /* This function is always called in a child shell
+        * after fork (not vfork, NOMMU doesn't use this function).
+        * Child shells are not interactive.
+        * SIGTTIN/SIGTTOU/SIGTSTP should not have special handling.
+        * Testcase: (while :; do :; done) + ^Z should background.
+        * Same goes for SIGTERM, SIGHUP, SIGINT.
+        */
+       unsigned sig;
+       unsigned mask;
+
+       if (!G.traps && !(G.non_DFL_mask & SPECIAL_INTERACTIVE_SIGS))
+               return;
+
+       /* Stupid. It can be done with *single* &= op, but we can't use
+        * the fact that G.blocked_set is implemented as a bitmask... */
+       mask = (SPECIAL_INTERACTIVE_SIGS >> 1);
+       sig = 1;
+       while (1) {
+               if (mask & 1)
+                       sigdelset(&G.blocked_set, sig);
+               mask >>= 1;
+               if (!mask)
+                       break;
+               sig++;
+       }
+
+       G.non_DFL_mask &= ~SPECIAL_INTERACTIVE_SIGS;
+       mask = G.non_DFL_mask;
+       if (G.traps) for (sig = 0; sig < NSIG; sig++, mask >>= 1) {
+               if (!G.traps[sig])
+                       continue;
+               free(G.traps[sig]);
+               G.traps[sig] = NULL;
+               /* There is no signal for 0 (EXIT) */
+               if (sig == 0)
+                       continue;
+               /* There was a trap handler, we are removing it.
+                * But if sig still has non-DFL handling,
+                * we should not unblock it. */
+               if (mask & 1)
+                       continue;
+               sigdelset(&G.blocked_set, sig);
+       }
+       sigprocmask(SIG_SETMASK, &G.blocked_set, NULL);
+}
+
+#else /* !BB_MMU */
+
+static void re_execute_shell(char ***to_free, const char *s, char *g_argv0, char **g_argv) NORETURN;
+static void re_execute_shell(char ***to_free, const char *s, char *g_argv0, char **g_argv)
+{
+       char param_buf[sizeof("-$%x:%x:%x:%x") + sizeof(unsigned) * 4];
+       char *heredoc_argv[4];
+       struct variable *cur;
+#if ENABLE_HUSH_FUNCTIONS
+       struct function *funcp;
+#endif
+       char **argv, **pp;
+       unsigned cnt;
+
+       if (!g_argv0) { /* heredoc */
+               argv = heredoc_argv;
+               argv[0] = (char *) G.argv0_for_re_execing;
+               argv[1] = (char *) "-<";
+               argv[2] = (char *) s;
+               argv[3] = NULL;
+               pp = &argv[3]; /* used as pointer to empty environment */
+               goto do_exec;
+       }
+
+       sprintf(param_buf, "-$%x:%x:%x" USE_HUSH_LOOPS(":%x")
+                       , (unsigned) G.root_pid
+                       , (unsigned) G.last_bg_pid
+                       , (unsigned) G.last_exitcode
+                       USE_HUSH_LOOPS(, G.depth_of_loop)
+                       );
+       /* 1:hush 2:-$<pid>:<pid>:<exitcode>:<depth> <vars...> <funcs...>
+        * 3:-c 4:<cmd> 5:<arg0> <argN...> 6:NULL
+        */
+       cnt = 6;
+       for (cur = G.top_var; cur; cur = cur->next) {
+               if (!cur->flg_export || cur->flg_read_only)
+                       cnt += 2;
+       }
+#if ENABLE_HUSH_FUNCTIONS
+       for (funcp = G.top_func; funcp; funcp = funcp->next)
+               cnt += 3;
+#endif
+       pp = g_argv;
+       while (*pp++)
+               cnt++;
+       *to_free = argv = pp = xzalloc(sizeof(argv[0]) * cnt);
+       *pp++ = (char *) G.argv0_for_re_execing;
+       *pp++ = param_buf;
+       for (cur = G.top_var; cur; cur = cur->next) {
+               if (cur->varstr == hush_version_str)
+                       continue;
+               if (cur->flg_read_only) {
+                       *pp++ = (char *) "-R";
+                       *pp++ = cur->varstr;
+               } else if (!cur->flg_export) {
+                       *pp++ = (char *) "-V";
+                       *pp++ = cur->varstr;
+               }
+       }
+#if ENABLE_HUSH_FUNCTIONS
+       for (funcp = G.top_func; funcp; funcp = funcp->next) {
+               *pp++ = (char *) "-F";
+               *pp++ = funcp->name;
+               *pp++ = funcp->body_as_string;
+       }
+#endif
+       /* We can pass activated traps here. Say, -Tnn:trap_string
+        *
+        * However, POSIX says that subshells reset signals with traps
+        * to SIG_DFL.
+        * I tested bash-3.2 and it not only does that with true subshells
+        * of the form ( list ), but with any forked children shells.
+        * I set trap "echo W" WINCH; and then tried:
+        *
+        * { echo 1; sleep 20; echo 2; } &
+        * while true; do echo 1; sleep 20; echo 2; break; done &
+        * true | { echo 1; sleep 20; echo 2; } | cat
+        *
+        * In all these cases sending SIGWINCH to the child shell
+        * did not run the trap. If I add trap "echo V" WINCH;
+        * _inside_ group (just before echo 1), it works.
+        *
+        * I conclude it means we don't need to pass active traps here.
+        * exec syscall below resets them to SIG_DFL for us.
+        */
+       *pp++ = (char *) "-c";
+       *pp++ = (char *) s;
+       *pp++ = g_argv0;
+       while (*g_argv)
+               *pp++ = *g_argv++;
+       /* *pp = NULL; - is already there */
+       pp = environ;
+
+ do_exec:
+       debug_printf_exec("re_execute_shell pid:%d cmd:'%s'\n", getpid(), s);
+       sigprocmask(SIG_SETMASK, &G.inherited_set, NULL);
+       execve(bb_busybox_exec_path, argv, pp);
+       /* Fallback. Useful for init=/bin/hush usage etc */
+       if (argv[0][0] == '/')
+               execve(argv[0], argv, pp);
+       xfunc_error_retval = 127;
+       bb_error_msg_and_die("can't re-execute the shell");
+}
+#endif  /* !BB_MMU */
+
+
+static void setup_heredoc(struct redir_struct *redir)
+{
+       struct fd_pair pair;
+       pid_t pid;
+       int len, written;
+       /* the _body_ of heredoc (misleading field name) */
+       const char *heredoc = redir->rd_filename;
+       char *expanded;
+#if !BB_MMU
+       char **to_free;
+#endif
+
+       expanded = NULL;
+       if (!(redir->rd_dup & HEREDOC_QUOTED)) {
+               expanded = expand_pseudo_dquoted(heredoc);
+               if (expanded)
+                       heredoc = expanded;
+       }
+       len = strlen(heredoc);
+
+       close(redir->rd_fd); /* often saves dup2+close in xmove_fd */
+       xpiped_pair(pair);
+       xmove_fd(pair.rd, redir->rd_fd);
+
+       /* Try writing without forking. Newer kernels have
+        * dynamically growing pipes. Must use non-blocking write! */
+       ndelay_on(pair.wr);
+       while (1) {
+               written = write(pair.wr, heredoc, len);
+               if (written <= 0)
+                       break;
+               len -= written;
+               if (len == 0) {
+                       close(pair.wr);
+                       free(expanded);
+                       return;
+               }
+               heredoc += written;
+       }
+       ndelay_off(pair.wr);
+
+       /* Okay, pipe buffer was not big enough */
+       /* Note: we must not create a stray child (bastard? :)
+        * for the unsuspecting parent process. Child creates a grandchild
+        * and exits before parent execs the process which consumes heredoc
+        * (that exec happens after we return from this function) */
+#if !BB_MMU
+       to_free = NULL;
+#endif
+       pid = vfork();
+       if (pid < 0)
+               bb_perror_msg_and_die("vfork");
+       if (pid == 0) {
+               /* child */
+               disable_restore_tty_pgrp_on_exit();
+               pid = BB_MMU ? fork() : vfork();
+               if (pid < 0)
+                       bb_perror_msg_and_die(BB_MMU ? "fork" : "vfork");
+               if (pid != 0)
+                       _exit(0);
+               /* grandchild */
+               close(redir->rd_fd); /* read side of the pipe */
+#if BB_MMU
+               full_write(pair.wr, heredoc, len); /* may loop or block */
+               _exit(0);
+#else
+               /* Delegate blocking writes to another process */
+               xmove_fd(pair.wr, STDOUT_FILENO);
+               re_execute_shell(&to_free, heredoc, NULL, NULL);
+#endif
+       }
+       /* parent */
+#if ENABLE_HUSH_FAST
+       G.count_SIGCHLD++;
+//bb_error_msg("[%d] fork in setup_heredoc: G.count_SIGCHLD:%d G.handled_SIGCHLD:%d", getpid(), G.count_SIGCHLD, G.handled_SIGCHLD);
+#endif
+       enable_restore_tty_pgrp_on_exit();
+#if !BB_MMU
+       free(to_free);
+#endif
+       close(pair.wr);
+       free(expanded);
+       wait(NULL); /* wait till child has died */
+}
+
+/* squirrel != NULL means we squirrel away copies of stdin, stdout,
+ * and stderr if they are redirected. */
+static int setup_redirects(struct command *prog, int squirrel[])
+{
+       int openfd, mode;
+       struct redir_struct *redir;
+
+       for (redir = prog->redirects; redir; redir = redir->next) {
+               if (redir->rd_type == REDIRECT_HEREDOC2) {
+                       /* rd_fd<<HERE case */
+                       if (squirrel && redir->rd_fd < 3
+                        && squirrel[redir->rd_fd] < 0
+                       ) {
+                               squirrel[redir->rd_fd] = dup(redir->rd_fd);
+                       }
+                       /* for REDIRECT_HEREDOC2, rd_filename holds _contents_
+                        * of the heredoc */
+                       debug_printf_parse("set heredoc '%s'\n",
+                                       redir->rd_filename);
+                       setup_heredoc(redir);
+                       continue;
+               }
+
+               if (redir->rd_dup == REDIRFD_TO_FILE) {
+                       /* rd_fd<*>file case (<*> is <,>,>>,<>) */
+                       char *p;
+                       if (redir->rd_filename == NULL) {
+                               /* Something went wrong in the parse.
+                                * Pretend it didn't happen */
+                               bb_error_msg("bug in redirect parse");
+                               continue;
+                       }
+                       mode = redir_table[redir->rd_type].mode;
+                       p = expand_string_to_string(redir->rd_filename);
+                       openfd = open_or_warn(p, mode);
+                       free(p);
+                       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!) */
+//what the above comment tries to say?
+                               return 1;
+                       }
+               } else {
+                       /* rd_fd<*>rd_dup or rd_fd<*>- cases */
+                       openfd = redir->rd_dup;
+               }
+
+               if (openfd != redir->rd_fd) {
+                       if (squirrel && redir->rd_fd < 3
+                        && squirrel[redir->rd_fd] < 0
+                       ) {
+                               squirrel[redir->rd_fd] = dup(redir->rd_fd);
+                       }
+                       if (openfd == REDIRFD_CLOSE) {
+                               /* "n>-" means "close me" */
+                               close(redir->rd_fd);
+                       } else {
+                               xdup2(openfd, redir->rd_fd);
+                               if (redir->rd_dup == REDIRFD_TO_FILE)
+                                       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) {
+                       /* We simply die on error */
+                       xmove_fd(fd, i);
+               }
+       }
+}
+
+
+static void free_pipe_list(struct pipe *head);
+
+/* Return code is the exit status of the pipe */
+static void free_pipe(struct pipe *pi)
+{
+       char **p;
+       struct command *command;
+       struct redir_struct *r, *rnext;
+       int a, i;
+
+       if (pi->stopped_cmds > 0) /* why? */
+               return;
+       debug_printf_clean("run pipe: (pid %d)\n", getpid());
+       for (i = 0; i < pi->num_cmds; i++) {
+               command = &pi->cmds[i];
+               debug_printf_clean("  command %d:\n", i);
+               if (command->argv) {
+                       for (a = 0, p = command->argv; *p; a++, p++) {
+                               debug_printf_clean("   argv[%d] = %s\n", a, *p);
+                       }
+                       free_strings(command->argv);
+                       command->argv = NULL;
+               }
+               /* not "else if": on syntax error, we may have both! */
+               if (command->group) {
+                       debug_printf_clean("   begin group (grp_type:%d)\n",
+                                       command->grp_type);
+                       free_pipe_list(command->group);
+                       debug_printf_clean("   end group\n");
+                       command->group = NULL;
+               }
+               /* else is crucial here.
+                * If group != NULL, child_func is meaningless */
+#if ENABLE_HUSH_FUNCTIONS
+               else if (command->child_func) {
+                       debug_printf_exec("cmd %p releases child func at %p\n", command, command->child_func);
+                       command->child_func->parent_cmd = NULL;
+               }
+#endif
+#if !BB_MMU
+               free(command->group_as_string);
+               command->group_as_string = NULL;
+#endif
+               for (r = command->redirects; r; r = rnext) {
+                       debug_printf_clean("   redirect %d%s",
+                                       r->rd_fd, redir_table[r->rd_type].descrip);
+                       /* guard against the case >$FOO, where foo is unset or blank */
+                       if (r->rd_filename) {
+                               debug_printf_clean(" fname:'%s'\n", r->rd_filename);
+                               free(r->rd_filename);
+                               r->rd_filename = NULL;
+                       }
+                       debug_printf_clean(" rd_dup:%d\n", r->rd_dup);
+                       rnext = r->next;
+                       free(r);
+               }
+               command->redirects = NULL;
+       }
+       free(pi->cmds);   /* children are an array, they get freed all at once */
+       pi->cmds = NULL;
+#if ENABLE_HUSH_JOB
+       free(pi->cmdtext);
+       pi->cmdtext = NULL;
+#endif
+}
+
+static void free_pipe_list(struct pipe *head)
+{
+       struct pipe *pi, *next;
+
+       for (pi = head; pi; pi = next) {
+#if HAS_KEYWORDS
+               debug_printf_clean(" pipe reserved word %d\n", pi->res_word);
+#endif
+               free_pipe(pi);
+               debug_printf_clean("pipe followup code %d\n", pi->followup);
+               next = pi->next;
+               /*pi->next = NULL;*/
+               free(pi);
+       }
+}
+
+
+static int run_list(struct pipe *pi);
+#if BB_MMU
+#define parse_stream(pstring, input, end_trigger) \
+       parse_stream(input, end_trigger)
+#endif
+static struct pipe *parse_stream(char **pstring,
+               struct in_str *input,
+               int end_trigger);
+static void parse_and_run_string(const char *s);
+
+
+static const struct built_in_command* find_builtin(const char *name)
+{
+       const struct built_in_command *x;
+       for (x = bltins; x != &bltins[ARRAY_SIZE(bltins)]; x++) {
+               if (strcmp(name, x->cmd) != 0)
+                       continue;
+               debug_printf_exec("found builtin '%s'\n", name);
+               return x;
+       }
+       return NULL;
+}
+
+#if ENABLE_HUSH_FUNCTIONS
+static const struct function *find_function(const char *name)
+{
+       const struct function *funcp = G.top_func;
+       while (funcp) {
+               if (strcmp(name, funcp->name) == 0) {
+                       break;
+               }
+               funcp = funcp->next;
+       }
+       if (funcp)
+               debug_printf_exec("found function '%s'\n", name);
+       return funcp;
+}
+
+/* Note: takes ownership on name ptr */
+static struct function *new_function(char *name)
+{
+       struct function *funcp;
+       struct function **funcpp = &G.top_func;
+
+       while ((funcp = *funcpp) != NULL) {
+               struct command *cmd;
+
+               if (strcmp(funcp->name, name) != 0) {
+                       funcpp = &funcp->next;
+                       continue;
+               }
+
+               cmd = funcp->parent_cmd;
+               debug_printf_exec("func %p parent_cmd %p\n", funcp, cmd);
+               if (!cmd) {
+                       debug_printf_exec("freeing & replacing function '%s'\n", funcp->name);
+                       free(funcp->name);
+                       /* Note: if !funcp->body, do not free body_as_string!
+                        * This is a special case of "-F name body" function:
+                        * body_as_string was not malloced! */
+                       if (funcp->body) {
+                               free_pipe_list(funcp->body);
+# if !BB_MMU
+                               free(funcp->body_as_string);
+# endif
+                       }
+               } else {
+                       debug_printf_exec("reinserting in tree & replacing function '%s'\n", funcp->name);
+                       cmd->argv[0] = funcp->name;
+                       cmd->group = funcp->body;
+# if !BB_MMU
+                       cmd->group_as_string = funcp->body_as_string;
+# endif
+               }
+               goto skip;
+       }
+       debug_printf_exec("remembering new function '%s'\n", name);
+       funcp = *funcpp = xzalloc(sizeof(*funcp));
+       /*funcp->next = NULL;*/
+ skip:
+       funcp->name = name;
+       return funcp;
+}
+
+static void unset_func(const char *name)
+{
+       struct function *funcp;
+       struct function **funcpp = &G.top_func;
+
+       while ((funcp = *funcpp) != NULL) {
+               if (strcmp(funcp->name, name) == 0) {
+                       *funcpp = funcp->next;
+                       /* funcp is unlinked now, deleting it.
+                        * Note: if !funcp->body, the function was created by
+                        * "-F name body", do not free ->body_as_string
+                        * and ->name as they were not malloced. */
+                       if (funcp->body) {
+                               free_pipe_list(funcp->body);
+                               free(funcp->name);
+# if !BB_MMU
+                               free(funcp->body_as_string);
+# endif
+                       }
+                       free(funcp);
+                       break;
+               }
+               funcpp = &funcp->next;
+       }
+}
+
+# if BB_MMU
+#define exec_function(nommu_save, funcp, argv) \
+       exec_function(funcp, argv)
+# endif
+static void exec_function(nommu_save_t *nommu_save,
+               const struct function *funcp,
+               char **argv) NORETURN;
+static void exec_function(nommu_save_t *nommu_save,
+               const struct function *funcp,
+               char **argv)
+{
+# if BB_MMU
+       int n = 1;
+
+       argv[0] = G.global_argv[0];
+       G.global_argv = argv;
+       while (*++argv)
+               n++;
+       G.global_argc = n;
+       /* On MMU, funcp->body is always non-NULL */
+       n = run_list(funcp->body);
+       fflush(NULL);
+       _exit(n);
+# else
+       re_execute_shell(&nommu_save->argv_from_re_execing,
+                       funcp->body_as_string,
+                       G.global_argv[0],
+                       argv + 1);
+# endif
+}
+
+static int run_function(const struct function *funcp, char **argv)
+{
+       int rc;
+       save_arg_t sv;
+       smallint sv_flg;
+
+       save_and_replace_G_args(&sv, argv);
+       /* "we are in function, ok to use return" */
+       sv_flg = G.flag_return_in_progress;
+       G.flag_return_in_progress = -1;
+
+       /* On MMU, funcp->body is always non-NULL */
+# if !BB_MMU
+       if (!funcp->body) {
+               /* Function defined by -F */
+               parse_and_run_string(funcp->body_as_string);
+               rc = G.last_exitcode;
+       } else
+# endif
+       {
+               rc = run_list(funcp->body);
+       }
+
+       G.flag_return_in_progress = sv_flg;
+       restore_G_args(&sv, argv);
+
+       return rc;
+}
+#endif /* ENABLE_HUSH_FUNCTIONS */
+
+
+#if BB_MMU
+#define pseudo_exec_argv(nommu_save, argv, assignment_cnt, argv_expanded) \
+       pseudo_exec_argv(argv, assignment_cnt, argv_expanded)
+#define pseudo_exec(nommu_save, command, argv_expanded) \
+       pseudo_exec(command, argv_expanded)
+#endif
+
+/* Called after [v]fork() in run_pipe, or from builtin_exec.
+ * Never returns.
+ * Don't 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? -- because of vfork! (vda) */
+static void pseudo_exec_argv(nommu_save_t *nommu_save,
+               char **argv, int assignment_cnt,
+               char **argv_expanded) NORETURN;
+static void pseudo_exec_argv(nommu_save_t *nommu_save,
+               char **argv, int assignment_cnt,
+               char **argv_expanded)
+{
+       char **new_env;
+
+       /* Case when we are here: ... | var=val | ... */
+       if (!argv[assignment_cnt])
+               _exit(EXIT_SUCCESS);
+
+       new_env = expand_assignments(argv, assignment_cnt);
+#if BB_MMU
+       set_vars_and_save_old(new_env);
+       free(new_env); /* optional */
+       /* we can also destroy set_vars_and_save_old's return value,
+        * to save memory */
+#else
+       nommu_save->new_env = new_env;
+       nommu_save->old_vars = set_vars_and_save_old(new_env);
+#endif
+       if (argv_expanded) {
+               argv = argv_expanded;
+       } else {
+               argv = expand_strvec_to_strvec(argv + assignment_cnt);
+#if !BB_MMU
+               nommu_save->argv = argv;
+#endif
+       }
+
+#if ENABLE_FEATURE_SH_STANDALONE || BB_MMU
+       if (strchr(argv[0], '/') != NULL)
+               goto skip;
+#endif
+
+       /* On NOMMU, we must never block!
+        * Example: { sleep 99999 | read line } & echo Ok
+        * read builtin will block on read syscall, leaving parent blocked
+        * in vfork. Therefore we can't do this:
+        */
+#if BB_MMU
+       /* 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.
+        */
+       {
+               int rcode;
+               const struct built_in_command *x = find_builtin(argv[0]);
+               if (x) {
+                       rcode = x->function(argv);
+                       fflush(NULL);
+                       _exit(rcode);
+               }
+       }
+#endif
+#if ENABLE_HUSH_FUNCTIONS
+       /* Check if the command matches any functions */
+       {
+               const struct function *funcp = find_function(argv[0]);
+               if (funcp) {
+                       exec_function(nommu_save, funcp, argv);
+               }
+       }
+#endif
+
+#if ENABLE_FEATURE_SH_STANDALONE
+       /* Check if the command matches any busybox applets */
+       {
+               int a = find_applet_by_name(argv[0]);
+               if (a >= 0) {
+# if BB_MMU /* see above why on NOMMU it is not allowed */
+                       if (APPLET_IS_NOEXEC(a)) {
+                               debug_printf_exec("running applet '%s'\n", argv[0]);
+                               run_applet_no_and_exit(a, argv);
+                       }
+# endif
+                       /* Re-exec ourselves */
+                       debug_printf_exec("re-execing applet '%s'\n", argv[0]);
+                       sigprocmask(SIG_SETMASK, &G.inherited_set, NULL);
+                       execv(bb_busybox_exec_path, argv);
+                       /* If they called chroot or otherwise made the binary no longer
+                        * executable, fall through */
+               }
+       }
+#endif
+
+#if ENABLE_FEATURE_SH_STANDALONE || BB_MMU
+ skip:
+#endif
+       debug_printf_exec("execing '%s'\n", argv[0]);
+       sigprocmask(SIG_SETMASK, &G.inherited_set, NULL);
+       execvp(argv[0], argv);
+       bb_perror_msg("can't execute '%s'", argv[0]);
+       _exit(EXIT_FAILURE);
+}
+
+/* Called after [v]fork() in run_pipe
+ */
+static void pseudo_exec(nommu_save_t *nommu_save,
+               struct command *command,
+               char **argv_expanded) NORETURN;
+static void pseudo_exec(nommu_save_t *nommu_save,
+               struct command *command,
+               char **argv_expanded)
+{
+       if (command->argv) {
+               pseudo_exec_argv(nommu_save, command->argv,
+                               command->assignment_cnt, argv_expanded);
+       }
+
+       if (command->group) {
+               /* Cases when we are here:
+                * ( list )
+                * { list } &
+                * ... | ( list ) | ...
+                * ... | { list } | ...
+                */
+#if BB_MMU
+               int rcode;
+               debug_printf_exec("pseudo_exec: run_list\n");
+               reset_traps_to_defaults();
+               rcode = run_list(command->group);
+               /* OK to leak memory by not calling free_pipe_list,
+                * since this process is about to exit */
+               _exit(rcode);
+#else
+               re_execute_shell(&nommu_save->argv_from_re_execing,
+                               command->group_as_string,
+                               G.global_argv[0],
+                               G.global_argv + 1);
+#endif
+       }
+
+       /* Case when we are here: ... | >file */
+       debug_printf_exec("pseudo_exec'ed null command\n");
+       _exit(EXIT_SUCCESS);
+}
+
+#if ENABLE_HUSH_JOB
+static const char *get_cmdtext(struct pipe *pi)
+{
+       char **argv;
+       char *p;
+       int len;
+
+       /* This is subtle. ->cmdtext is created only on first backgrounding.
+        * (Think "cat, <ctrl-z>, fg, <ctrl-z>, fg, <ctrl-z>...." here...)
+        * On subsequent bg argv is trashed, but we won't use it */
+       if (pi->cmdtext)
+               return pi->cmdtext;
+       argv = pi->cmds[0].argv;
+       if (!argv || !argv[0]) {
+               pi->cmdtext = xzalloc(1);
+               return pi->cmdtext;
+       }
+
+       len = 0;
+       do {
+               len += strlen(*argv) + 1;
+       } while (*++argv);
+       p = xmalloc(len);
+       pi->cmdtext = p;
+       argv = pi->cmds[0].argv;
+       do {
+               len = strlen(*argv);
+               memcpy(p, *argv, len);
+               p += len;
+               *p++ = ' ';
+       } while (*++argv);
+       p[-1] = '\0';
+       return pi->cmdtext;
+}
+
+static void insert_bg_job(struct pipe *pi)
+{
+       struct pipe *job, **jobp;
+       int i;
+
+       /* Linear search for the ID of the job to use */
+       pi->jobid = 1;
+       for (job = G.job_list; job; job = job->next)
+               if (job->jobid >= pi->jobid)
+                       pi->jobid = job->jobid + 1;
+
+       /* Add job to the list of running jobs */
+       jobp = &G.job_list;
+       while ((job = *jobp) != NULL)
+               jobp = &job->next;
+       job = *jobp = xmalloc(sizeof(*job));
+
+       *job = *pi; /* physical copy */
+       job->next = NULL;
+       job->cmds = xzalloc(sizeof(pi->cmds[0]) * pi->num_cmds);
+       /* Cannot copy entire pi->cmds[] vector! This causes double frees */
+       for (i = 0; i < pi->num_cmds; i++) {
+               job->cmds[i].pid = pi->cmds[i].pid;
+               /* all other fields are not used and stay zero */
+       }
+       job->cmdtext = xstrdup(get_cmdtext(pi));
+
+       if (G_interactive_fd)
+               printf("[%d] %d %s\n", job->jobid, job->cmds[0].pid, job->cmdtext);
+       /* Last command's pid goes to $! */
+       G.last_bg_pid = job->cmds[job->num_cmds - 1].pid;
+       G.last_jobid = job->jobid;
+}
+
+static void remove_bg_job(struct pipe *pi)
+{
+       struct pipe *prev_pipe;
+
+       if (pi == G.job_list) {
+               G.job_list = pi->next;
+       } else {
+               prev_pipe = G.job_list;
+               while (prev_pipe->next != pi)
+                       prev_pipe = prev_pipe->next;
+               prev_pipe->next = pi->next;
+       }
+       if (G.job_list)
+               G.last_jobid = G.job_list->jobid;
+       else
+               G.last_jobid = 0;
+}
+
+/* Remove a backgrounded job */
+static void delete_finished_bg_job(struct pipe *pi)
+{
+       remove_bg_job(pi);
+       pi->stopped_cmds = 0;
+       free_pipe(pi);
+       free(pi);
+}
+#endif /* JOB */
+
+/* Check 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;
+#if ENABLE_HUSH_JOB
+       struct pipe *pi;
+#endif
+       pid_t childpid;
+       int rcode = 0;
+
+       debug_printf_jobs("checkjobs %p\n", fg_pipe);
+
+       attributes = WUNTRACED;
+       if (fg_pipe == NULL)
+               attributes |= WNOHANG;
+
+       errno = 0;
+#if ENABLE_HUSH_FAST
+       if (G.handled_SIGCHLD == G.count_SIGCHLD) {
+//bb_error_msg("[%d] checkjobs: G.count_SIGCHLD:%d G.handled_SIGCHLD:%d children?:%d fg_pipe:%p",
+//getpid(), G.count_SIGCHLD, G.handled_SIGCHLD, G.we_have_children, fg_pipe);
+               /* There was neither fork nor SIGCHLD since last waitpid */
+               /* Avoid doing waitpid syscall if possible */
+               if (!G.we_have_children) {
+                       errno = ECHILD;
+                       return -1;
+               }
+               if (fg_pipe == NULL) { /* is WNOHANG set? */
+                       /* We have children, but they did not exit
+                        * or stop yet (we saw no SIGCHLD) */
+                       return 0;
+               }
+               /* else: !WNOHANG, waitpid will block, can't short-circuit */
+       }
+#endif
+
+/* Do we do this right?
+ * bash-3.00# sleep 20 | false
+ * <ctrl-Z pressed>
+ * [3]+  Stopped          sleep 20 | false
+ * bash-3.00# echo $?
+ * 1   <========== bg pipe is not fully done, but exitcode is already known!
+ * [hush 1.14.0: yes we do it right]
+ */
+ wait_more:
+       while (1) {
+               int i;
+               int dead;
+
+#if ENABLE_HUSH_FAST
+               i = G.count_SIGCHLD;
+#endif
+               childpid = waitpid(-1, &status, attributes);
+               if (childpid <= 0) {
+                       if (childpid && errno != ECHILD)
+                               bb_perror_msg("waitpid");
+#if ENABLE_HUSH_FAST
+                       else { /* Until next SIGCHLD, waitpid's are useless */
+                               G.we_have_children = (childpid == 0);
+                               G.handled_SIGCHLD = i;
+//bb_error_msg("[%d] checkjobs: waitpid returned <= 0, G.count_SIGCHLD:%d G.handled_SIGCHLD:%d", getpid(), G.count_SIGCHLD, G.handled_SIGCHLD);
+                       }
+#endif
+                       break;
+               }
+               dead = WIFEXITED(status) || WIFSIGNALED(status);
+
+#if DEBUG_JOBS
+               if (WIFSTOPPED(status))
+                       debug_printf_jobs("pid %d stopped by sig %d (exitcode %d)\n",
+                                       childpid, WSTOPSIG(status), WEXITSTATUS(status));
+               if (WIFSIGNALED(status))
+                       debug_printf_jobs("pid %d killed by sig %d (exitcode %d)\n",
+                                       childpid, WTERMSIG(status), WEXITSTATUS(status));
+               if (WIFEXITED(status))
+                       debug_printf_jobs("pid %d exited, exitcode %d\n",
+                                       childpid, WEXITSTATUS(status));
+#endif
+               /* Were we asked to wait for fg pipe? */
+               if (fg_pipe) {
+                       for (i = 0; i < fg_pipe->num_cmds; i++) {
+                               debug_printf_jobs("check pid %d\n", fg_pipe->cmds[i].pid);
+                               if (fg_pipe->cmds[i].pid != childpid)
+                                       continue;
+                               if (dead) {
+                                       fg_pipe->cmds[i].pid = 0;
+                                       fg_pipe->alive_cmds--;
+                                       if (i == fg_pipe->num_cmds - 1) {
+                                               /* last process gives overall exitstatus */
+                                               /* Note: is WIFSIGNALED, WEXITSTATUS = sig + 128 */
+                                               rcode = WEXITSTATUS(status);
+                                               IF_HAS_KEYWORDS(if (fg_pipe->pi_inverted) rcode = !rcode;)
+                                               /* bash prints killing signal's name for *last*
+                                                * process in pipe (prints just newline for SIGINT).
+                                                * Mimic this. Example: "sleep 5" + ^\
+                                                */
+                                               if (WIFSIGNALED(status)) {
+                                                       int sig = WTERMSIG(status);
+                                                       printf("%s\n", sig == SIGINT ? "" : get_signame(sig));
+                                               }
+                                       }
+                               } else {
+                                       fg_pipe->cmds[i].is_stopped = 1;
+                                       fg_pipe->stopped_cmds++;
+                               }
+                               debug_printf_jobs("fg_pipe: alive_cmds %d stopped_cmds %d\n",
+                                               fg_pipe->alive_cmds, fg_pipe->stopped_cmds);
+                               if (fg_pipe->alive_cmds - fg_pipe->stopped_cmds <= 0) {
+                                       /* All processes in fg pipe have exited or stopped */
+/* Note: *non-interactive* bash does not continue if all processes in fg pipe
+ * are stopped. Testcase: "cat | cat" in a script (not on command line!)
+ * and "killall -STOP cat" */
+                                       if (G_interactive_fd) {
+#if ENABLE_HUSH_JOB
+                                               if (fg_pipe->alive_cmds)
+                                                       insert_bg_job(fg_pipe);
+#endif
+                                               return rcode;
+                                       }
+                                       if (!fg_pipe->alive_cmds)
+                                               return rcode;
+                               }
+                               /* There are still running processes in the fg pipe */
+                               goto wait_more; /* do waitpid again */
+                       }
+                       /* it wasnt fg_pipe, look for process in bg pipes */
+               }
+
+#if ENABLE_HUSH_JOB
+               /* We asked to wait for bg or orphaned children */
+               /* No need to remember exitcode in this case */
+               for (pi = G.job_list; pi; pi = pi->next) {
+                       for (i = 0; i < pi->num_cmds; i++) {
+                               if (pi->cmds[i].pid == childpid)
+                                       goto found_pi_and_prognum;
+                       }
+               }
+               /* Happens when shell is used as init process (init=/bin/sh) */
+               debug_printf("checkjobs: pid %d was not in our list!\n", childpid);
+               continue; /* do waitpid again */
+
+ found_pi_and_prognum:
+               if (dead) {
+                       /* child exited */
+                       pi->cmds[i].pid = 0;
+                       pi->alive_cmds--;
+                       if (!pi->alive_cmds) {
+                               if (G_interactive_fd)
+                                       printf(JOB_STATUS_FORMAT, pi->jobid,
+                                                       "Done", pi->cmdtext);
+                               delete_finished_bg_job(pi);
+                       }
+               } else {
+                       /* child stopped */
+                       pi->cmds[i].is_stopped = 1;
+                       pi->stopped_cmds++;
+               }
+#endif
+       } /* while (waitpid succeeds)... */
+
+       return rcode;
+}
+
+#if ENABLE_HUSH_JOB
+static int checkjobs_and_fg_shell(struct pipe* fg_pipe)
+{
+       pid_t p;
+       int rcode = checkjobs(fg_pipe);
+       if (G_saved_tty_pgrp) {
+               /* Job finished, move the shell to the foreground */
+               p = getpgrp(); /* our process group id */
+               debug_printf_jobs("fg'ing ourself: getpgrp()=%d\n", (int)p);
+               tcsetpgrp(G_interactive_fd, p);
+       }
+       return rcode;
+}
+#endif
+
+/* Start all the jobs, but don'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 returns, and the exit code is provided as the
+ * return value.
+ *
+ * Returns -1 only if started some children. IOW: we have to
+ * mask out retvals of builtins etc with 0xff!
+ *
+ * The only case when we do not need to [v]fork is when the pipe
+ * is single, non-backgrounded, non-subshell command. Examples:
+ * cmd ; ...   { list } ; ...
+ * cmd && ...  { list } && ...
+ * cmd || ...  { list } || ...
+ * If it is, then we can run cmd as a builtin, NOFORK [do we do this?],
+ * or (if SH_STANDALONE) an applet, and we can run the { list }
+ * with run_list. If it isn't one of these, we fork and exec cmd.
+ *
+ * Cases when we must fork:
+ * non-single:   cmd | cmd
+ * backgrounded: cmd &     { list } &
+ * subshell:     ( list ) [&]
+ */
+static int run_pipe(struct pipe *pi)
+{
+       static const char *const null_ptr = NULL;
+       int i;
+       int nextin;
+       struct command *command;
+       char **argv_expanded;
+       char **argv;
+       char *p;
+       /* it is not always needed, but we aim to smaller code */
+       int squirrel[] = { -1, -1, -1 };
+       int rcode;
+
+       debug_printf_exec("run_pipe start: members:%d\n", pi->num_cmds);
+       debug_enter();
+
+       USE_HUSH_JOB(pi->pgrp = -1;)
+       pi->stopped_cmds = 0;
+       command = &(pi->cmds[0]);
+       argv_expanded = NULL;
+
+       if (pi->num_cmds != 1
+        || pi->followup == PIPE_BG
+        || command->grp_type == GRP_SUBSHELL
+       ) {
+               goto must_fork;
+       }
+
+       pi->alive_cmds = 1;
+
+       debug_printf_exec(": group:%p argv:'%s'\n",
+               command->group, command->argv ? command->argv[0] : "NONE");
+
+       if (command->group) {
+#if ENABLE_HUSH_FUNCTIONS
+               if (command->grp_type == GRP_FUNCTION) {
+                       /* "executing" func () { list } */
+                       struct function *funcp;
+
+                       funcp = new_function(command->argv[0]);
+                       /* funcp->name is already set to argv[0] */
+                       funcp->body = command->group;
+# if !BB_MMU
+                       funcp->body_as_string = command->group_as_string;
+                       command->group_as_string = NULL;
+# endif
+                       command->group = NULL;
+                       command->argv[0] = NULL;
+                       debug_printf_exec("cmd %p has child func at %p\n", command, funcp);
+                       funcp->parent_cmd = command;
+                       command->child_func = funcp;
+
+                       debug_printf_exec("run_pipe: return EXIT_SUCCESS\n");
+                       debug_leave();
+                       return EXIT_SUCCESS;
+               }
+#endif
+               /* { list } */
+               debug_printf("non-subshell group\n");
+               rcode = 1; /* exitcode if redir failed */
+               if (setup_redirects(command, squirrel) == 0) {
+                       debug_printf_exec(": run_list\n");
+                       rcode = run_list(command->group) & 0xff;
+               }
+               restore_redirects(squirrel);
+               IF_HAS_KEYWORDS(if (pi->pi_inverted) rcode = !rcode;)
+               debug_leave();
+               debug_printf_exec("run_pipe: return %d\n", rcode);
+               return rcode;
+       }
+
+       argv = command->argv ? command->argv : (char **) &null_ptr;
+       {
+               const struct built_in_command *x;
+#if ENABLE_HUSH_FUNCTIONS
+               const struct function *funcp;
+#else
+               enum { funcp = 0 };
+#endif
+               char **new_env = NULL;
+               struct variable *old_vars = NULL;
+
+               if (argv[command->assignment_cnt] == NULL) {
+                       /* Assignments, but no command */
+                       /* Ensure redirects take effect. Try "a=t >file" */
+                       rcode = setup_redirects(command, squirrel);
+                       restore_redirects(squirrel);
+                       /* Set shell variables */
+                       while (*argv) {
+                               p = expand_string_to_string(*argv);
+                               debug_printf_exec("set shell var:'%s'->'%s'\n",
+                                               *argv, p);
+                               set_local_var(p, 0, 0);
+                               argv++;
+                       }
+                       /* Do we need to flag set_local_var() errors?
+                        * "assignment to readonly var" and "putenv error"
+                        */
+                       IF_HAS_KEYWORDS(if (pi->pi_inverted) rcode = !rcode;)
+                       debug_leave();
+                       debug_printf_exec("run_pipe: return %d\n", rcode);
+                       return rcode;
+               }
+
+               /* Expand the rest into (possibly) many strings each */
+               argv_expanded = expand_strvec_to_strvec(argv + command->assignment_cnt);
+
+               x = find_builtin(argv_expanded[0]);
+#if ENABLE_HUSH_FUNCTIONS
+               funcp = NULL;
+               if (!x)
+                       funcp = find_function(argv_expanded[0]);
+#endif
+               if (x || funcp) {
+                       if (!funcp) {
+                               if (x->function == builtin_exec && argv_expanded[1] == NULL) {
+                                       debug_printf("exec with redirects only\n");
+                                       rcode = setup_redirects(command, NULL);
+                                       goto clean_up_and_ret1;
+                               }
+                       }
+                       /* 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. */
+                       rcode = setup_redirects(command, squirrel);
+                       if (rcode == 0) {
+                               new_env = expand_assignments(argv, command->assignment_cnt);
+                               old_vars = set_vars_and_save_old(new_env);
+                               if (!funcp) {
+                                       debug_printf_exec(": builtin '%s' '%s'...\n",
+                                               x->cmd, argv_expanded[1]);
+                                       rcode = x->function(argv_expanded) & 0xff;
+                                       fflush(NULL);
+                               }
+#if ENABLE_HUSH_FUNCTIONS
+                               else {
+                                       debug_printf_exec(": function '%s' '%s'...\n",
+                                               funcp->name, argv_expanded[1]);
+                                       rcode = run_function(funcp, argv_expanded) & 0xff;
+                               }
+#endif
+                       }
+#if ENABLE_FEATURE_SH_STANDALONE
+ clean_up_and_ret:
+#endif
+                       restore_redirects(squirrel);
+                       unset_vars(new_env);
+                       add_vars(old_vars);
+ clean_up_and_ret1:
+                       free(argv_expanded);
+                       IF_HAS_KEYWORDS(if (pi->pi_inverted) rcode = !rcode;)
+                       debug_leave();
+                       debug_printf_exec("run_pipe return %d\n", rcode);
+                       return rcode;
+               }
+
+#if ENABLE_FEATURE_SH_STANDALONE
+               i = find_applet_by_name(argv_expanded[0]);
+               if (i >= 0 && APPLET_IS_NOFORK(i)) {
+                       rcode = setup_redirects(command, squirrel);
+                       if (rcode == 0) {
+                               new_env = expand_assignments(argv, command->assignment_cnt);
+                               old_vars = set_vars_and_save_old(new_env);
+                               debug_printf_exec(": run_nofork_applet '%s' '%s'...\n",
+                                       argv_expanded[0], argv_expanded[1]);
+                               rcode = run_nofork_applet(i, argv_expanded);
+                       }
+                       goto clean_up_and_ret;
+               }
+#endif
+               /* It is neither builtin nor applet. We must fork. */
+       }
+
+ must_fork:
+       /* NB: argv_expanded may already be created, and that
+        * might include `cmd` runs! Do not rerun it! We *must*
+        * use argv_expanded if it's non-NULL */
+
+       /* Going to fork a child per each pipe member */
+       pi->alive_cmds = 0;
+       nextin = 0;
+
+       for (i = 0; i < pi->num_cmds; i++) {
+               struct fd_pair pipefds;
+#if !BB_MMU
+               volatile nommu_save_t nommu_save;
+               nommu_save.new_env = NULL;
+               nommu_save.old_vars = NULL;
+               nommu_save.argv = NULL;
+               nommu_save.argv_from_re_execing = NULL;
+#endif
+               command = &(pi->cmds[i]);
+               if (command->argv) {
+                       debug_printf_exec(": pipe member '%s' '%s'...\n",
+                                       command->argv[0], command->argv[1]);
+               } else {
+                       debug_printf_exec(": pipe member with no argv\n");
+               }
+
+               /* pipes are inserted between pairs of commands */
+               pipefds.rd = 0;
+               pipefds.wr = 1;
+               if ((i + 1) < pi->num_cmds)
+                       xpiped_pair(pipefds);
+
+               command->pid = BB_MMU ? fork() : vfork();
+               if (!command->pid) { /* child */
+#if ENABLE_HUSH_JOB
+                       disable_restore_tty_pgrp_on_exit();
+
+                       /* Every child adds itself to new process group
+                        * with pgid == pid_of_first_child_in_pipe */
+                       if (G.run_list_level == 1 && G_interactive_fd) {
+                               pid_t pgrp;
+                               pgrp = pi->pgrp;
+                               if (pgrp < 0) /* true for 1st process only */
+                                       pgrp = getpid();
+                               if (setpgid(0, pgrp) == 0
+                                && pi->followup != PIPE_BG
+                                && G_saved_tty_pgrp /* we have ctty */
+                               ) {
+                                       /* We do it in *every* child, not just first,
+                                        * to avoid races */
+                                       tcsetpgrp(G_interactive_fd, pgrp);
+                               }
+                       }
+#endif
+                       if (pi->alive_cmds == 0 && pi->followup == PIPE_BG) {
+                               /* 1st cmd in backgrounded pipe
+                                * should have its stdin /dev/null'ed */
+                               close(0);
+                               if (open(bb_dev_null, O_RDONLY))
+                                       xopen("/", O_RDONLY);
+                       } else {
+                               xmove_fd(nextin, 0);
+                       }
+                       xmove_fd(pipefds.wr, 1);
+                       if (pipefds.rd > 1)
+                               close(pipefds.rd);
+                       /* Like bash, explicit redirects override pipes,
+                        * and the pipe fd is available for dup'ing. */
+                       if (setup_redirects(command, NULL))
+                               _exit(1);
+
+                       /* Restore default handlers just prior to exec */
+                       /*signal(SIGCHLD, SIG_DFL); - so far we don't have any handlers */
+
+                       /* Stores to nommu_save list of env vars putenv'ed
+                        * (NOMMU, on MMU we don't need that) */
+                       /* cast away volatility... */
+                       pseudo_exec((nommu_save_t*) &nommu_save, command, argv_expanded);
+                       /* pseudo_exec() does not return */
+               }
+
+               /* parent or error */
+#if ENABLE_HUSH_FAST
+               G.count_SIGCHLD++;
+//bb_error_msg("[%d] fork in run_pipe: G.count_SIGCHLD:%d G.handled_SIGCHLD:%d", getpid(), G.count_SIGCHLD, G.handled_SIGCHLD);
+#endif
+               enable_restore_tty_pgrp_on_exit();
+#if !BB_MMU
+               /* Clean up after vforked child */
+               free(nommu_save.argv);
+               free(nommu_save.argv_from_re_execing);
+               unset_vars(nommu_save.new_env);
+               add_vars(nommu_save.old_vars);
+#endif
+               free(argv_expanded);
+               argv_expanded = NULL;
+               if (command->pid < 0) { /* [v]fork failed */
+                       /* Clearly indicate, was it fork or vfork */
+                       bb_perror_msg(BB_MMU ? "fork" : "vfork");
+               } else {
+                       pi->alive_cmds++;
+#if ENABLE_HUSH_JOB
+                       /* Second and next children need to know pid of first one */
+                       if (pi->pgrp < 0)
+                               pi->pgrp = command->pid;
+#endif
+               }
+
+               if (i)
+                       close(nextin);
+               if ((i + 1) < pi->num_cmds)
+                       close(pipefds.wr);
+               /* Pass read (output) pipe end to next iteration */
+               nextin = pipefds.rd;
+       }
+
+       if (!pi->alive_cmds) {
+               debug_leave();
+               debug_printf_exec("run_pipe return 1 (all forks failed, no children)\n");
+               return 1;
+       }
+
+       debug_leave();
+       debug_printf_exec("run_pipe return -1 (%u children started)\n", pi->alive_cmds);
+       return -1;
+}
+
+#ifndef debug_print_tree
+static void debug_print_tree(struct pipe *pi, int lvl)
+{
+       static const char *const PIPE[] = {
+               [PIPE_SEQ] = "SEQ",
+               [PIPE_AND] = "AND",
+               [PIPE_OR ] = "OR" ,
+               [PIPE_BG ] = "BG" ,
+       };
+       static const char *RES[] = {
+               [RES_NONE ] = "NONE" ,
+#if ENABLE_HUSH_IF
+               [RES_IF   ] = "IF"   ,
+               [RES_THEN ] = "THEN" ,
+               [RES_ELIF ] = "ELIF" ,
+               [RES_ELSE ] = "ELSE" ,
+               [RES_FI   ] = "FI"   ,
+#endif
+#if ENABLE_HUSH_LOOPS
+               [RES_FOR  ] = "FOR"  ,
+               [RES_WHILE] = "WHILE",
+               [RES_UNTIL] = "UNTIL",
+               [RES_DO   ] = "DO"   ,
+               [RES_DONE ] = "DONE" ,
+#endif
+#if ENABLE_HUSH_LOOPS || ENABLE_HUSH_CASE
+               [RES_IN   ] = "IN"   ,
+#endif
+#if ENABLE_HUSH_CASE
+               [RES_CASE ] = "CASE" ,
+               [RES_CASE_IN ] = "CASE_IN" ,
+               [RES_MATCH] = "MATCH",
+               [RES_CASE_BODY] = "CASE_BODY",
+               [RES_ESAC ] = "ESAC" ,
+#endif
+               [RES_XXXX ] = "XXXX" ,
+               [RES_SNTX ] = "SNTX" ,
+       };
+       static const char *const GRPTYPE[] = {
+               "{}",
+               "()",
+#if ENABLE_HUSH_FUNCTIONS
+               "func()",
+#endif
+       };
+
+       int pin, prn;
+
+       pin = 0;
+       while (pi) {
+               fprintf(stderr, "%*spipe %d res_word=%s followup=%d %s\n", lvl*2, "",
+                               pin, RES[pi->res_word], pi->followup, PIPE[pi->followup]);
+               prn = 0;
+               while (prn < pi->num_cmds) {
+                       struct command *command = &pi->cmds[prn];
+                       char **argv = command->argv;
+
+                       fprintf(stderr, "%*s cmd %d assignment_cnt:%d",
+                                       lvl*2, "", prn,
+                                       command->assignment_cnt);
+                       if (command->group) {
+                               fprintf(stderr, " group %s: (argv=%p)\n",
+                                               GRPTYPE[command->grp_type],
+                                               argv);
+                               debug_print_tree(command->group, lvl+1);
+                               prn++;
+                               continue;
+                       }
+                       if (argv) while (*argv) {
+                               fprintf(stderr, " '%s'", *argv);
+                               argv++;
+                       }
+                       fprintf(stderr, "\n");
+                       prn++;
+               }
+               pi = pi->next;
+               pin++;
+       }
+}
+#endif
+
+/* NB: called by pseudo_exec, and therefore must not modify any
+ * global data until exec/_exit (we can be a child after vfork!) */
+static int run_list(struct pipe *pi)
+{
+#if ENABLE_HUSH_CASE
+       char *case_word = NULL;
+#endif
+#if ENABLE_HUSH_LOOPS
+       struct pipe *loop_top = NULL;
+       char **for_lcur = NULL;
+       char **for_list = NULL;
+#endif
+       smallint last_followup;
+       smalluint rcode;
+#if ENABLE_HUSH_IF || ENABLE_HUSH_CASE
+       smalluint cond_code = 0;
+#else
+       enum { cond_code = 0 };
+#endif
+#if HAS_KEYWORDS
+       smallint rword; /* enum reserved_style */
+       smallint last_rword; /* ditto */
+#endif
+
+       debug_printf_exec("run_list start lvl %d\n", G.run_list_level);
+       debug_enter();
+
+#if ENABLE_HUSH_LOOPS
+       /* Check syntax for "for" */
+       for (struct pipe *cpipe = pi; cpipe; cpipe = cpipe->next) {
+               if (cpipe->res_word != RES_FOR && cpipe->res_word != RES_IN)
+                       continue;
+               /* current word is FOR or IN (BOLD in comments below) */
+               if (cpipe->next == NULL) {
+                       syntax_error("malformed for");
+                       debug_leave();
+                       debug_printf_exec("run_list lvl %d return 1\n", G.run_list_level);
+                       return 1;
+               }
+               /* "FOR v; do ..." and "for v IN a b; do..." are ok */
+               if (cpipe->next->res_word == RES_DO)
+                       continue;
+               /* next word is not "do". It must be "in" then ("FOR v in ...") */
+               if (cpipe->res_word == RES_IN /* "for v IN a b; not_do..."? */
+                || cpipe->next->res_word != RES_IN /* FOR v not_do_and_not_in..."? */
+               ) {
+                       syntax_error("malformed for");
+                       debug_leave();
+                       debug_printf_exec("run_list lvl %d return 1\n", G.run_list_level);
+                       return 1;
+               }
+       }
+#endif
+
+       /* Past this point, all code paths should jump to ret: label
+        * in order to return, no direct "return" statements please.
+        * This helps to ensure that no memory is leaked. */
+
+#if ENABLE_HUSH_JOB
+       G.run_list_level++;
+#endif
+
+#if HAS_KEYWORDS
+       rword = RES_NONE;
+       last_rword = RES_XXXX;
+#endif
+       last_followup = PIPE_SEQ;
+       rcode = G.last_exitcode;
+
+       /* Go through list of pipes, (maybe) executing them. */
+       for (; pi; pi = USE_HUSH_LOOPS(rword == RES_DONE ? loop_top : ) pi->next) {
+               if (G.flag_SIGINT)
+                       break;
+
+               IF_HAS_KEYWORDS(rword = pi->res_word;)
+               debug_printf_exec(": rword=%d cond_code=%d last_rword=%d\n",
+                               rword, cond_code, last_rword);
+#if ENABLE_HUSH_LOOPS
+               if ((rword == RES_WHILE || rword == RES_UNTIL || rword == RES_FOR)
+                && loop_top == NULL /* avoid bumping G.depth_of_loop twice */
+               ) {
+                       /* start of a loop: remember where loop starts */
+                       loop_top = pi;
+                       G.depth_of_loop++;
+               }
+#endif
+               /* Still in the same "if...", "then..." or "do..." branch? */
+               if (IF_HAS_KEYWORDS(rword == last_rword &&) 1) {
+                       if ((rcode == 0 && last_followup == PIPE_OR)
+                        || (rcode != 0 && last_followup == PIPE_AND)
+                       ) {
+                               /* It is "<true> || CMD" or "<false> && CMD"
+                                * and we should not execute CMD */
+                               debug_printf_exec("skipped cmd because of || or &&\n");
+                               last_followup = pi->followup;
+                               continue;
+                       }
+               }
+               last_followup = pi->followup;
+               IF_HAS_KEYWORDS(last_rword = rword;)
+#if ENABLE_HUSH_IF
+               if (cond_code) {
+                       if (rword == RES_THEN) {
+                               /* if false; then ... fi has exitcode 0! */
+                               G.last_exitcode = rcode = EXIT_SUCCESS;
+                               /* "if <false> THEN cmd": skip cmd */
+                               continue;
+                       }
+               } else {
+                       if (rword == RES_ELSE || rword == RES_ELIF) {
+                               /* "if <true> then ... ELSE/ELIF cmd":
+                                * skip cmd and all following ones */
+                               break;
+                       }
+               }
+#endif
+#if ENABLE_HUSH_LOOPS
+               if (rword == RES_FOR) { /* && pi->num_cmds - always == 1 */
+                       if (!for_lcur) {
+                               /* first loop through for */
+
+                               static const char encoded_dollar_at[] ALIGN1 = {
+                                       SPECIAL_VAR_SYMBOL, '@' | 0x80, SPECIAL_VAR_SYMBOL, '\0'
+                               }; /* encoded representation of "$@" */
+                               static const char *const encoded_dollar_at_argv[] = {
+                                       encoded_dollar_at, NULL
+                               }; /* argv list with one element: "$@" */
+                               char **vals;
+
+                               vals = (char**)encoded_dollar_at_argv;
+                               if (pi->next->res_word == RES_IN) {
+                                       /* if no variable values after "in" we skip "for" */
+                                       if (!pi->next->cmds[0].argv) {
+                                               G.last_exitcode = rcode = EXIT_SUCCESS;
+                                               debug_printf_exec(": null FOR: exitcode EXIT_SUCCESS\n");
+                                               break;
+                                       }
+                                       vals = pi->next->cmds[0].argv;
+                               } /* else: "for var; do..." -> assume "$@" list */
+                               /* create list of variable values */
+                               debug_print_strings("for_list made from", vals);
+                               for_list = expand_strvec_to_strvec(vals);
+                               for_lcur = for_list;
+                               debug_print_strings("for_list", for_list);
+                       }
+                       if (!*for_lcur) {
+                               /* "for" loop is over, clean up */
+                               free(for_list);
+                               for_list = NULL;
+                               for_lcur = NULL;
+                               break;
+                       }
+                       /* Insert next value from for_lcur */
+                       /* note: *for_lcur already has quotes removed, $var expanded, etc */
+                       set_local_var(xasprintf("%s=%s", pi->cmds[0].argv[0], *for_lcur++), 0, 0);
+                       continue;
+               }
+               if (rword == RES_IN) {
+                       continue; /* "for v IN list;..." - "in" has no cmds anyway */
+               }
+               if (rword == RES_DONE) {
+                       continue; /* "done" has no cmds too */
+               }
+#endif
+#if ENABLE_HUSH_CASE
+               if (rword == RES_CASE) {
+                       case_word = expand_strvec_to_string(pi->cmds->argv);
+                       continue;
+               }
+               if (rword == RES_MATCH) {
+                       char **argv;
+
+                       if (!case_word) /* "case ... matched_word) ... WORD)": we executed selected branch, stop */
+                               break;
+                       /* all prev words didn't match, does this one match? */
+                       argv = pi->cmds->argv;
+                       while (*argv) {
+                               char *pattern = expand_string_to_string(*argv);
+                               /* TODO: which FNM_xxx flags to use? */
+                               cond_code = (fnmatch(pattern, case_word, /*flags:*/ 0) != 0);
+                               free(pattern);
+                               if (cond_code == 0) { /* match! we will execute this branch */
+                                       free(case_word); /* make future "word)" stop */
+                                       case_word = NULL;
+                                       break;
+                               }
+                               argv++;
+                       }
+                       continue;
+               }
+               if (rword == RES_CASE_BODY) { /* inside of a case branch */
+                       if (cond_code != 0)
+                               continue; /* not matched yet, skip this pipe */
+               }
+#endif
+               /* Just pressing <enter> in shell should check for jobs.
+                * OTOH, in non-interactive shell this is useless
+                * and only leads to extra job checks */
+               if (pi->num_cmds == 0) {
+                       if (G_interactive_fd)
+                               goto check_jobs_and_continue;
+                       continue;
+               }
+
+               /* After analyzing all keywords and conditions, we decided
+                * to execute this pipe. NB: have to do checkjobs(NULL)
+                * after run_pipe to collect any background children,
+                * even if list execution is to be stopped. */
+               debug_printf_exec(": run_pipe with %d members\n", pi->num_cmds);
+               {
+                       int r;
+#if ENABLE_HUSH_LOOPS
+                       G.flag_break_continue = 0;
+#endif
+                       rcode = r = run_pipe(pi); /* NB: rcode is a smallint */
+                       if (r != -1) {
+                               /* We ran a builtin, function, or group.
+                                * rcode is already known
+                                * and we don't need to wait for anything. */
+                               G.last_exitcode = rcode;
+                               debug_printf_exec(": builtin/func exitcode %d\n", rcode);
+                               check_and_run_traps(0);
+#if ENABLE_HUSH_LOOPS
+                               /* Was it "break" or "continue"? */
+                               if (G.flag_break_continue) {
+                                       smallint fbc = G.flag_break_continue;
+                                       /* We might fall into outer *loop*,
+                                        * don't want to break it too */
+                                       if (loop_top) {
+                                               G.depth_break_continue--;
+                                               if (G.depth_break_continue == 0)
+                                                       G.flag_break_continue = 0;
+                                               /* else: e.g. "continue 2" should *break* once, *then* continue */
+                                       } /* else: "while... do... { we are here (innermost list is not a loop!) };...done" */
+                                       if (G.depth_break_continue != 0 || fbc == BC_BREAK)
+                                               goto check_jobs_and_break;
+                                       /* "continue": simulate end of loop */
+                                       rword = RES_DONE;
+                                       continue;
+                               }
+#endif
+#if ENABLE_HUSH_FUNCTIONS
+                               if (G.flag_return_in_progress == 1) {
+                                       /* same as "goto check_jobs_and_break" */
+                                       checkjobs(NULL);
+                                       break;
+                               }
+#endif
+                       } else if (pi->followup == PIPE_BG) {
+                               /* What does bash do with attempts to background builtins? */
+                               /* even bash 3.2 doesn't do that well with nested bg:
+                                * try "{ { sleep 10; echo DEEP; } & echo HERE; } &".
+                                * I'm NOT treating inner &'s as jobs */
+                               check_and_run_traps(0);
+#if ENABLE_HUSH_JOB
+                               if (G.run_list_level == 1)
+                                       insert_bg_job(pi);
+#endif
+                               G.last_exitcode = rcode = EXIT_SUCCESS;
+                               debug_printf_exec(": cmd&: exitcode EXIT_SUCCESS\n");
+                       } else {
+#if ENABLE_HUSH_JOB
+                               if (G.run_list_level == 1 && G_interactive_fd) {
+                                       /* Waits for completion, then fg's main shell */
+                                       rcode = checkjobs_and_fg_shell(pi);
+                                       debug_printf_exec(": checkjobs_and_fg_shell exitcode %d\n", rcode);
+                                       check_and_run_traps(0);
+                               } else
+#endif
+                               { /* This one just waits for completion */
+                                       rcode = checkjobs(pi);
+                                       debug_printf_exec(": checkjobs exitcode %d\n", rcode);
+                                       check_and_run_traps(0);
+                               }
+                               G.last_exitcode = rcode;
+                       }
+               }
+
+               /* Analyze how result affects subsequent commands */
+#if ENABLE_HUSH_IF
+               if (rword == RES_IF || rword == RES_ELIF)
+                       cond_code = rcode;
+#endif
+#if ENABLE_HUSH_LOOPS
+               /* Beware of "while false; true; do ..."! */
+               if (pi->next && pi->next->res_word == RES_DO) {
+                       if (rword == RES_WHILE) {
+                               if (rcode) {
+                                       /* "while false; do...done" - exitcode 0 */
+                                       G.last_exitcode = rcode = EXIT_SUCCESS;
+                                       debug_printf_exec(": while expr is false: breaking (exitcode:EXIT_SUCCESS)\n");
+                                       goto check_jobs_and_break;
+                               }
+                       }
+                       if (rword == RES_UNTIL) {
+                               if (!rcode) {
+                                       debug_printf_exec(": until expr is true: breaking\n");
+ check_jobs_and_break:
+                                       checkjobs(NULL);
+                                       break;
+                               }
+                       }
+               }
+#endif
+
+ check_jobs_and_continue:
+               checkjobs(NULL);
+       } /* for (pi) */
+
+#if ENABLE_HUSH_JOB
+       G.run_list_level--;
+#endif
+#if ENABLE_HUSH_LOOPS
+       if (loop_top)
+               G.depth_of_loop--;
+       free(for_list);
+#endif
+#if ENABLE_HUSH_CASE
+       free(case_word);
+#endif
+       debug_leave();
+       debug_printf_exec("run_list lvl %d return %d\n", G.run_list_level + 1, rcode);
+       return rcode;
+}
+
+/* Select which version we will use */
+static int run_and_free_list(struct pipe *pi)
+{
+       int rcode = 0;
+       debug_printf_exec("run_and_free_list entered\n");
+       if (!G.fake_mode) {
+               debug_printf_exec(": run_list: 1st pipe with %d cmds\n", pi->num_cmds);
+               rcode = run_list(pi);
+       }
+       /* free_pipe_list has the side effect of clearing memory.
+        * In the long run that function can be merged with run_list,
+        * but doing that now would hobble the debugging effort. */
+       free_pipe_list(pi);
+       debug_printf_exec("run_and_free_list return %d\n", rcode);
+       return rcode;
+}
+
+
+static struct pipe *new_pipe(void)
+{
+       struct pipe *pi;
+       pi = xzalloc(sizeof(struct pipe));
+       /*pi->followup = 0; - deliberately invalid value */
+       /*pi->res_word = RES_NONE; - RES_NONE is 0 anyway */
+       return pi;
+}
+
+/* Command (member of a pipe) is complete, or we start a new pipe
+ * if ctx->command is NULL.
+ * No errors possible here.
+ */
+static int done_command(struct parse_context *ctx)
+{
+       /* The command is really already in the pipe structure, so
+        * advance the pipe counter and make a new, null command. */
+       struct pipe *pi = ctx->pipe;
+       struct command *command = ctx->command;
+
+       if (command) {
+               if (IS_NULL_CMD(command)) {
+                       debug_printf_parse("done_command: skipping null cmd, num_cmds=%d\n", pi->num_cmds);
+                       goto clear_and_ret;
+               }
+               pi->num_cmds++;
+               debug_printf_parse("done_command: ++num_cmds=%d\n", pi->num_cmds);
+               //debug_print_tree(ctx->list_head, 20);
+       } else {
+               debug_printf_parse("done_command: initializing, num_cmds=%d\n", pi->num_cmds);
+       }
+
+       /* Only real trickiness here is that the uncommitted
+        * command structure is not counted in pi->num_cmds. */
+       pi->cmds = xrealloc(pi->cmds, sizeof(*pi->cmds) * (pi->num_cmds+1));
+       ctx->command = command = &pi->cmds[pi->num_cmds];
+ clear_and_ret:
+       memset(command, 0, sizeof(*command));
+       return pi->num_cmds; /* used only for 0/nonzero check */
+}
+
+static void done_pipe(struct parse_context *ctx, pipe_style type)
+{
+       int not_null;
+
+       debug_printf_parse("done_pipe entered, followup %d\n", type);
+       /* Close previous command */
+       not_null = done_command(ctx);
+       ctx->pipe->followup = type;
+#if HAS_KEYWORDS
+       ctx->pipe->pi_inverted = ctx->ctx_inverted;
+       ctx->ctx_inverted = 0;
+       ctx->pipe->res_word = ctx->ctx_res_w;
+#endif
+
+       /* Without this check, even just <enter> on command line generates
+        * tree of three NOPs (!). Which is harmless but annoying.
+        * IOW: it is safe to do it unconditionally. */
+       if (not_null
+#if ENABLE_HUSH_IF
+        || ctx->ctx_res_w == RES_FI
+#endif
+#if ENABLE_HUSH_LOOPS
+        || ctx->ctx_res_w == RES_DONE
+        || ctx->ctx_res_w == RES_FOR
+        || ctx->ctx_res_w == RES_IN
+#endif
+#if ENABLE_HUSH_CASE
+        || ctx->ctx_res_w == RES_ESAC
+#endif
+       ) {
+               struct pipe *new_p;
+               debug_printf_parse("done_pipe: adding new pipe: "
+                               "not_null:%d ctx->ctx_res_w:%d\n",
+                               not_null, ctx->ctx_res_w);
+               new_p = new_pipe();
+               ctx->pipe->next = new_p;
+               ctx->pipe = new_p;
+               /* RES_THEN, RES_DO etc are "sticky" -
+                * they remain set for pipes inside if/while.
+                * This is used to control execution.
+                * RES_FOR and RES_IN are NOT sticky (needed to support
+                * cases where variable or value happens to match a keyword):
+                */
+#if ENABLE_HUSH_LOOPS
+               if (ctx->ctx_res_w == RES_FOR
+                || ctx->ctx_res_w == RES_IN)
+                       ctx->ctx_res_w = RES_NONE;
+#endif
+#if ENABLE_HUSH_CASE
+               if (ctx->ctx_res_w == RES_MATCH)
+                       ctx->ctx_res_w = RES_CASE_BODY;
+               if (ctx->ctx_res_w == RES_CASE)
+                       ctx->ctx_res_w = RES_CASE_IN;
+#endif
+               ctx->command = NULL; /* trick done_command below */
+               /* Create the memory for command, roughly:
+                * ctx->pipe->cmds = new struct command;
+                * ctx->command = &ctx->pipe->cmds[0];
+                */
+               done_command(ctx);
+               //debug_print_tree(ctx->list_head, 10);
+       }
+       debug_printf_parse("done_pipe return\n");
+}
+
+static void initialize_context(struct parse_context *ctx)
+{
+       memset(ctx, 0, sizeof(*ctx));
+       ctx->pipe = ctx->list_head = new_pipe();
+       /* Create the memory for command, roughly:
+        * ctx->pipe->cmds = new struct command;
+        * ctx->command = &ctx->pipe->cmds[0];
+        */
+       done_command(ctx);
+}
+
+/* If a reserved word is found and processed, parse context is modified
+ * and 1 is returned.
+ */
+#if HAS_KEYWORDS
+struct reserved_combo {
+       char literal[6];
+       unsigned char res;
+       unsigned char assignment_flag;
+       int flag;
+};
+enum {
+       FLAG_END   = (1 << RES_NONE ),
+#if ENABLE_HUSH_IF
+       FLAG_IF    = (1 << RES_IF   ),
+       FLAG_THEN  = (1 << RES_THEN ),
+       FLAG_ELIF  = (1 << RES_ELIF ),
+       FLAG_ELSE  = (1 << RES_ELSE ),
+       FLAG_FI    = (1 << RES_FI   ),
+#endif
+#if ENABLE_HUSH_LOOPS
+       FLAG_FOR   = (1 << RES_FOR  ),
+       FLAG_WHILE = (1 << RES_WHILE),
+       FLAG_UNTIL = (1 << RES_UNTIL),
+       FLAG_DO    = (1 << RES_DO   ),
+       FLAG_DONE  = (1 << RES_DONE ),
+       FLAG_IN    = (1 << RES_IN   ),
+#endif
+#if ENABLE_HUSH_CASE
+       FLAG_MATCH = (1 << RES_MATCH),
+       FLAG_ESAC  = (1 << RES_ESAC ),
+#endif
+       FLAG_START = (1 << RES_XXXX ),
+};
+
+static const struct reserved_combo* match_reserved_word(o_string *word)
+{
+       /* 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 const struct reserved_combo reserved_list[] = {
+#if ENABLE_HUSH_IF
+               { "!",     RES_NONE,  NOT_ASSIGNMENT , 0 },
+               { "if",    RES_IF,    WORD_IS_KEYWORD, FLAG_THEN | FLAG_START },
+               { "then",  RES_THEN,  WORD_IS_KEYWORD, FLAG_ELIF | FLAG_ELSE | FLAG_FI },
+               { "elif",  RES_ELIF,  WORD_IS_KEYWORD, FLAG_THEN },
+               { "else",  RES_ELSE,  WORD_IS_KEYWORD, FLAG_FI   },
+               { "fi",    RES_FI,    NOT_ASSIGNMENT , FLAG_END  },
+#endif
+#if ENABLE_HUSH_LOOPS
+               { "for",   RES_FOR,   NOT_ASSIGNMENT , FLAG_IN | FLAG_DO | FLAG_START },
+               { "while", RES_WHILE, WORD_IS_KEYWORD, FLAG_DO | FLAG_START },
+               { "until", RES_UNTIL, WORD_IS_KEYWORD, FLAG_DO | FLAG_START },
+               { "in",    RES_IN,    NOT_ASSIGNMENT , FLAG_DO   },
+               { "do",    RES_DO,    WORD_IS_KEYWORD, FLAG_DONE },
+               { "done",  RES_DONE,  NOT_ASSIGNMENT , FLAG_END  },
+#endif
+#if ENABLE_HUSH_CASE
+               { "case",  RES_CASE,  NOT_ASSIGNMENT , FLAG_MATCH | FLAG_START },
+               { "esac",  RES_ESAC,  NOT_ASSIGNMENT , FLAG_END  },
+#endif
+       };
+       const struct reserved_combo *r;
+
+       for (r = reserved_list; r < reserved_list + ARRAY_SIZE(reserved_list); r++) {
+               if (strcmp(word->data, r->literal) == 0)
+                       return r;
+       }
+       return NULL;
+}
+/* Return 0: not a keyword, 1: keyword
+ */
+static int reserved_word(o_string *word, struct parse_context *ctx)
+{
+#if ENABLE_HUSH_CASE
+       static const struct reserved_combo reserved_match = {
+               "",        RES_MATCH, NOT_ASSIGNMENT , FLAG_MATCH | FLAG_ESAC
+       };
+#endif
+       const struct reserved_combo *r;
+
+       if (word->o_quoted)
+               return 0;
+       r = match_reserved_word(word);
+       if (!r)
+               return 0;
+
+       debug_printf("found reserved word %s, res %d\n", r->literal, r->res);
+#if ENABLE_HUSH_CASE
+       if (r->res == RES_IN && ctx->ctx_res_w == RES_CASE_IN) {
+               /* "case word IN ..." - IN part starts first MATCH part */
+               r = &reserved_match;
+       } else
+#endif
+       if (r->flag == 0) { /* '!' */
+               if (ctx->ctx_inverted) { /* bash doesn't accept '! ! true' */
+                       syntax_error("! ! command");
+                       ctx->ctx_res_w = RES_SNTX;
+               }
+               ctx->ctx_inverted = 1;
+               return 1;
+       }
+       if (r->flag & FLAG_START) {
+               struct parse_context *old;
+
+               old = xmalloc(sizeof(*old));
+               debug_printf_parse("push stack %p\n", old);
+               *old = *ctx;   /* physical copy */
+               initialize_context(ctx);
+               ctx->stack = old;
+       } else if (/*ctx->ctx_res_w == RES_NONE ||*/ !(ctx->old_flag & (1 << r->res))) {
+               syntax_error_at(word->data);
+               ctx->ctx_res_w = RES_SNTX;
+               return 1;
+       } else {
+               /* "{...} fi" is ok. "{...} if" is not
+                * Example:
+                * if { echo foo; } then { echo bar; } fi */
+               if (ctx->command->group)
+                       done_pipe(ctx, PIPE_SEQ);
+       }
+
+       ctx->ctx_res_w = r->res;
+       ctx->old_flag = r->flag;
+       word->o_assignment = r->assignment_flag;
+
+       if (ctx->old_flag & FLAG_END) {
+               struct parse_context *old;
+
+               done_pipe(ctx, PIPE_SEQ);
+               debug_printf_parse("pop stack %p\n", ctx->stack);
+               old = ctx->stack;
+               old->command->group = ctx->list_head;
+               old->command->grp_type = GRP_NORMAL;
+#if !BB_MMU
+               o_addstr(&old->as_string, ctx->as_string.data);
+               o_free_unsafe(&ctx->as_string);
+               old->command->group_as_string = xstrdup(old->as_string.data);
+               debug_printf_parse("pop, remembering as:'%s'\n",
+                               old->command->group_as_string);
+#endif
+               *ctx = *old;   /* physical copy */
+               free(old);
+       }
+       return 1;
+}
+#endif
+
+/* Word is complete, look at it and update parsing context.
+ * Normal return is 0. Syntax errors return 1.
+ * Note: on return, word is reset, but not o_free'd!
+ */
+static int done_word(o_string *word, struct parse_context *ctx)
+{
+       struct command *command = ctx->command;
+
+       debug_printf_parse("done_word entered: '%s' %p\n", word->data, command);
+       if (word->length == 0 && word->o_quoted == 0) {
+               debug_printf_parse("done_word return 0: true null, ignored\n");
+               return 0;
+       }
+
+       if (ctx->pending_redirect) {
+               /* We do not glob in e.g. >*.tmp case. bash seems to glob here
+                * only if run as "bash", not "sh" */
+               /* http://www.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html
+                * "2.7 Redirection
+                * ...the word that follows the redirection operator
+                * shall be subjected to tilde expansion, parameter expansion,
+                * command substitution, arithmetic expansion, and quote
+                * removal. Pathname expansion shall not be performed
+                * on the word by a non-interactive shell; an interactive
+                * shell may perform it, but shall do so only when
+                * the expansion would result in one word."
+                */
+               ctx->pending_redirect->rd_filename = xstrdup(word->data);
+               /* Cater for >\file case:
+                * >\a creates file a; >\\a, >"\a", >"\\a" create file \a
+                * Same with heredocs:
+                * for <<\H delim is H; <<\\H, <<"\H", <<"\\H" - \H
+                */
+               if (ctx->pending_redirect->rd_type == REDIRECT_HEREDOC) {
+                       unbackslash(ctx->pending_redirect->rd_filename);
+                       /* Is it <<"HEREDOC"? */
+                       if (word->o_quoted) {
+                               ctx->pending_redirect->rd_dup |= HEREDOC_QUOTED;
+                       }
+               }
+               debug_printf_parse("word stored in rd_filename: '%s'\n", word->data);
+               ctx->pending_redirect = NULL;
+       } else {
+               /* If this word wasn't an assignment, next ones definitely
+                * can't be assignments. Even if they look like ones. */
+               if (word->o_assignment != DEFINITELY_ASSIGNMENT
+                && word->o_assignment != WORD_IS_KEYWORD
+               ) {
+                       word->o_assignment = NOT_ASSIGNMENT;
+               } else {
+                       if (word->o_assignment == DEFINITELY_ASSIGNMENT)
+                               command->assignment_cnt++;
+                       word->o_assignment = MAYBE_ASSIGNMENT;
+               }
+
+#if HAS_KEYWORDS
+# if ENABLE_HUSH_CASE
+               if (ctx->ctx_dsemicolon
+                && strcmp(word->data, "esac") != 0 /* not "... pattern) cmd;; esac" */
+               ) {
+                       /* already done when ctx_dsemicolon was set to 1: */
+                       /* ctx->ctx_res_w = RES_MATCH; */
+                       ctx->ctx_dsemicolon = 0;
+               } else
+# endif
+               if (!command->argv /* if it's the first word... */
+# if ENABLE_HUSH_LOOPS
+                && ctx->ctx_res_w != RES_FOR /* ...not after FOR or IN */
+                && ctx->ctx_res_w != RES_IN
+# endif
+# if ENABLE_HUSH_CASE
+                && ctx->ctx_res_w != RES_CASE
+# endif
+               ) {
+                       debug_printf_parse("checking '%s' for reserved-ness\n", word->data);
+                       if (reserved_word(word, ctx)) {
+                               o_reset_to_empty_unquoted(word);
+                               debug_printf_parse("done_word return %d\n",
+                                               (ctx->ctx_res_w == RES_SNTX));
+                               return (ctx->ctx_res_w == RES_SNTX);
+                       }
+               }
+#endif
+               if (command->group) {
+                       /* "{ echo foo; } echo bar" - bad */
+                       syntax_error_at(word->data);
+                       debug_printf_parse("done_word return 1: syntax error, "
+                                       "groups and arglists don't mix\n");
+                       return 1;
+               }
+               if (word->o_quoted /* word had "xx" or 'xx' at least as part of it. */
+                /* optimization: and if it's ("" or '') or ($v... or `cmd`...): */
+                && (word->data[0] == '\0' || word->data[0] == SPECIAL_VAR_SYMBOL)
+                /* (otherwise it's known to be not empty and is already safe) */
+               ) {
+                       /* exclude "$@" - it can expand to no word despite "" */
+                       char *p = word->data;
+                       while (p[0] == SPECIAL_VAR_SYMBOL
+                           && (p[1] & 0x7f) == '@'
+                           && p[2] == SPECIAL_VAR_SYMBOL
+                       ) {
+                               p += 3;
+                       }
+                       if (p == word->data || p[0] != '\0') {
+                               /* saw no "$@", or not only "$@" but some
+                                * real text is there too */
+                               /* insert "empty variable" reference, this makes
+                                * e.g. "", $empty"" etc to not disappear */
+                               o_addchr(word, SPECIAL_VAR_SYMBOL);
+                               o_addchr(word, SPECIAL_VAR_SYMBOL);
+                       }
+               }
+               command->argv = add_string_to_strings(command->argv, xstrdup(word->data));
+               debug_print_strings("word appended to argv", command->argv);
+       }
+
+#if ENABLE_HUSH_LOOPS
+       if (ctx->ctx_res_w == RES_FOR) {
+               if (word->o_quoted
+                || !is_well_formed_var_name(command->argv[0], '\0')
+               ) {
+                       /* bash says just "not a valid identifier" */
+                       syntax_error("not a valid identifier in for");
+                       return 1;
+               }
+               /* Force FOR to have just one word (variable name) */
+               /* NB: basically, this makes hush see "for v in ..."
+                * syntax as if it is "for v; in ...". FOR and IN become
+                * two pipe structs in parse tree. */
+               done_pipe(ctx, PIPE_SEQ);
+       }
+#endif
+#if ENABLE_HUSH_CASE
+       /* Force CASE to have just one word */
+       if (ctx->ctx_res_w == RES_CASE) {
+               done_pipe(ctx, PIPE_SEQ);
+       }
+#endif
+
+       o_reset_to_empty_unquoted(word);
+
+       debug_printf_parse("done_word return 0\n");
+       return 0;
+}
+
+
+/* Peek ahead in the input to find out if we have a "&n" construct,
+ * as in "2>&1", that represents duplicating a file descriptor.
+ * Return:
+ * REDIRFD_CLOSE if >&- "close fd" construct is seen,
+ * REDIRFD_SYNTAX_ERR if syntax error,
+ * REDIRFD_TO_FILE if no & was seen,
+ * or the number found.
+ */
+#if BB_MMU
+#define parse_redir_right_fd(as_string, input) \
+       parse_redir_right_fd(input)
+#endif
+static int parse_redir_right_fd(o_string *as_string, struct in_str *input)
+{
+       int ch, d, ok;
+
+       ch = i_peek(input);
+       if (ch != '&')
+               return REDIRFD_TO_FILE;
+
+       ch = i_getch(input);  /* get the & */
+       nommu_addchr(as_string, ch);
+       ch = i_peek(input);
+       if (ch == '-') {
+               ch = i_getch(input);
+               nommu_addchr(as_string, ch);
+               return REDIRFD_CLOSE;
+       }
+       d = 0;
+       ok = 0;
+       while (ch != EOF && isdigit(ch)) {
+               d = d*10 + (ch-'0');
+               ok = 1;
+               ch = i_getch(input);
+               nommu_addchr(as_string, ch);
+               ch = i_peek(input);
+       }
+       if (ok) return d;
+
+//TODO: this is the place to catch ">&file" bashism (redirect both fd 1 and 2)
+
+       bb_error_msg("ambiguous redirect");
+       return REDIRFD_SYNTAX_ERR;
+}
+
+/* Return code is 0 normal, 1 if a syntax error is detected
+ */
+static int parse_redirect(struct parse_context *ctx,
+               int fd,
+               redir_type style,
+               struct in_str *input)
+{
+       struct command *command = ctx->command;
+       struct redir_struct *redir;
+       struct redir_struct **redirp;
+       int dup_num;
+
+       dup_num = REDIRFD_TO_FILE;
+       if (style != REDIRECT_HEREDOC) {
+               /* Check for a '>&1' type redirect */
+               dup_num = parse_redir_right_fd(&ctx->as_string, input);
+               if (dup_num == REDIRFD_SYNTAX_ERR)
+                       return 1;
+       } else {
+               int ch = i_peek(input);
+               dup_num = (ch == '-'); /* HEREDOC_SKIPTABS bit is 1 */
+               if (dup_num) { /* <<-... */
+                       ch = i_getch(input);
+                       nommu_addchr(&ctx->as_string, ch);
+                       ch = i_peek(input);
+               }
+       }
+
+       if (style == REDIRECT_OVERWRITE && dup_num == REDIRFD_TO_FILE) {
+               int ch = i_peek(input);
+               if (ch == '|') {
+                       /* >|FILE redirect ("clobbering" >).
+                        * Since we do not support "set -o noclobber" yet,
+                        * >| and > are the same for now. Just eat |.
+                        */
+                       ch = i_getch(input);
+                       nommu_addchr(&ctx->as_string, ch);
+               }
+       }
+
+       /* Create a new redir_struct and append it to the linked list */
+       redirp = &command->redirects;
+       while ((redir = *redirp) != NULL) {
+               redirp = &(redir->next);
+       }
+       *redirp = redir = xzalloc(sizeof(*redir));
+       /* redir->next = NULL; */
+       /* redir->rd_filename = NULL; */
+       redir->rd_type = style;
+       redir->rd_fd = (fd == -1) ? redir_table[style].default_fd : fd;
+
+       debug_printf_parse("redirect type %d %s\n", redir->rd_fd,
+                               redir_table[style].descrip);
+
+       redir->rd_dup = dup_num;
+       if (style != REDIRECT_HEREDOC && dup_num != REDIRFD_TO_FILE) {
+               /* 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_parse("duplicating redirect '%d>&%d'\n",
+                               redir->rd_fd, redir->rd_dup);
+       } else {
+               /* Set ctx->pending_redirect, so we know what to do at the
+                * end of the next parsed word. */
+               ctx->pending_redirect = redir;
+       }
+       return 0;
+}
+
+/* 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
+ *
+ * http://www.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html
+ * "2.7 Redirection
+ * ... If n is quoted, the number shall not be recognized as part of
+ * the redirection expression. For example:
+ * echo \2>a
+ * writes the character 2 into file a"
+ * We are getting it right by setting ->o_quoted on any \<char>
+ *
+ * A -1 return means no valid number was found,
+ * the caller should use the appropriate default for this redirection.
+ */
+static int redirect_opt_num(o_string *o)
+{
+       int num;
+
+       if (o->data == NULL)
+               return -1;
+       num = bb_strtou(o->data, NULL, 10);
+       if (errno || num < 0)
+               return -1;
+       o_reset_to_empty_unquoted(o);
+       return num;
+}
+
+#if BB_MMU
+#define fetch_till_str(as_string, input, word, skip_tabs) \
+       fetch_till_str(input, word, skip_tabs)
+#endif
+static char *fetch_till_str(o_string *as_string,
+               struct in_str *input,
+               const char *word,
+               int skip_tabs)
+{
+       o_string heredoc = NULL_O_STRING;
+       int past_EOL = 0;
+       int ch;
+
+       goto jump_in;
+       while (1) {
+               ch = i_getch(input);
+               nommu_addchr(as_string, ch);
+               if (ch == '\n') {
+                       if (strcmp(heredoc.data + past_EOL, word) == 0) {
+                               heredoc.data[past_EOL] = '\0';
+                               debug_printf_parse("parsed heredoc '%s'\n", heredoc.data);
+                               return heredoc.data;
+                       }
+                       do {
+                               o_addchr(&heredoc, ch);
+                               past_EOL = heredoc.length;
+ jump_in:
+                               do {
+                                       ch = i_getch(input);
+                                       nommu_addchr(as_string, ch);
+                               } while (skip_tabs && ch == '\t');
+                       } while (ch == '\n');
+               }
+               if (ch == EOF) {
+                       o_free_unsafe(&heredoc);
+                       return NULL;
+               }
+               o_addchr(&heredoc, ch);
+               nommu_addchr(as_string, ch);
+       }
+}
+
+/* Look at entire parse tree for not-yet-loaded REDIRECT_HEREDOCs
+ * and load them all. There should be exactly heredoc_cnt of them.
+ */
+static int fetch_heredocs(int heredoc_cnt, struct parse_context *ctx, struct in_str *input)
+{
+       struct pipe *pi = ctx->list_head;
+
+       while (pi && heredoc_cnt) {
+               int i;
+               struct command *cmd = pi->cmds;
+
+               debug_printf_parse("fetch_heredocs: num_cmds:%d cmd argv0:'%s'\n",
+                               pi->num_cmds,
+                               cmd->argv ? cmd->argv[0] : "NONE");
+               for (i = 0; i < pi->num_cmds; i++) {
+                       struct redir_struct *redir = cmd->redirects;
+
+                       debug_printf_parse("fetch_heredocs: %d cmd argv0:'%s'\n",
+                                       i, cmd->argv ? cmd->argv[0] : "NONE");
+                       while (redir) {
+                               if (redir->rd_type == REDIRECT_HEREDOC) {
+                                       char *p;
+
+                                       redir->rd_type = REDIRECT_HEREDOC2;
+                                       /* redir->dup is (ab)used to indicate <<- */
+                                       p = fetch_till_str(&ctx->as_string, input,
+                                               redir->rd_filename, redir->rd_dup & HEREDOC_SKIPTABS);
+                                       if (!p) {
+                                               syntax_error("unexpected EOF in here document");
+                                               return 1;
+                                       }
+                                       free(redir->rd_filename);
+                                       redir->rd_filename = p;
+                                       heredoc_cnt--;
+                               }
+                               redir = redir->next;
+                       }
+                       cmd++;
+               }
+               pi = pi->next;
+       }
+#if 0
+       /* Should be 0. If it isn't, it's a parse error */
+       if (heredoc_cnt)
+               bb_error_msg_and_die("heredoc BUG 2");
+#endif
+       return 0;
+}
+
+
+#if ENABLE_HUSH_TICK
+static FILE *generate_stream_from_string(const char *s)
+{
+       FILE *pf;
+       int pid, channel[2];
+#if !BB_MMU
+       char **to_free;
+#endif
+
+       xpipe(channel);
+       pid = BB_MMU ? fork() : vfork();
+       if (pid < 0)
+               bb_perror_msg_and_die(BB_MMU ? "fork" : "vfork");
+
+       if (pid == 0) { /* child */
+               disable_restore_tty_pgrp_on_exit();
+               /* Process substitution is not considered to be usual
+                * 'command execution'.
+                * SUSv3 says ctrl-Z should be ignored, ctrl-C should not.
+                */
+               bb_signals(0
+                       + (1 << SIGTSTP)
+                       + (1 << SIGTTIN)
+                       + (1 << SIGTTOU)
+                       , SIG_IGN);
+               close(channel[0]); /* NB: close _first_, then move fd! */
+               xmove_fd(channel[1], 1);
+               /* Prevent it from trying to handle ctrl-z etc */
+               USE_HUSH_JOB(G.run_list_level = 1;)
+#if BB_MMU
+               reset_traps_to_defaults();
+               parse_and_run_string(s);
+               _exit(G.last_exitcode);
+#else
+       /* We re-execute after vfork on NOMMU. This makes this script safe:
+        * yes "0123456789012345678901234567890" | dd bs=32 count=64k >BIG
+        * huge=`cat BIG` # was blocking here forever
+        * echo OK
+        */
+               re_execute_shell(&to_free,
+                               s,
+                               G.global_argv[0],
+                               G.global_argv + 1);
+#endif
+       }
+
+       /* parent */
+#if ENABLE_HUSH_FAST
+       G.count_SIGCHLD++;
+//bb_error_msg("[%d] fork in generate_stream_from_string: G.count_SIGCHLD:%d G.handled_SIGCHLD:%d", getpid(), G.count_SIGCHLD, G.handled_SIGCHLD);
+#endif
+       enable_restore_tty_pgrp_on_exit();
+#if !BB_MMU
+       free(to_free);
+#endif
+       close(channel[1]);
+       pf = fdopen(channel[0], "r");
+       return pf;
+}
+
+/* Return code is exit status of the process that is run. */
+static int process_command_subs(o_string *dest, const char *s)
+{
+       FILE *pf;
+       struct in_str pipe_str;
+       int ch, eol_cnt;
+
+       pf = generate_stream_from_string(s);
+       if (pf == NULL)
+               return 1;
+       close_on_exec_on(fileno(pf));
+
+       /* Now send results of command back into original context */
+       setup_file_in_str(&pipe_str, pf);
+       eol_cnt = 0;
+       while ((ch = i_getch(&pipe_str)) != EOF) {
+               if (ch == '\n') {
+                       eol_cnt++;
+                       continue;
+               }
+               while (eol_cnt) {
+                       o_addchr(dest, '\n');
+                       eol_cnt--;
+               }
+               o_addQchr(dest, ch);
+       }
+
+       debug_printf("done reading from pipe, pclose()ing\n");
+       /* Note: we got EOF, and we just close the read end of the pipe.
+        * We do not wait for the `cmd` child to terminate. bash and ash do.
+        * Try these:
+        * echo `echo Hi; exec 1>&-; sleep 2` - bash waits 2 sec
+        * `false`; echo $? - bash outputs "1"
+        */
+       fclose(pf);
+       debug_printf("closed FILE from child. return 0\n");
+       return 0;
+}
+#endif
+
+static int parse_group(o_string *dest, struct parse_context *ctx,
+       struct in_str *input, int ch)
+{
+       /* dest contains characters seen prior to ( or {.
+        * Typically it's empty, but for function defs,
+        * it contains function name (without '()'). */
+       struct pipe *pipe_list;
+       int endch;
+       struct command *command = ctx->command;
+
+       debug_printf_parse("parse_group entered\n");
+#if ENABLE_HUSH_FUNCTIONS
+       if (ch == '(' && !dest->o_quoted) {
+               if (dest->length)
+                       if (done_word(dest, ctx))
+                               return 1;
+               if (!command->argv)
+                       goto skip; /* (... */
+               if (command->argv[1]) { /* word word ... (... */
+                       syntax_error_unexpected_ch('(');
+                       return 1;
+               }
+               /* it is "word(..." or "word (..." */
+               do
+                       ch = i_getch(input);
+               while (ch == ' ' || ch == '\t');
+               if (ch != ')') {
+                       syntax_error_unexpected_ch(ch);
+                       return 1;
+               }
+               nommu_addchr(&ctx->as_string, ch);
+               do
+                       ch = i_getch(input);
+               while (ch == ' ' || ch == '\t' || ch == '\n');
+               if (ch != '{') {
+                       syntax_error_unexpected_ch(ch);
+                       return 1;
+               }
+               nommu_addchr(&ctx->as_string, ch);
+               command->grp_type = GRP_FUNCTION;
+               goto skip;
+       }
+#endif
+       if (command->argv /* word [word]{... */
+        || dest->length /* word{... */
+        || dest->o_quoted /* ""{... */
+       ) {
+               syntax_error(NULL);
+               debug_printf_parse("parse_group return 1: "
+                       "syntax error, groups and arglists don't mix\n");
+               return 1;
+       }
+
+#if ENABLE_HUSH_FUNCTIONS
+ skip:
+#endif
+       endch = '}';
+       if (ch == '(') {
+               endch = ')';
+               command->grp_type = GRP_SUBSHELL;
+       } else {
+               /* bash does not allow "{echo...", requires whitespace */
+               ch = i_getch(input);
+               if (ch != ' ' && ch != '\t' && ch != '\n') {
+                       syntax_error_unexpected_ch(ch);
+                       return 1;
+               }
+               nommu_addchr(&ctx->as_string, ch);
+       }
+
+       {
+#if !BB_MMU
+               char *as_string = NULL;
+#endif
+               pipe_list = parse_stream(&as_string, input, endch);
+#if !BB_MMU
+               if (as_string)
+                       o_addstr(&ctx->as_string, as_string);
+#endif
+               /* empty ()/{} or parse error? */
+               if (!pipe_list || pipe_list == ERR_PTR) {
+                       /* parse_stream already emitted error msg */
+#if !BB_MMU
+                       free(as_string);
+#endif
+                       debug_printf_parse("parse_group return 1: "
+                               "parse_stream returned %p\n", pipe_list);
+                       return 1;
+               }
+               command->group = pipe_list;
+#if !BB_MMU
+               as_string[strlen(as_string) - 1] = '\0'; /* plink ')' or '}' */
+               command->group_as_string = as_string;
+               debug_printf_parse("end of group, remembering as:'%s'\n",
+                               command->group_as_string);
+#endif
+       }
+       debug_printf_parse("parse_group return 0\n");
+       return 0;
+       /* command remains "open", available for possible redirects */
+}
+
+#if ENABLE_HUSH_TICK || ENABLE_SH_MATH_SUPPORT
+/* Subroutines for copying $(...) and `...` things */
+static void add_till_backquote(o_string *dest, struct in_str *input);
+/* '...' */
+static void add_till_single_quote(o_string *dest, struct in_str *input)
+{
+       while (1) {
+               int ch = i_getch(input);
+               if (ch == EOF) {
+                       syntax_error_unterm_ch('\'');
+                       /*xfunc_die(); - redundant */
+               }
+               if (ch == '\'')
+                       return;
+               o_addchr(dest, ch);
+       }
+}
+/* "...\"...`..`...." - do we need to handle "...$(..)..." too? */
+static void add_till_double_quote(o_string *dest, struct in_str *input)
+{
+       while (1) {
+               int ch = i_getch(input);
+               if (ch == EOF) {
+                       syntax_error_unterm_ch('"');
+                       /*xfunc_die(); - redundant */
+               }
+               if (ch == '"')
+                       return;
+               if (ch == '\\') {  /* \x. Copy both chars. */
+                       o_addchr(dest, ch);
+                       ch = i_getch(input);
+               }
+               o_addchr(dest, ch);
+               if (ch == '`') {
+                       add_till_backquote(dest, input);
+                       o_addchr(dest, ch);
+                       continue;
+               }
+               //if (ch == '$') ...
+       }
+}
+/* Process `cmd` - copy contents until "`" is seen. Complicated by
+ * \` quoting.
+ * "Within the backquoted style of command substitution, backslash
+ * shall retain its literal meaning, except when followed by: '$', '`', or '\'.
+ * The search for the matching backquote shall be satisfied by the first
+ * backquote found without a preceding backslash; during this search,
+ * if a non-escaped backquote is encountered within a shell comment,
+ * a here-document, an embedded command substitution of the $(command)
+ * form, or a quoted string, undefined results occur. A single-quoted
+ * or double-quoted string that begins, but does not end, within the
+ * "`...`" sequence produces undefined results."
+ * Example                               Output
+ * echo `echo '\'TEST\`echo ZZ\`BEST`    \TESTZZBEST
+ */
+static void add_till_backquote(o_string *dest, struct in_str *input)
+{
+       while (1) {
+               int ch = i_getch(input);
+               if (ch == EOF) {
+                       syntax_error_unterm_ch('`');
+                       /*xfunc_die(); - redundant */
+               }
+               if (ch == '`')
+                       return;
+               if (ch == '\\') {
+                       /* \x. Copy both chars unless it is \` */
+                       int ch2 = i_getch(input);
+                       if (ch2 == EOF) {
+                               syntax_error_unterm_ch('`');
+                               /*xfunc_die(); - redundant */
+                       }
+                       if (ch2 != '`' && ch2 != '$' && ch2 != '\\')
+                               o_addchr(dest, ch);
+                       ch = ch2;
+               }
+               o_addchr(dest, ch);
+       }
+}
+/* Process $(cmd) - copy contents until ")" is seen. Complicated by
+ * quoting and nested ()s.
+ * "With the $(command) style of command substitution, all characters
+ * following the open parenthesis to the matching closing parenthesis
+ * constitute the command. Any valid shell script can be used for command,
+ * except a script consisting solely of redirections which produces
+ * unspecified results."
+ * Example                              Output
+ * echo $(echo '(TEST)' BEST)           (TEST) BEST
+ * echo $(echo 'TEST)' BEST)            TEST) BEST
+ * echo $(echo \(\(TEST\) BEST)         ((TEST) BEST
+ */
+static void add_till_closing_paren(o_string *dest, struct in_str *input, bool dbl)
+{
+       int count = 0;
+       while (1) {
+               int ch = i_getch(input);
+               if (ch == EOF) {
+                       syntax_error_unterm_ch(')');
+                       /*xfunc_die(); - redundant */
+               }
+               if (ch == '(')
+                       count++;
+               if (ch == ')') {
+                       if (--count < 0) {
+                               if (!dbl)
+                                       break;
+                               if (i_peek(input) == ')') {
+                                       i_getch(input);
+                                       break;
+                               }
+                       }
+               }
+               o_addchr(dest, ch);
+               if (ch == '\'') {
+                       add_till_single_quote(dest, input);
+                       o_addchr(dest, ch);
+                       continue;
+               }
+               if (ch == '"') {
+                       add_till_double_quote(dest, input);
+                       o_addchr(dest, ch);
+                       continue;
+               }
+               if (ch == '\\') {
+                       /* \x. Copy verbatim. Important for  \(, \) */
+                       ch = i_getch(input);
+                       if (ch == EOF) {
+                               syntax_error_unterm_ch(')');
+                               /*xfunc_die(); - redundant */
+                       }
+                       o_addchr(dest, ch);
+                       continue;
+               }
+       }
+}
+#endif /* ENABLE_HUSH_TICK || ENABLE_SH_MATH_SUPPORT */
+
+/* Return code: 0 for OK, 1 for syntax error */
+#if BB_MMU
+#define handle_dollar(as_string, dest, input) \
+       handle_dollar(dest, input)
+#endif
+static int handle_dollar(o_string *as_string,
+               o_string *dest,
+               struct in_str *input)
+{
+       int expansion;
+       int ch = i_peek(input);  /* first character after the $ */
+       unsigned char quote_mask = dest->o_escape ? 0x80 : 0;
+
+       debug_printf_parse("handle_dollar entered: ch='%c'\n", ch);
+       if (isalpha(ch)) {
+               ch = i_getch(input);
+               nommu_addchr(as_string, ch);
+ make_var:
+               o_addchr(dest, SPECIAL_VAR_SYMBOL);
+               while (1) {
+                       debug_printf_parse(": '%c'\n", ch);
+                       o_addchr(dest, ch | quote_mask);
+                       quote_mask = 0;
+                       ch = i_peek(input);
+                       if (!isalnum(ch) && ch != '_')
+                               break;
+                       ch = i_getch(input);
+                       nommu_addchr(as_string, ch);
+               }
+               o_addchr(dest, SPECIAL_VAR_SYMBOL);
+       } else if (isdigit(ch)) {
+ make_one_char_var:
+               ch = i_getch(input);
+               nommu_addchr(as_string, ch);
+               o_addchr(dest, SPECIAL_VAR_SYMBOL);
+               debug_printf_parse(": '%c'\n", ch);
+               o_addchr(dest, ch | quote_mask);
+               o_addchr(dest, SPECIAL_VAR_SYMBOL);
+       } else switch (ch) {
+       case '$': /* pid */
+       case '!': /* last bg pid */
+       case '?': /* last exit code */
+       case '#': /* number of args */
+       case '*': /* args */
+       case '@': /* args */
+               goto make_one_char_var;
+       case '{': {
+               bool first_char, all_digits;
+
+               o_addchr(dest, SPECIAL_VAR_SYMBOL);
+               ch = i_getch(input);
+               nommu_addchr(as_string, ch);
+               /* TODO: maybe someone will try to escape the '}' */
+               expansion = 0;
+               first_char = true;
+               all_digits = false;
+               while (1) {
+                       ch = i_getch(input);
+                       nommu_addchr(as_string, ch);
+                       if (ch == '}') {
+                               break;
+                       }
+
+                       if (first_char) {
+                               if (ch == '#') {
+                                       /* ${#var}: length of var contents */
+                                       goto char_ok;
+                               }
+                               if (isdigit(ch)) {
+                                       all_digits = true;
+                                       goto char_ok;
+                               }
+                               /* They're being verbose and doing ${?} */
+                               if (i_peek(input) == '}' && strchr("$!?#*@_", ch))
+                                       goto char_ok;
+                       }
+
+                       if (expansion < 2
+                        && (  (all_digits && !isdigit(ch))
+                           || (!all_digits && !isalnum(ch) && ch != '_')
+                           )
+                       ) {
+                               /* handle parameter expansions
+                                * http://www.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html#tag_02_06_02
+                                */
+                               if (first_char)
+                                       goto case_default;
+                               switch (ch) {
+                               case ':': /* null modifier */
+                                       if (expansion == 0) {
+                                               debug_printf_parse(": null modifier\n");
+                                               ++expansion;
+                                               break;
+                                       }
+                                       goto case_default;
+                               case '#': /* remove prefix */
+                               case '%': /* remove suffix */
+                                       if (expansion == 0) {
+                                               debug_printf_parse(": remove suffix/prefix\n");
+                                               expansion = 2;
+                                               break;
+                                       }
+                                       goto case_default;
+                               case '-': /* default value */
+                               case '=': /* assign default */
+                               case '+': /* alternative */
+                               case '?': /* error indicate */
+                                       debug_printf_parse(": parameter expansion\n");
+                                       expansion = 2;
+                                       break;
+                               default:
+                               case_default:
+                                       syntax_error_unterm_str("${name}");
+                                       debug_printf_parse("handle_dollar return 1: unterminated ${name}\n");
+                                       return 1;
+                               }
+                       }
+ char_ok:
+                       debug_printf_parse(": '%c'\n", ch);
+                       o_addchr(dest, ch | quote_mask);
+                       quote_mask = 0;
+                       first_char = false;
+               } /* while (1) */
+               o_addchr(dest, SPECIAL_VAR_SYMBOL);
+               break;
+       }
+#if (ENABLE_SH_MATH_SUPPORT || ENABLE_HUSH_TICK)
+       case '(': {
+# if !BB_MMU
+               int pos;
+# endif
+               ch = i_getch(input);
+               nommu_addchr(as_string, ch);
+# if ENABLE_SH_MATH_SUPPORT
+               if (i_peek(input) == '(') {
+                       ch = i_getch(input);
+                       nommu_addchr(as_string, ch);
+                       o_addchr(dest, SPECIAL_VAR_SYMBOL);
+                       o_addchr(dest, /*quote_mask |*/ '+');
+#  if !BB_MMU
+                       pos = dest->length;
+#  endif
+                       add_till_closing_paren(dest, input, true);
+#  if !BB_MMU
+                       if (as_string) {
+                               o_addstr(as_string, dest->data + pos);
+                               o_addchr(as_string, ')');
+                               o_addchr(as_string, ')');
+                       }
+#  endif
+                       o_addchr(dest, SPECIAL_VAR_SYMBOL);
+                       break;
+               }
+# endif
+# if ENABLE_HUSH_TICK
+               o_addchr(dest, SPECIAL_VAR_SYMBOL);
+               o_addchr(dest, quote_mask | '`');
+#  if !BB_MMU
+               pos = dest->length;
+#  endif
+               add_till_closing_paren(dest, input, false);
+#  if !BB_MMU
+               if (as_string) {
+                       o_addstr(as_string, dest->data + pos);
+                       o_addchr(as_string, '`');
+               }
+#  endif
+               o_addchr(dest, SPECIAL_VAR_SYMBOL);
+# endif
+               break;
+       }
+#endif
+       case '_':
+               ch = i_getch(input);
+               nommu_addchr(as_string, ch);
+               ch = i_peek(input);
+               if (isalnum(ch)) { /* it's $_name or $_123 */
+                       ch = '_';
+                       goto make_var;
+               }
+               /* else: it's $_ */
+       /* TODO: */
+       /* $_ Shell or shell script name; or last cmd name */
+       /* $- Option flags set by set builtin or shell options (-i etc) */
+       default:
+               o_addQchr(dest, '$');
+       }
+       debug_printf_parse("handle_dollar return 0\n");
+       return 0;
+}
+
+#if BB_MMU
+#define parse_stream_dquoted(as_string, dest, input, dquote_end) \
+       parse_stream_dquoted(dest, input, dquote_end)
+#endif
+static int parse_stream_dquoted(o_string *as_string,
+               o_string *dest,
+               struct in_str *input,
+               int dquote_end)
+{
+       int ch;
+       int next;
+
+ again:
+       ch = i_getch(input);
+       if (ch != EOF)
+               nommu_addchr(as_string, ch);
+       if (ch == dquote_end) { /* may be only '"' or EOF */
+               if (dest->o_assignment == NOT_ASSIGNMENT)
+                       dest->o_escape ^= 1;
+               debug_printf_parse("parse_stream_dquoted return 0\n");
+               return 0;
+       }
+       /* note: can't move it above ch == dquote_end check! */
+       if (ch == EOF) {
+               syntax_error_unterm_ch('"');
+               /*xfunc_die(); - redundant */
+       }
+       next = '\0';
+       if (ch != '\n') {
+               next = i_peek(input);
+       }
+       debug_printf_parse(": ch=%c (%d) escape=%d\n",
+                                       ch, ch, dest->o_escape);
+       if (ch == '\\') {
+               if (next == EOF) {
+                       syntax_error("\\<eof>");
+                       xfunc_die();
+               }
+               /* bash:
+                * "The backslash retains its special meaning [in "..."]
+                * only when followed by one of the following characters:
+                * $, `, ", \, or <newline>.  A double quote may be quoted
+                * within double quotes by preceding it with a backslash."
+                */
+               if (strchr("$`\"\\\n", next) != NULL) {
+                       ch = i_getch(input);
+                       if (ch != '\n') {
+                               o_addqchr(dest, ch);
+                               nommu_addchr(as_string, ch);
+                       }
+               } else {
+                       o_addqchr(dest, '\\');
+                       nommu_addchr(as_string, '\\');
+               }
+               goto again;
+       }
+       if (ch == '$') {
+               if (handle_dollar(as_string, dest, input) != 0) {
+                       debug_printf_parse("parse_stream_dquoted return 1: "
+                                       "handle_dollar returned non-0\n");
+                       return 1;
+               }
+               goto again;
+       }
+#if ENABLE_HUSH_TICK
+       if (ch == '`') {
+               //int pos = dest->length;
+               o_addchr(dest, SPECIAL_VAR_SYMBOL);
+               o_addchr(dest, 0x80 | '`');
+               add_till_backquote(dest, input);
+               o_addchr(dest, SPECIAL_VAR_SYMBOL);
+               //debug_printf_subst("SUBST RES3 '%s'\n", dest->data + pos);
+               goto again;
+       }
+#endif
+       o_addQchr(dest, ch);
+       if (ch == '='
+        && (dest->o_assignment == MAYBE_ASSIGNMENT
+           || dest->o_assignment == WORD_IS_KEYWORD)
+        && is_well_formed_var_name(dest->data, '=')
+       ) {
+               dest->o_assignment = DEFINITELY_ASSIGNMENT;
+       }
+       goto again;
+}
+
+/*
+ * Scan input until EOF or end_trigger char.
+ * Return a list of pipes to execute, or NULL on EOF
+ * or if end_trigger character is met.
+ * On syntax error, exit is shell is not interactive,
+ * reset parsing machinery and start parsing anew,
+ * or return ERR_PTR.
+ */
+static struct pipe *parse_stream(char **pstring,
+               struct in_str *input,
+               int end_trigger)
+{
+       struct parse_context ctx;
+       o_string dest = NULL_O_STRING;
+       int is_in_dquote;
+       int heredoc_cnt;
+
+       /* Double-quote state is handled in the state variable is_in_dquote.
+        * A single-quote triggers a bypass of the main loop until its mate is
+        * found.  When recursing, quote state is passed in via dest->o_escape.
+        */
+       debug_printf_parse("parse_stream entered, end_trigger='%c'\n",
+                       end_trigger ? : 'X');
+       debug_enter();
+
+       G.ifs = get_local_var_value("IFS");
+       if (G.ifs == NULL)
+               G.ifs = " \t\n";
+
+ reset:
+#if ENABLE_HUSH_INTERACTIVE
+       input->promptmode = 0; /* PS1 */
+#endif
+       /* dest.o_assignment = MAYBE_ASSIGNMENT; - already is */
+       initialize_context(&ctx);
+       is_in_dquote = 0;
+       heredoc_cnt = 0;
+       while (1) {
+               const char *is_ifs;
+               const char *is_special;
+               int ch;
+               int next;
+               int redir_fd;
+               redir_type redir_style;
+
+               if (is_in_dquote) {
+                       /* dest.o_quoted = 1; - already is (see below) */
+                       if (parse_stream_dquoted(&ctx.as_string, &dest, input, '"')) {
+                               goto parse_error;
+                       }
+                       /* We reached closing '"' */
+                       is_in_dquote = 0;
+               }
+               ch = i_getch(input);
+               debug_printf_parse(": ch=%c (%d) escape=%d\n",
+                                               ch, ch, dest.o_escape);
+               if (ch == EOF) {
+                       struct pipe *pi;
+
+                       if (heredoc_cnt) {
+                               syntax_error_unterm_str("here document");
+                               goto parse_error;
+                       }
+                       /* end_trigger == '}' case errors out earlier,
+                        * checking only ')' */
+                       if (end_trigger == ')') {
+                               syntax_error_unterm_ch('('); /* exits */
+                               /* goto parse_error; */
+                       }
+
+                       if (done_word(&dest, &ctx)) {
+                               goto parse_error;
+                       }
+                       o_free(&dest);
+                       done_pipe(&ctx, PIPE_SEQ);
+                       pi = ctx.list_head;
+                       /* If we got nothing... */
+                       /* (this makes bare "&" cmd a no-op.
+                        * bash says: "syntax error near unexpected token '&'") */
+                       if (pi->num_cmds == 0
+                           IF_HAS_KEYWORDS( && pi->res_word == RES_NONE)
+                       ) {
+                               free_pipe_list(pi);
+                               pi = NULL;
+                       }
+#if !BB_MMU
+                       debug_printf_parse("as_string '%s'\n", ctx.as_string.data);
+                       if (pstring)
+                               *pstring = ctx.as_string.data;
+                       else
+                               o_free_unsafe(&ctx.as_string);
+#endif
+                       debug_leave();
+                       debug_printf_parse("parse_stream return %p\n", pi);
+                       return pi;
+               }
+               nommu_addchr(&ctx.as_string, ch);
+               is_ifs = strchr(G.ifs, ch);
+               is_special = strchr("<>;&|(){}#'" /* special outside of "str" */
+                               "\\$\"" USE_HUSH_TICK("`") /* always special */
+                               , ch);
+
+               if (!is_special && !is_ifs) { /* ordinary char */
+ ordinary_char:
+                       o_addQchr(&dest, ch);
+                       if ((dest.o_assignment == MAYBE_ASSIGNMENT
+                           || dest.o_assignment == WORD_IS_KEYWORD)
+                        && ch == '='
+                        && is_well_formed_var_name(dest.data, '=')
+                       ) {
+                               dest.o_assignment = DEFINITELY_ASSIGNMENT;
+                       }
+                       continue;
+               }
+
+               if (is_ifs) {
+                       if (done_word(&dest, &ctx)) {
+                               goto parse_error;
+                       }
+                       if (ch == '\n') {
+#if ENABLE_HUSH_CASE
+                               /* "case ... in <newline> word) ..." -
+                                * newlines are ignored (but ';' wouldn't be) */
+                               if (ctx.command->argv == NULL
+                                && ctx.ctx_res_w == RES_MATCH
+                               ) {
+                                       continue;
+                               }
+#endif
+                               /* Treat newline as a command separator. */
+                               done_pipe(&ctx, PIPE_SEQ);
+                               debug_printf_parse("heredoc_cnt:%d\n", heredoc_cnt);
+                               if (heredoc_cnt) {
+                                       if (fetch_heredocs(heredoc_cnt, &ctx, input)) {
+                                               goto parse_error;
+                                       }
+                                       heredoc_cnt = 0;
+                               }
+                               dest.o_assignment = MAYBE_ASSIGNMENT;
+                               ch = ';';
+                               /* note: if (is_ifs) continue;
+                                * will still trigger for us */
+                       }
+               }
+
+               /* "cmd}" or "cmd }..." without semicolon or &:
+                * } is an ordinary char in this case, even inside { cmd; }
+                * Pathological example: { ""}; } should exec "}" cmd
+                */
+               if (ch == '}') {
+                       if (!IS_NULL_CMD(ctx.command) /* cmd } */
+                        || dest.length != 0 /* word} */
+                        || dest.o_quoted    /* ""} */
+                       ) {
+                               goto ordinary_char;
+                       }
+                       if (!IS_NULL_PIPE(ctx.pipe)) /* cmd | } */
+                               goto skip_end_trigger;
+                       /* else: } does terminate a group */
+               }
+
+               if (end_trigger && end_trigger == ch
+                && (ch != ';' || heredoc_cnt == 0)
+#if ENABLE_HUSH_CASE
+                && (ch != ')'
+                   || ctx.ctx_res_w != RES_MATCH
+                   || (!dest.o_quoted && strcmp(dest.data, "esac") == 0)
+                   )
+#endif
+               ) {
+                       if (heredoc_cnt) {
+                               /* This is technically valid:
+                                * { cat <<HERE; }; echo Ok
+                                * heredoc
+                                * heredoc
+                                * HERE
+                                * but we don't support this.
+                                * We require heredoc to be in enclosing {}/(),
+                                * if any.
+                                */
+                               syntax_error_unterm_str("here document");
+                               goto parse_error;
+                       }
+                       if (done_word(&dest, &ctx)) {
+                               goto parse_error;
+                       }
+                       done_pipe(&ctx, PIPE_SEQ);
+                       dest.o_assignment = MAYBE_ASSIGNMENT;
+                       /* Do we sit outside of any if's, loops or case's? */
+                       if (!HAS_KEYWORDS
+                        IF_HAS_KEYWORDS(|| (ctx.ctx_res_w == RES_NONE && ctx.old_flag == 0))
+                       ) {
+                               o_free(&dest);
+#if !BB_MMU
+                               debug_printf_parse("as_string '%s'\n", ctx.as_string.data);
+                               if (pstring)
+                                       *pstring = ctx.as_string.data;
+                               else
+                                       o_free_unsafe(&ctx.as_string);
+#endif
+                               debug_leave();
+                               debug_printf_parse("parse_stream return %p: "
+                                               "end_trigger char found\n",
+                                               ctx.list_head);
+                               return ctx.list_head;
+                       }
+               }
+ skip_end_trigger:
+               if (is_ifs)
+                       continue;
+
+               next = '\0';
+               if (ch != '\n') {
+                       next = i_peek(input);
+               }
+
+               /* Catch <, > before deciding whether this word is
+                * an assignment. a=1 2>z b=2: b=2 is still assignment */
+               switch (ch) {
+               case '>':
+                       redir_fd = redirect_opt_num(&dest);
+                       if (done_word(&dest, &ctx)) {
+                               goto parse_error;
+                       }
+                       redir_style = REDIRECT_OVERWRITE;
+                       if (next == '>') {
+                               redir_style = REDIRECT_APPEND;
+                               ch = i_getch(input);
+                               nommu_addchr(&ctx.as_string, ch);
+                       }
+#if 0
+                       else if (next == '(') {
+                               syntax_error(">(process) not supported");
+                               goto parse_error;
+                       }
+#endif
+                       if (parse_redirect(&ctx, redir_fd, redir_style, input))
+                               goto parse_error;
+                       continue; /* back to top of while (1) */
+               case '<':
+                       redir_fd = redirect_opt_num(&dest);
+                       if (done_word(&dest, &ctx)) {
+                               goto parse_error;
+                       }
+                       redir_style = REDIRECT_INPUT;
+                       if (next == '<') {
+                               redir_style = REDIRECT_HEREDOC;
+                               heredoc_cnt++;
+                               debug_printf_parse("++heredoc_cnt=%d\n", heredoc_cnt);
+                               ch = i_getch(input);
+                               nommu_addchr(&ctx.as_string, ch);
+                       } else if (next == '>') {
+                               redir_style = REDIRECT_IO;
+                               ch = i_getch(input);
+                               nommu_addchr(&ctx.as_string, ch);
+                       }
+#if 0
+                       else if (next == '(') {
+                               syntax_error("<(process) not supported");
+                               goto parse_error;
+                       }
+#endif
+                       if (parse_redirect(&ctx, redir_fd, redir_style, input))
+                               goto parse_error;
+                       continue; /* back to top of while (1) */
+               }
+
+               if (dest.o_assignment == MAYBE_ASSIGNMENT
+                /* check that we are not in word in "a=1 2>word b=1": */
+                && !ctx.pending_redirect
+               ) {
+                       /* ch is a special char and thus this word
+                        * cannot be an assignment */
+                       dest.o_assignment = NOT_ASSIGNMENT;
+               }
+
+               switch (ch) {
+               case '#':
+                       if (dest.length == 0) {
+                               while (1) {
+                                       ch = i_peek(input);
+                                       if (ch == EOF || ch == '\n')
+                                               break;
+                                       i_getch(input);
+                                       /* note: we do not add it to &ctx.as_string */
+                               }
+                               nommu_addchr(&ctx.as_string, '\n');
+                       } else {
+                               o_addQchr(&dest, ch);
+                       }
+                       break;
+               case '\\':
+                       if (next == EOF) {
+                               syntax_error("\\<eof>");
+                               xfunc_die();
+                       }
+                       ch = i_getch(input);
+                       if (ch != '\n') {
+                               o_addchr(&dest, '\\');
+                               nommu_addchr(&ctx.as_string, '\\');
+                               o_addchr(&dest, ch);
+                               nommu_addchr(&ctx.as_string, ch);
+                               /* Example: echo Hello \2>file
+                                * we need to know that word 2 is quoted */
+                               dest.o_quoted = 1;
+                       }
+                       break;
+               case '$':
+                       if (handle_dollar(&ctx.as_string, &dest, input) != 0) {
+                               debug_printf_parse("parse_stream parse error: "
+                                       "handle_dollar returned non-0\n");
+                               goto parse_error;
+                       }
+                       break;
+               case '\'':
+                       dest.o_quoted = 1;
+                       while (1) {
+                               ch = i_getch(input);
+                               if (ch == EOF) {
+                                       syntax_error_unterm_ch('\'');
+                                       /*xfunc_die(); - redundant */
+                               }
+                               nommu_addchr(&ctx.as_string, ch);
+                               if (ch == '\'')
+                                       break;
+                               o_addqchr(&dest, ch);
+                       }
+                       break;
+               case '"':
+                       dest.o_quoted = 1;
+                       is_in_dquote ^= 1; /* invert */
+                       if (dest.o_assignment == NOT_ASSIGNMENT)
+                               dest.o_escape ^= 1;
+                       break;
+#if ENABLE_HUSH_TICK
+               case '`': {
+#if !BB_MMU
+                       int pos;
+#endif
+                       o_addchr(&dest, SPECIAL_VAR_SYMBOL);
+                       o_addchr(&dest, '`');
+#if !BB_MMU
+                       pos = dest.length;
+#endif
+                       add_till_backquote(&dest, input);
+#if !BB_MMU
+                       o_addstr(&ctx.as_string, dest.data + pos);
+                       o_addchr(&ctx.as_string, '`');
+#endif
+                       o_addchr(&dest, SPECIAL_VAR_SYMBOL);
+                       //debug_printf_subst("SUBST RES3 '%s'\n", dest.data + pos);
+                       break;
+               }
+#endif
+               case ';':
+#if ENABLE_HUSH_CASE
+ case_semi:
+#endif
+                       if (done_word(&dest, &ctx)) {
+                               goto parse_error;
+                       }
+                       done_pipe(&ctx, PIPE_SEQ);
+#if ENABLE_HUSH_CASE
+                       /* Eat multiple semicolons, detect
+                        * whether it means something special */
+                       while (1) {
+                               ch = i_peek(input);
+                               if (ch != ';')
+                                       break;
+                               ch = i_getch(input);
+                               nommu_addchr(&ctx.as_string, ch);
+                               if (ctx.ctx_res_w == RES_CASE_BODY) {
+                                       ctx.ctx_dsemicolon = 1;
+                                       ctx.ctx_res_w = RES_MATCH;
+                                       break;
+                               }
+                       }
+#endif
+ new_cmd:
+                       /* We just finished a cmd. New one may start
+                        * with an assignment */
+                       dest.o_assignment = MAYBE_ASSIGNMENT;
+                       break;
+               case '&':
+                       if (done_word(&dest, &ctx)) {
+                               goto parse_error;
+                       }
+                       if (next == '&') {
+                               ch = i_getch(input);
+                               nommu_addchr(&ctx.as_string, ch);
+                               done_pipe(&ctx, PIPE_AND);
+                       } else {
+                               done_pipe(&ctx, PIPE_BG);
+                       }
+                       goto new_cmd;
+               case '|':
+                       if (done_word(&dest, &ctx)) {
+                               goto parse_error;
+                       }
+#if ENABLE_HUSH_CASE
+                       if (ctx.ctx_res_w == RES_MATCH)
+                               break; /* we are in case's "word | word)" */
+#endif
+                       if (next == '|') { /* || */
+                               ch = i_getch(input);
+                               nommu_addchr(&ctx.as_string, ch);
+                               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);
+                       }
+                       goto new_cmd;
+               case '(':
+#if ENABLE_HUSH_CASE
+                       /* "case... in [(]word)..." - skip '(' */
+                       if (ctx.ctx_res_w == RES_MATCH
+                        && ctx.command->argv == NULL /* not (word|(... */
+                        && dest.length == 0 /* not word(... */
+                        && dest.o_quoted == 0 /* not ""(... */
+                       ) {
+                               continue;
+                       }
+#endif
+               case '{':
+                       if (parse_group(&dest, &ctx, input, ch) != 0) {
+                               goto parse_error;
+                       }
+                       goto new_cmd;
+               case ')':
+#if ENABLE_HUSH_CASE
+                       if (ctx.ctx_res_w == RES_MATCH)
+                               goto case_semi;
+#endif
+               case '}':
+                       /* proper use of this character is caught by end_trigger:
+                        * if we see {, we call parse_group(..., end_trigger='}')
+                        * and it will match } earlier (not here). */
+                       syntax_error_unexpected_ch(ch);
+                       goto parse_error;
+               default:
+                       if (HUSH_DEBUG)
+                               bb_error_msg_and_die("BUG: unexpected %c\n", ch);
+               }
+       } /* while (1) */
+
+ parse_error:
+       {
+               struct parse_context *pctx;
+               IF_HAS_KEYWORDS(struct parse_context *p2;)
+
+               /* Clean up allocated tree.
+                * Samples for finding leaks on syntax error recovery path.
+                * Run them from interactive shell, watch pmap `pidof hush`.
+                * while if false; then false; fi do break; done
+                * (bash accepts it)
+                * while if false; then false; fi; do break; fi
+                * Samples to catch leaks at execution:
+                * while if (true | {true;}); then echo ok; fi; do break; done
+                * while if (true | {true;}); then echo ok; fi; do (if echo ok; break; then :; fi) | cat; break; done
+                */
+               pctx = &ctx;
+               do {
+                       /* Update pipe/command counts,
+                        * otherwise freeing may miss some */
+                       done_pipe(pctx, PIPE_SEQ);
+                       debug_printf_clean("freeing list %p from ctx %p\n",
+                                       pctx->list_head, pctx);
+                       debug_print_tree(pctx->list_head, 0);
+                       free_pipe_list(pctx->list_head);
+                       debug_printf_clean("freed list %p\n", pctx->list_head);
+#if !BB_MMU
+                       o_free_unsafe(&pctx->as_string);
+#endif
+                       IF_HAS_KEYWORDS(p2 = pctx->stack;)
+                       if (pctx != &ctx) {
+                               free(pctx);
+                       }
+                       IF_HAS_KEYWORDS(pctx = p2;)
+               } while (HAS_KEYWORDS && pctx);
+               /* Free text, clear all dest fields */
+               o_free(&dest);
+               /* If we are not in top-level parse, we return,
+                * our caller will propagate error.
+                */
+               if (end_trigger != ';') {
+#if !BB_MMU
+                       if (pstring)
+                               *pstring = NULL;
+#endif
+                       debug_leave();
+                       return ERR_PTR;
+               }
+               /* Discard cached input, force prompt */
+               input->p = NULL;
+               USE_HUSH_INTERACTIVE(input->promptme = 1;)
+               goto reset;
+       }
+}
+
+/* Executing from string: eval, sh -c '...'
+ *          or from file: /etc/profile, . file, sh <script>, sh (intereactive)
+ * end_trigger controls how often we stop parsing
+ * NUL: parse all, execute, return
+ * ';': parse till ';' or newline, execute, repeat till EOF
+ */
+static void parse_and_run_stream(struct in_str *inp, int end_trigger)
+{
+       while (1) {
+               struct pipe *pipe_list;
+
+               pipe_list = parse_stream(NULL, inp, end_trigger);
+               if (!pipe_list) /* EOF */
+                       break;
+               debug_print_tree(pipe_list, 0);
+               debug_printf_exec("parse_and_run_stream: run_and_free_list\n");
+               run_and_free_list(pipe_list);
+       }
+}
+
+static void parse_and_run_string(const char *s)
+{
+       struct in_str input;
+       setup_string_in_str(&input, s);
+       parse_and_run_stream(&input, '\0');
+}
+
+static void parse_and_run_file(FILE *f)
+{
+       struct in_str input;
+       setup_file_in_str(&input, f);
+       parse_and_run_stream(&input, ';');
+}
+
+/* Called a few times only (or even once if "sh -c") */
+static void block_signals(int second_time)
+{
+       unsigned sig;
+       unsigned mask;
+
+       mask = (1 << SIGQUIT);
+       if (G_interactive_fd) {
+               mask = (1 << SIGQUIT) | SPECIAL_INTERACTIVE_SIGS;
+               if (G_saved_tty_pgrp) /* we have ctty, job control sigs work */
+                       mask |= SPECIAL_JOB_SIGS;
+       }
+       G.non_DFL_mask = mask;
+
+       if (!second_time)
+               sigprocmask(SIG_SETMASK, NULL, &G.blocked_set);
+       sig = 0;
+       while (mask) {
+               if (mask & 1)
+                       sigaddset(&G.blocked_set, sig);
+               mask >>= 1;
+               sig++;
+       }
+       sigdelset(&G.blocked_set, SIGCHLD);
+
+       sigprocmask(SIG_SETMASK, &G.blocked_set,
+                       second_time ? NULL : &G.inherited_set);
+       /* POSIX allows shell to re-enable SIGCHLD
+        * even if it was SIG_IGN on entry */
+#if ENABLE_HUSH_FAST
+       G.count_SIGCHLD++; /* ensure it is != G.handled_SIGCHLD */
+       if (!second_time)
+               signal(SIGCHLD, SIGCHLD_handler);
+#else
+       if (!second_time)
+               signal(SIGCHLD, SIG_DFL);
+#endif
+}
+
+#if ENABLE_HUSH_JOB
+/* helper */
+static void maybe_set_to_sigexit(int sig)
+{
+       void (*handler)(int);
+       /* non_DFL_mask'ed signals are, well, masked,
+        * no need to set handler for them.
+        */
+       if (!((G.non_DFL_mask >> sig) & 1)) {
+               handler = signal(sig, sigexit);
+               if (handler == SIG_IGN) /* oops... restore back to IGN! */
+                       signal(sig, handler);
+       }
+}
+/* Set handlers to restore tty pgrp and exit */
+static void set_fatal_handlers(void)
+{
+       /* We _must_ restore tty pgrp on fatal signals */
+       if (HUSH_DEBUG) {
+               maybe_set_to_sigexit(SIGILL );
+               maybe_set_to_sigexit(SIGFPE );
+               maybe_set_to_sigexit(SIGBUS );
+               maybe_set_to_sigexit(SIGSEGV);
+               maybe_set_to_sigexit(SIGTRAP);
+       } /* else: hush is perfect. what SEGV? */
+       maybe_set_to_sigexit(SIGABRT);
+       /* bash 3.2 seems to handle these just like 'fatal' ones */
+       maybe_set_to_sigexit(SIGPIPE);
+       maybe_set_to_sigexit(SIGALRM);
+       /* if we are interactive, SIGHUP, SIGTERM and SIGINT are masked.
+        * if we aren't interactive... but in this case
+        * we never want to restore pgrp on exit, and this fn is not called */
+       /*maybe_set_to_sigexit(SIGHUP );*/
+       /*maybe_set_to_sigexit(SIGTERM);*/
+       /*maybe_set_to_sigexit(SIGINT );*/
+}
+#endif
+
+static int set_mode(const char cstate, const char mode)
+{
+       int state = (cstate == '-' ? 1 : 0);
+       switch (mode) {
+               case 'n': G.fake_mode = state; break;
+               case 'x': /*G.debug_mode = state;*/ break;
+               default:  return EXIT_FAILURE;
+       }
+       return EXIT_SUCCESS;
+}
+
+int hush_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int hush_main(int argc, char **argv)
+{
+       static const struct variable const_shell_ver = {
+               .next = NULL,
+               .varstr = (char*)hush_version_str,
+               .max_len = 1, /* 0 can provoke free(name) */
+               .flg_export = 1,
+               .flg_read_only = 1,
+       };
+       int signal_mask_is_inited = 0;
+       int opt;
+       char **e;
+       struct variable *cur_var;
+
+       INIT_G();
+       if (EXIT_SUCCESS) /* if EXIT_SUCCESS == 0, is already done */
+               G.last_exitcode = EXIT_SUCCESS;
+#if !BB_MMU
+       G.argv0_for_re_execing = argv[0];
+#endif
+       /* Deal with HUSH_VERSION */
+       G.shell_ver = const_shell_ver; /* copying struct here */
+       G.top_var = &G.shell_ver;
+       debug_printf_env("unsetenv '%s'\n", "HUSH_VERSION");
+       unsetenv("HUSH_VERSION"); /* in case it exists in initial env */
+       /* Initialize our shell local variables with the values
+        * currently living in the environment */
+       cur_var = G.top_var;
+       e = environ;
+       if (e) while (*e) {
+               char *value = strchr(*e, '=');
+               if (value) { /* paranoia */
+                       cur_var->next = xzalloc(sizeof(*cur_var));
+                       cur_var = cur_var->next;
+                       cur_var->varstr = *e;
+                       cur_var->max_len = strlen(*e);
+                       cur_var->flg_export = 1;
+               }
+               e++;
+       }
+       debug_printf_env("putenv '%s'\n", hush_version_str);
+       putenv((char *)hush_version_str); /* reinstate HUSH_VERSION */
+#if ENABLE_FEATURE_EDITING
+       G.line_input_state = new_line_input_t(FOR_SHELL);
+#endif
+       G.global_argc = argc;
+       G.global_argv = argv;
+       /* Initialize some more globals to non-zero values */
+       set_cwd();
+       cmdedit_update_prompt();
+
+       if (setjmp(die_jmp)) {
+               /* xfunc has failed! die die die */
+               /* no EXIT traps, this is an escape hatch! */
+               G.exiting = 1;
+               hush_exit(xfunc_error_retval);
+       }
+
+       /* Shell is non-interactive at first. We need to call
+        * block_signals(0) if we are going to execute "sh <script>",
+        * "sh -c <cmds>" or login shell's /etc/profile and friends.
+        * If we later decide that we are interactive, we run block_signals(0)
+        * (or re-run block_signals(1) if we ran block_signals(0) before)
+        * in order to intercept (more) signals.
+        */
+
+       /* Parse options */
+       /* http://www.opengroup.org/onlinepubs/9699919799/utilities/sh.html */
+       while (1) {
+               opt = getopt(argc, argv, "c:xins"
+#if !BB_MMU
+                               "<:$:R:V:"
+# if ENABLE_HUSH_FUNCTIONS
+                               "F:"
+# endif
+#endif
+               );
+               if (opt <= 0)
+                       break;
+               switch (opt) {
+               case 'c':
+                       if (!G.root_pid)
+                               G.root_pid = getpid();
+                       G.global_argv = argv + optind;
+                       if (!argv[optind]) {
+                               /* -c 'script' (no params): prevent empty $0 */
+                               *--G.global_argv = argv[0];
+                               optind--;
+                       } /* else -c 'script' PAR0 PAR1: $0 is PAR0 */
+                       G.global_argc = argc - optind;
+                       block_signals(0); /* 0: called 1st time */
+                       parse_and_run_string(optarg);
+                       goto final_return;
+               case 'i':
+                       /* Well, we cannot just declare interactiveness,
+                        * we have to have some stuff (ctty, etc) */
+                       /* G_interactive_fd++; */
+                       break;
+               case 's':
+                       /* "-s" means "read from stdin", but this is how we always
+                        * operate, so simply do nothing here. */
+                       break;
+#if !BB_MMU
+               case '<': /* "big heredoc" support */
+                       full_write(STDOUT_FILENO, optarg, strlen(optarg));
+                       _exit(0);
+               case '$':
+                       G.root_pid = bb_strtou(optarg, &optarg, 16);
+                       optarg++;
+                       G.last_bg_pid = bb_strtou(optarg, &optarg, 16);
+                       optarg++;
+                       G.last_exitcode = bb_strtou(optarg, &optarg, 16);
+# if ENABLE_HUSH_LOOPS
+                       optarg++;
+                       G.depth_of_loop = bb_strtou(optarg, &optarg, 16);
+# endif
+                       break;
+               case 'R':
+               case 'V':
+                       set_local_var(xstrdup(optarg), 0, opt == 'R');
+                       break;
+# if ENABLE_HUSH_FUNCTIONS
+               case 'F': {
+                       struct function *funcp = new_function(optarg);
+                       /* funcp->name is already set to optarg */
+                       /* funcp->body is set to NULL. It's a special case. */
+                       funcp->body_as_string = argv[optind];
+                       optind++;
+                       break;
+               }
+# endif
+#endif
+               case 'n':
+               case 'x':
+                       if (!set_mode('-', opt))
+                               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
+               }
+       } /* option parsing loop */
+
+       if (!G.root_pid)
+               G.root_pid = getpid();
+
+       /* If we are login shell... */
+       if (argv[0] && argv[0][0] == '-') {
+               FILE *input;
+               debug_printf("sourcing /etc/profile\n");
+               input = fopen_for_read("/etc/profile");
+               if (input != NULL) {
+                       close_on_exec_on(fileno(input));
+                       block_signals(0); /* 0: called 1st time */
+                       signal_mask_is_inited = 1;
+                       parse_and_run_file(input);
+                       fclose(input);
+               }
+               /* bash: after sourcing /etc/profile,
+                * tries to source (in the given order):
+                * ~/.bash_profile, ~/.bash_login, ~/.profile,
+                * stopping of first found. --noprofile turns this off.
+                * bash also sources ~/.bash_logout on exit.
+                * If called as sh, skips .bash_XXX files.
+                */
+       }
+
+       if (argv[optind]) {
+               FILE *input;
+               /*
+                * "bash <script>" (which is never interactive (unless -i?))
+                * sources $BASH_ENV here (without scanning $PATH).
+                * If called as sh, does the same but with $ENV.
+                */
+               debug_printf("running script '%s'\n", argv[optind]);
+               G.global_argv = argv + optind;
+               G.global_argc = argc - optind;
+               input = xfopen_for_read(argv[optind]);
+               close_on_exec_on(fileno(input));
+               if (!signal_mask_is_inited)
+                       block_signals(0); /* 0: called 1st time */
+               parse_and_run_file(input);
+#if ENABLE_FEATURE_CLEAN_UP
+               fclose(input);
+#endif
+               goto final_return;
+       }
+
+       /* Up to here, shell was non-interactive. Now it may become one.
+        * NB: don't forget to (re)run block_signals(0/1) as needed.
+        */
+
+       /* 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 ENABLE_HUSH_JOB
+       if (isatty(STDIN_FILENO) && isatty(STDOUT_FILENO)) {
+               G_saved_tty_pgrp = tcgetpgrp(STDIN_FILENO);
+               debug_printf("saved_tty_pgrp:%d\n", G_saved_tty_pgrp);
+               if (G_saved_tty_pgrp < 0)
+                       G_saved_tty_pgrp = 0;
+
+               /* try to dup stdin to high fd#, >= 255 */
+               G_interactive_fd = fcntl(STDIN_FILENO, F_DUPFD, 255);
+               if (G_interactive_fd < 0) {
+                       /* try to dup to any fd */
+                       G_interactive_fd = dup(STDIN_FILENO);
+                       if (G_interactive_fd < 0) {
+                               /* give up */
+                               G_interactive_fd = 0;
+                               G_saved_tty_pgrp = 0;
+                       }
+               }
+// TODO: track & disallow any attempts of user
+// to (inadvertently) close/redirect G_interactive_fd
+       }
+       debug_printf("interactive_fd:%d\n", G_interactive_fd);
+       if (G_interactive_fd) {
+               close_on_exec_on(G_interactive_fd);
+
+               if (G_saved_tty_pgrp) {
+                       /* If we were run as 'hush &', sleep until we are
+                        * in the foreground (tty pgrp == our pgrp).
+                        * If we get started under a job aware app (like bash),
+                        * make sure we are now in charge so we don't fight over
+                        * who gets the foreground */
+                       while (1) {
+                               pid_t shell_pgrp = getpgrp();
+                               G_saved_tty_pgrp = tcgetpgrp(G_interactive_fd);
+                               if (G_saved_tty_pgrp == shell_pgrp)
+                                       break;
+                               /* send TTIN to ourself (should stop us) */
+                               kill(- shell_pgrp, SIGTTIN);
+                       }
+               }
+
+               /* Block some signals */
+               block_signals(signal_mask_is_inited);
+
+               if (G_saved_tty_pgrp) {
+                       /* Set other signals to restore saved_tty_pgrp */
+                       set_fatal_handlers();
+                       /* Put ourselves in our own process group
+                        * (bash, too, does this only if ctty is available) */
+                       bb_setpgrp(); /* is the same as setpgid(our_pid, our_pid); */
+                       /* Grab control of the terminal */
+                       tcsetpgrp(G_interactive_fd, getpid());
+               }
+               /* -1 is special - makes xfuncs longjmp, not exit
+                * (we reset die_sleep = 0 whereever we [v]fork) */
+               enable_restore_tty_pgrp_on_exit(); /* sets die_sleep = -1 */
+       } else if (!signal_mask_is_inited) {
+               block_signals(0); /* 0: called 1st time */
+       } /* else: block_signals(0) was done before */
+#elif ENABLE_HUSH_INTERACTIVE
+       /* No job control compiled in, only prompt/line editing */
+       if (isatty(STDIN_FILENO) && isatty(STDOUT_FILENO)) {
+               G_interactive_fd = fcntl(STDIN_FILENO, F_DUPFD, 255);
+               if (G_interactive_fd < 0) {
+                       /* try to dup to any fd */
+                       G_interactive_fd = dup(STDIN_FILENO);
+                       if (G_interactive_fd < 0)
+                               /* give up */
+                               G_interactive_fd = 0;
+               }
+       }
+       if (G_interactive_fd) {
+               close_on_exec_on(G_interactive_fd);
+               block_signals(signal_mask_is_inited);
+       } else if (!signal_mask_is_inited) {
+               block_signals(0);
+       }
+#else
+       /* We have interactiveness code disabled */
+       if (!signal_mask_is_inited) {
+               block_signals(0);
+       }
+#endif
+       /* bash:
+        * if interactive but not a login shell, sources ~/.bashrc
+        * (--norc turns this off, --rcfile <file> overrides)
+        */
+
+       if (!ENABLE_FEATURE_SH_EXTRA_QUIET && G_interactive_fd) {
+               printf("\n\n%s hush - the humble shell\n", bb_banner);
+               if (ENABLE_HUSH_HELP)
+                       puts("Enter 'help' for a list of built-in commands.");
+               puts("");
+       }
+
+       parse_and_run_file(stdin);
+
+ final_return:
+#if ENABLE_FEATURE_CLEAN_UP
+       if (G.cwd != bb_msg_unknown)
+               free((char*)G.cwd);
+       cur_var = G.top_var->next;
+       while (cur_var) {
+               struct variable *tmp = cur_var;
+               if (!cur_var->max_len)
+                       free(cur_var->varstr);
+               cur_var = cur_var->next;
+               free(tmp);
+       }
+#endif
+       hush_exit(G.last_exitcode);
+}
+
+
+#if ENABLE_LASH
+int lash_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int lash_main(int argc, char **argv)
+{
+       //bb_error_msg("lash is deprecated, please use hush instead");
+       return hush_main(argc, argv);
+}
+#endif
+
+
+/*
+ * Built-ins
+ */
+static int builtin_true(char **argv UNUSED_PARAM)
+{
+       return 0;
+}
+
+static int builtin_test(char **argv)
+{
+       int argc = 0;
+       while (*argv) {
+               argc++;
+               argv++;
+       }
+       return test_main(argc, argv - argc);
+}
+
+static int builtin_echo(char **argv)
+{
+       int argc = 0;
+       while (*argv) {
+               argc++;
+               argv++;
+       }
+       return echo_main(argc, argv - argc);
+}
+
+static int builtin_eval(char **argv)
+{
+       int rcode = EXIT_SUCCESS;
+
+       if (*++argv) {
+               char *str = expand_strvec_to_string(argv);
+               /* bash:
+                * eval "echo Hi; done" ("done" is syntax error):
+                * "echo Hi" will not execute too.
+                */
+               parse_and_run_string(str);
+               free(str);
+               rcode = G.last_exitcode;
+       }
+       return rcode;
+}
+
+static int builtin_cd(char **argv)
+{
+       const char *newdir = argv[1];
+       if (newdir == NULL) {
+               /* bash does nothing (exitcode 0) if HOME is ""; if it's unset,
+                * bash says "bash: cd: HOME not set" and does nothing
+                * (exitcode 1)
+                */
+               newdir = get_local_var_value("HOME") ? : "/";
+       }
+       if (chdir(newdir)) {
+               /* Mimic bash message exactly */
+               bb_perror_msg("cd: %s", newdir);
+               return EXIT_FAILURE;
+       }
+       set_cwd();
+       return EXIT_SUCCESS;
+}
+
+static int builtin_exec(char **argv)
+{
+       if (*++argv == NULL)
+               return EXIT_SUCCESS; /* bash does this */
+       {
+#if !BB_MMU
+               nommu_save_t dummy;
+#endif
+               /* TODO: if exec fails, bash does NOT exit! We do... */
+               pseudo_exec_argv(&dummy, argv, 0, NULL);
+               /* never returns */
+       }
+}
+
+static int builtin_exit(char **argv)
+{
+       debug_printf_exec("%s()\n", __func__);
+
+       /* interactive bash:
+        * # trap "echo EEE" EXIT
+        * # exit
+        * exit
+        * There are stopped jobs.
+        * (if there are _stopped_ jobs, running ones don't count)
+        * # exit
+        * exit
+        # EEE (then bash exits)
+        *
+        * we can use G.exiting = -1 as indicator "last cmd was exit"
+        */
+
+       /* note: EXIT trap is run by hush_exit */
+       if (*++argv == NULL)
+               hush_exit(G.last_exitcode);
+       /* mimic bash: exit 123abc == exit 255 + error msg */
+       xfunc_error_retval = 255;
+       /* bash: exit -2 == exit 254, no error msg */
+       hush_exit(xatoi(*argv) & 0xff);
+}
+
+static void print_escaped(const char *s)
+{
+       do {
+               if (*s != '\'') {
+                       const char *p;
+
+                       p = strchrnul(s, '\'');
+                       /* print 'xxxx', possibly just '' */
+                       printf("'%.*s'", (int)(p - s), s);
+                       if (*p == '\0')
+                               break;
+                       s = p;
+               }
+               /* s points to '; print "'''...'''" */
+               putchar('"');
+               do putchar('\''); while (*++s == '\'');
+               putchar('"');
+       } while (*s);
+}
+
+static int builtin_export(char **argv)
+{
+       unsigned opt_unexport;
+
+       if (argv[1] == NULL) {
+               char **e = environ;
+               if (e) {
+                       while (*e) {
+#if 0
+                               puts(*e++);
+#else
+                               /* ash emits: export VAR='VAL'
+                                * bash: declare -x VAR="VAL"
+                                * we follow ash example */
+                               const char *s = *e++;
+                               const char *p = strchr(s, '=');
+
+                               if (!p) /* wtf? take next variable */
+                                       continue;
+                               /* export var= */
+                               printf("export %.*s", (int)(p - s) + 1, s);
+                               print_escaped(p + 1);
+                               putchar('\n');
+#endif
+                       }
+                       /*fflush(stdout); - done after each builtin anyway */
+               }
+               return EXIT_SUCCESS;
+       }
+
+#if ENABLE_HUSH_EXPORT_N
+       /* "!": do not abort on errors */
+       /* "+": stop at 1st non-option */
+       opt_unexport = getopt32(argv, "!+n");
+       if (opt_unexport == (unsigned)-1)
+               return EXIT_FAILURE;
+       argv += optind;
+#else
+       opt_unexport = 0;
+       argv++;
+#endif
+
+       do {
+               char *name = *argv;
+
+               /* So far we do not check that name is valid (TODO?) */
+
+               if (strchr(name, '=') == NULL) {
+                       struct variable *var;
+
+                       var = get_local_var(name);
+                       if (opt_unexport) {
+                               /* export -n NAME (without =VALUE) */
+                               if (var) {
+                                       var->flg_export = 0;
+                                       debug_printf_env("%s: unsetenv '%s'\n", __func__, name);
+                                       unsetenv(name);
+                               } /* else: export -n NOT_EXISTING_VAR: no-op */
+                               continue;
+                       }
+                       /* export NAME (without =VALUE) */
+                       if (var) {
+                               var->flg_export = 1;
+                               debug_printf_env("%s: putenv '%s'\n", __func__, var->varstr);
+                               putenv(var->varstr);
+                               continue;
+                       }
+                       /* Exporting non-existing variable.
+                        * bash does not put it in environment,
+                        * but remembers that it is exported,
+                        * and does put it in env when it is set later.
+                        * We just set it to "" and export. */
+                       name = xasprintf("%s=", name);
+               } else {
+                       /* (Un)exporting NAME=VALUE */
+                       name = xstrdup(name);
+               }
+               set_local_var(name,
+                       /*export:*/ (opt_unexport ? -1 : 1),
+                       /*readonly:*/ 0
+               );
+       } while (*++argv);
+
+       return EXIT_SUCCESS;
+}
+
+static int builtin_trap(char **argv)
+{
+       int sig;
+       char *new_cmd;
+
+       if (!G.traps)
+               G.traps = xzalloc(sizeof(G.traps[0]) * NSIG);
+
+       argv++;
+       if (!*argv) {
+               int i;
+               /* No args: print all trapped */
+               for (i = 0; i < NSIG; ++i) {
+                       if (G.traps[i]) {
+                               printf("trap -- ");
+                               print_escaped(G.traps[i]);
+                               printf(" %s\n", get_signame(i));
+                       }
+               }
+               /*fflush(stdout); - done after each builtin anyway */
+               return EXIT_SUCCESS;
+       }
+
+       new_cmd = NULL;
+       /* If first arg is a number: reset all specified signals */
+       sig = bb_strtou(*argv, NULL, 10);
+       if (errno == 0) {
+               int ret;
+ process_sig_list:
+               ret = EXIT_SUCCESS;
+               while (*argv) {
+                       sig = get_signum(*argv++);
+                       if (sig < 0 || sig >= NSIG) {
+                               ret = EXIT_FAILURE;
+                               /* Mimic bash message exactly */
+                               bb_perror_msg("trap: %s: invalid signal specification", argv[-1]);
+                               continue;
+                       }
+
+                       free(G.traps[sig]);
+                       G.traps[sig] = xstrdup(new_cmd);
+
+                       debug_printf("trap: setting SIG%s (%i) to '%s'",
+                               get_signame(sig), sig, G.traps[sig]);
+
+                       /* There is no signal for 0 (EXIT) */
+                       if (sig == 0)
+                               continue;
+
+                       if (new_cmd) {
+                               sigaddset(&G.blocked_set, sig);
+                       } else {
+                               /* There was a trap handler, we are removing it
+                                * (if sig has non-DFL handling,
+                                * we don't need to do anything) */
+                               if (sig < 32 && (G.non_DFL_mask & (1 << sig)))
+                                       continue;
+                               sigdelset(&G.blocked_set, sig);
+                       }
+               }
+               sigprocmask(SIG_SETMASK, &G.blocked_set, NULL);
+               return ret;
+       }
+
+       if (!argv[1]) { /* no second arg */
+               bb_error_msg("trap: invalid arguments");
+               return EXIT_FAILURE;
+       }
+
+       /* First arg is "-": reset all specified to default */
+       /* First arg is "--": skip it, the rest is "handler SIGs..." */
+       /* Everything else: set arg as signal handler
+        * (includes "" case, which ignores signal) */
+       if (argv[0][0] == '-') {
+               if (argv[0][1] == '\0') { /* "-" */
+                       /* new_cmd remains NULL: "reset these sigs" */
+                       goto reset_traps;
+               }
+               if (argv[0][1] == '-' && argv[0][2] == '\0') { /* "--" */
+                       argv++;
+               }
+               /* else: "-something", no special meaning */
+       }
+       new_cmd = *argv;
+ reset_traps:
+       argv++;
+       goto process_sig_list;
+}
+
+#if ENABLE_HUSH_JOB
+/* built-in 'fg' and 'bg' handler */
+static int builtin_fg_bg(char **argv)
+{
+       int i, jobnum;
+       struct pipe *pi;
+
+       if (!G_interactive_fd)
+               return EXIT_FAILURE;
+
+       /* If they gave us no args, assume they want the last backgrounded task */
+       if (!argv[1]) {
+               for (pi = G.job_list; pi; pi = pi->next) {
+                       if (pi->jobid == G.last_jobid) {
+                               goto found;
+                       }
+               }
+               bb_error_msg("%s: no current job", argv[0]);
+               return EXIT_FAILURE;
+       }
+       if (sscanf(argv[1], "%%%d", &jobnum) != 1) {
+               bb_error_msg("%s: bad argument '%s'", argv[0], argv[1]);
+               return EXIT_FAILURE;
+       }
+       for (pi = G.job_list; pi; pi = pi->next) {
+               if (pi->jobid == jobnum) {
+                       goto found;
+               }
+       }
+       bb_error_msg("%s: %d: no such job", argv[0], jobnum);
+       return EXIT_FAILURE;
+ found:
+       /* TODO: bash prints a string representation
+        * of job being foregrounded (like "sleep 1 | cat") */
+       if (argv[0][0] == 'f' && G_saved_tty_pgrp) {
+               /* Put the job into the foreground.  */
+               tcsetpgrp(G_interactive_fd, pi->pgrp);
+       }
+
+       /* Restart the processes in the job */
+       debug_printf_jobs("reviving %d procs, pgrp %d\n", pi->num_cmds, pi->pgrp);
+       for (i = 0; i < pi->num_cmds; i++) {
+               debug_printf_jobs("reviving pid %d\n", pi->cmds[i].pid);
+               pi->cmds[i].is_stopped = 0;
+       }
+       pi->stopped_cmds = 0;
+
+       i = kill(- pi->pgrp, SIGCONT);
+       if (i < 0) {
+               if (errno == ESRCH) {
+                       delete_finished_bg_job(pi);
+                       return EXIT_SUCCESS;
+               }
+               bb_perror_msg("kill (SIGCONT)");
+       }
+
+       if (argv[0][0] == 'f') {
+               remove_bg_job(pi);
+               return checkjobs_and_fg_shell(pi);
+       }
+       return EXIT_SUCCESS;
+}
+#endif
+
+#if ENABLE_HUSH_HELP
+static int builtin_help(char **argv UNUSED_PARAM)
+{
+       const struct built_in_command *x;
+
+       printf("\n"
+               "Built-in commands:\n"
+               "------------------\n");
+       for (x = bltins; x != &bltins[ARRAY_SIZE(bltins)]; x++) {
+               printf("%s\t%s\n", x->cmd, x->descr);
+       }
+       printf("\n\n");
+       return EXIT_SUCCESS;
+}
+#endif
+
+#if ENABLE_HUSH_JOB
+static int builtin_jobs(char **argv UNUSED_PARAM)
+{
+       struct pipe *job;
+       const char *status_string;
+
+       for (job = G.job_list; job; job = job->next) {
+               if (job->alive_cmds == job->stopped_cmds)
+                       status_string = "Stopped";
+               else
+                       status_string = "Running";
+
+               printf(JOB_STATUS_FORMAT, job->jobid, status_string, job->cmdtext);
+       }
+       return EXIT_SUCCESS;
+}
+#endif
+
+#if HUSH_DEBUG
+static int builtin_memleak(char **argv UNUSED_PARAM)
+{
+       void *p;
+       unsigned long l;
+
+       /* Crude attempt to find where "free memory" starts,
+        * sans fragmentation. */
+       p = malloc(240);
+       l = (unsigned long)p;
+       free(p);
+       p = malloc(3400);
+       if (l < (unsigned long)p) l = (unsigned long)p;
+       free(p);
+
+       if (!G.memleak_value)
+               G.memleak_value = l;
+       
+       l -= G.memleak_value;
+       if ((long)l < 0)
+               l = 0;
+       l /= 1024;
+       if (l > 127)
+               l = 127;
+
+       /* Exitcode is "how many kilobytes we leaked since 1st call" */
+       return l;
+}
+#endif
+
+static int builtin_pwd(char **argv UNUSED_PARAM)
+{
+       puts(set_cwd());
+       return EXIT_SUCCESS;
+}
+
+static int builtin_read(char **argv)
+{
+       char *string;
+       const char *name = "REPLY";
+
+       if (argv[1]) {
+               name = argv[1];
+               /* bash (3.2.33(1)) bug: "read 0abcd" will execute,
+                * and _after_ that_ it will complain */
+               if (!is_well_formed_var_name(name, '\0')) {
+                       /* Mimic bash message */
+                       bb_error_msg("read: '%s': not a valid identifier", name);
+                       return 1;
+               }
+       }
+
+//TODO: bash unbackslashes input, splits words and puts them in argv[i]
+
+       string = xmalloc_reads(STDIN_FILENO, xasprintf("%s=", name), NULL);
+       return set_local_var(string, 0, 0);
+}
+
+/* http://www.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#set
+ * built-in 'set' handler
+ * SUSv3 says:
+ * set [-abCefhmnuvx] [-o option] [argument...]
+ * set [+abCefhmnuvx] [+o option] [argument...]
+ * set -- [argument...]
+ * set -o
+ * set +o
+ * Implementations shall support the options in both their hyphen and
+ * plus-sign forms. These options can also be specified as options to sh.
+ * Examples:
+ * Write out all variables and their values: set
+ * Set $1, $2, and $3 and set "$#" to 3: set c a b
+ * Turn on the -x and -v options: set -xv
+ * Unset all positional parameters: set --
+ * Set $1 to the value of x, even if it begins with '-' or '+': set -- "$x"
+ * Set the positional parameters to the expansion of x, even if x expands
+ * with a leading '-' or '+': set -- $x
+ *
+ * So far, we only support "set -- [argument...]" and some of the short names.
+ */
+static int builtin_set(char **argv)
+{
+       int n;
+       char **pp, **g_argv;
+       char *arg = *++argv;
+
+       if (arg == NULL) {
+               struct variable *e;
+               for (e = G.top_var; e; e = e->next)
+                       puts(e->varstr);
+               return EXIT_SUCCESS;
+       }
+
+       do {
+               if (!strcmp(arg, "--")) {
+                       ++argv;
+                       goto set_argv;
+               }
+               if (arg[0] != '+' && arg[0] != '-')
+                       break;
+               for (n = 1; arg[n]; ++n)
+                       if (set_mode(arg[0], arg[n]))
+                               goto error;
+       } while ((arg = *++argv) != NULL);
+       /* Now argv[0] is 1st argument */
+
+       if (arg == NULL)
+               return EXIT_SUCCESS;
+ set_argv:
+
+       /* NB: G.global_argv[0] ($0) is never freed/changed */
+       g_argv = G.global_argv;
+       if (G.global_args_malloced) {
+               pp = g_argv;
+               while (*++pp)
+                       free(*pp);
+               g_argv[1] = NULL;
+       } else {
+               G.global_args_malloced = 1;
+               pp = xzalloc(sizeof(pp[0]) * 2);
+               pp[0] = g_argv[0]; /* retain $0 */
+               g_argv = pp;
+       }
+       /* This realloc's G.global_argv */
+       G.global_argv = pp = add_strings_to_strings(g_argv, argv, /*dup:*/ 1);
+
+       n = 1;
+       while (*++pp)
+               n++;
+       G.global_argc = n;
+
+       return EXIT_SUCCESS;
+
+       /* Nothing known, so abort */
+ error:
+       bb_error_msg("set: %s: invalid option", arg);
+       return EXIT_FAILURE;
+}
+
+static int builtin_shift(char **argv)
+{
+       int n = 1;
+       if (argv[1]) {
+               n = atoi(argv[1]);
+       }
+       if (n >= 0 && n < G.global_argc) {
+               if (G.global_args_malloced) {
+                       int m = 1;
+                       while (m <= n)
+                               free(G.global_argv[m++]);
+               }
+               G.global_argc -= n;
+               memmove(&G.global_argv[1], &G.global_argv[n+1],
+                               G.global_argc * sizeof(G.global_argv[0]));
+               return EXIT_SUCCESS;
+       }
+       return EXIT_FAILURE;
+}
+
+static int builtin_source(char **argv)
+{
+       const char *PATH;
+       FILE *input;
+       save_arg_t sv;
+#if ENABLE_HUSH_FUNCTIONS
+       smallint sv_flg;
+#endif
+
+       if (*++argv == NULL)
+               return EXIT_FAILURE;
+
+       if (strchr(*argv, '/') == NULL
+        && (PATH = get_local_var_value("PATH")) != NULL
+       ) {
+               /* Search through $PATH */
+               while (1) {
+                       const char *end = strchrnul(PATH, ':');
+                       int sz = end - PATH; /* must be int! */
+
+                       if (sz != 0) {
+                               char *tmp = xasprintf("%.*s/%s", sz, PATH, *argv);
+                               input = fopen_for_read(tmp);
+                               free(tmp);
+                       } else {
+                               /* We have xxx::yyyy in $PATH,
+                                * it means "use current dir" */
+                               input = fopen_for_read(*argv);
+                       }
+                       if (input)
+                               goto opened_ok;
+                       if (*end == '\0')
+                               break;
+                       PATH = end + 1;
+               }
+       }
+       input = fopen_or_warn(*argv, "r");
+       if (!input) {
+               /* bb_perror_msg("%s", *argv); - done by fopen_or_warn */
+               return EXIT_FAILURE;
+       }
+ opened_ok:
+       close_on_exec_on(fileno(input));
+
+#if ENABLE_HUSH_FUNCTIONS
+       sv_flg = G.flag_return_in_progress;
+       /* "we are inside sourced file, ok to use return" */
+       G.flag_return_in_progress = -1;
+#endif
+       save_and_replace_G_args(&sv, argv);
+
+       parse_and_run_file(input);
+       fclose(input);
+
+       restore_G_args(&sv, argv);
+#if ENABLE_HUSH_FUNCTIONS
+       G.flag_return_in_progress = sv_flg;
+#endif
+
+       return G.last_exitcode;
+}
+
+static int builtin_umask(char **argv)
+{
+       int rc;
+       mode_t mask;
+
+       mask = umask(0);
+       if (argv[1]) {
+               mode_t old_mask = mask;
+
+               mask ^= 0777;
+               rc = bb_parse_mode(argv[1], &mask);
+               mask ^= 0777;
+               if (rc == 0) {
+                       mask = old_mask;
+                       /* bash messages:
+                        * bash: umask: 'q': invalid symbolic mode operator
+                        * bash: umask: 999: octal number out of range
+                        */
+                       bb_error_msg("%s: '%s' invalid mode", argv[0], argv[1]);
+               }
+       } else {
+               rc = 1;
+               /* Mimic bash */
+               printf("%04o\n", (unsigned) mask);
+               /* fall through and restore mask which we set to 0 */
+       }
+       umask(mask);
+
+       return !rc; /* rc != 0 - success */
+}
+
+/* http://www.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#unset */
+static int builtin_unset(char **argv)
+{
+       int ret;
+       unsigned opts;
+
+       /* "!": do not abort on errors */
+       /* "+": stop at 1st non-option */
+       opts = getopt32(argv, "!+vf");
+       if (opts == (unsigned)-1)
+               return EXIT_FAILURE;
+       if (opts == 3) {
+               bb_error_msg("unset: -v and -f are exclusive");
+               return EXIT_FAILURE;
+       }
+       argv += optind;
+
+       ret = EXIT_SUCCESS;
+       while (*argv) {
+               if (!(opts & 2)) { /* not -f */
+                       if (unset_local_var(*argv)) {
+                               /* unset <nonexistent_var> doesn't fail.
+                                * Error is when one tries to unset RO var.
+                                * Message was printed by unset_local_var. */
+                               ret = EXIT_FAILURE;
+                       }
+               }
+#if ENABLE_HUSH_FUNCTIONS
+               else {
+                       unset_func(*argv);
+               }
+#endif
+               argv++;
+       }
+       return ret;
+}
+
+/* http://www.opengroup.org/onlinepubs/9699919799/utilities/wait.html */
+static int builtin_wait(char **argv)
+{
+       int ret = EXIT_SUCCESS;
+       int status, sig;
+
+       if (*++argv == NULL) {
+               /* Don't care about wait results */
+               /* Note 1: must wait until there are no more children */
+               /* Note 2: must be interruptible */
+               /* Examples:
+                * $ sleep 3 & sleep 6 & wait
+                * [1] 30934 sleep 3
+                * [2] 30935 sleep 6
+                * [1] Done                   sleep 3
+                * [2] Done                   sleep 6
+                * $ sleep 3 & sleep 6 & wait
+                * [1] 30936 sleep 3
+                * [2] 30937 sleep 6
+                * [1] Done                   sleep 3
+                * ^C <-- after ~4 sec from keyboard
+                * $
+                */
+               sigaddset(&G.blocked_set, SIGCHLD);
+               sigprocmask(SIG_SETMASK, &G.blocked_set, NULL);
+               while (1) {
+                       checkjobs(NULL);
+                       if (errno == ECHILD)
+                               break;
+                       /* Wait for SIGCHLD or any other signal of interest */
+                       /* sigtimedwait with infinite timeout: */
+                       sig = sigwaitinfo(&G.blocked_set, NULL);
+                       if (sig > 0) {
+                               sig = check_and_run_traps(sig);
+                               if (sig && sig != SIGCHLD) { /* see note 2 */
+                                       ret = 128 + sig;
+                                       break;
+                               }
+                       }
+               }
+               sigdelset(&G.blocked_set, SIGCHLD);
+               sigprocmask(SIG_SETMASK, &G.blocked_set, NULL);
+               return ret;
+       }
+
+       /* This is probably buggy wrt interruptible-ness */
+       while (*argv) {
+               pid_t pid = bb_strtou(*argv, NULL, 10);
+               if (errno) {
+                       /* mimic bash message */
+                       bb_error_msg("wait: '%s': not a pid or valid job spec", *argv);
+                       return EXIT_FAILURE;
+               }
+               if (waitpid(pid, &status, 0) == pid) {
+                       if (WIFSIGNALED(status))
+                               ret = 128 + WTERMSIG(status);
+                       else if (WIFEXITED(status))
+                               ret = WEXITSTATUS(status);
+                       else /* wtf? */
+                               ret = EXIT_FAILURE;
+               } else {
+                       bb_perror_msg("wait %s", *argv);
+                       ret = 127;
+               }
+               argv++;
+       }
+
+       return ret;
+}
+
+#if ENABLE_HUSH_LOOPS || ENABLE_HUSH_FUNCTIONS
+static unsigned parse_numeric_argv1(char **argv, unsigned def, unsigned def_min)
+{
+       if (argv[1]) {
+               def = bb_strtou(argv[1], NULL, 10);
+               if (errno || def < def_min || argv[2]) {
+                       bb_error_msg("%s: bad arguments", argv[0]);
+                       def = UINT_MAX;
+               }
+       }
+       return def;
+}
+#endif
+
+#if ENABLE_HUSH_LOOPS
+static int builtin_break(char **argv)
+{
+       unsigned depth;
+       if (G.depth_of_loop == 0) {
+               bb_error_msg("%s: only meaningful in a loop", argv[0]);
+               return EXIT_SUCCESS; /* bash compat */
+       }
+       G.flag_break_continue++; /* BC_BREAK = 1 */
+
+       G.depth_break_continue = depth = parse_numeric_argv1(argv, 1, 1);
+       if (depth == UINT_MAX)
+               G.flag_break_continue = BC_BREAK;
+       if (G.depth_of_loop < depth)
+               G.depth_break_continue = G.depth_of_loop;
+
+       return EXIT_SUCCESS;
+}
+
+static int builtin_continue(char **argv)
+{
+       G.flag_break_continue = 1; /* BC_CONTINUE = 2 = 1+1 */
+       return builtin_break(argv);
+}
+#endif
+
+#if ENABLE_HUSH_FUNCTIONS
+static int builtin_return(char **argv)
+{
+       int rc;
+
+       if (G.flag_return_in_progress != -1) {
+               bb_error_msg("%s: not in a function or sourced script", argv[0]);
+               return EXIT_FAILURE; /* bash compat */
+       }
+
+       G.flag_return_in_progress = 1;
+
+       /* bash:
+        * out of range: wraps around at 256, does not error out
+        * non-numeric param:
+        * f() { false; return qwe; }; f; echo $?
+        * bash: return: qwe: numeric argument required  <== we do this
+        * 255  <== we also do this
+        */
+       rc = parse_numeric_argv1(argv, G.last_exitcode, 0);
+       return rc;
+}
+#endif
diff --git a/shell/hush_doc.txt b/shell/hush_doc.txt
new file mode 100644 (file)
index 0000000..c68dc24
--- /dev/null
@@ -0,0 +1,143 @@
+2008-07-14
+
+       Command parsing
+
+Command parsing results in a list of "pipe" structures.
+This list correspond not only to usual "pipe1 || pipe2 && pipe3"
+lists, but it also controls execution of if, while, etc statements.
+Every such statement is a list for hush. List consists of pipes.
+
+struct pipe fields:
+  smallint res_word - "none" for normal commands,
+                      "if" for if condition etc
+  struct child_prog progs[] - array of commands in pipe
+  smallint followup - how this pipe is related to next: is it
+                      "pipe; pipe", "pipe & pipe" "pipe && pipe",
+                      "pipe || pipe"?
+
+Blocks of commands { pipe; pipe; } and (pipe; pipe) are represented
+as one pipe struct with one progs[0] element which is a "group" -
+struct child_prog can contain a list of pipes. Sometimes these
+"groups" are created implicitly, e.g. every control
+statement (if, while, etc) sits inside its own group.
+
+res_word controls statement execution. Examples:
+
+"echo Hello" -
+pipe 0 res_word=NONE followup=SEQ prog[0] 'echo' 'Hello'
+pipe 1 res_word=NONE followup=SEQ
+
+"echo foo || echo bar" -
+pipe 0 res_word=NONE followup=OR  prog[0] 'echo' 'foo'
+pipe 1 res_word=NONE followup=SEQ prog[0] 'echo' 'bar'
+pipe 2 res_word=NONE followup=SEQ
+
+"if true; then echo Hello; true; fi" -
+res_word=NONE followup=SEQ
+ prog 0 group {}:
+  pipe 0 res_word=IF followup=SEQ prog[0] 'true'
+  pipe 1 res_word=THEN followup=SEQ prog[0] 'echo' 'Hello'
+  pipe 2 res_word=THEN followup=SEQ prog[0] 'true'
+  pipe 3 res_word=FI followup=SEQ
+  pipe 4 res_word=NONE followup=(null)
+pipe 1 res_word=NONE followup=SEQ
+
+Above you see that if is a list, and it sits in a {} group
+implicitly created by hush. Also note two THEN res_word's -
+it is explained below.
+
+"if true; then { echo Hello; true; }; fi" -
+pipe 0 res_word=NONE followup=SEQ
+ prog 0 group {}:
+  pipe 0 res_word=IF followup=SEQ prog[0] 'true'
+  pipe 1 res_word=THEN followup=SEQ
+   prog 0 group {}:
+    pipe 0 res_word=NONE followup=SEQ prog[0] 'echo' 'Hello'
+    pipe 1 res_word=NONE followup=SEQ prog[0] 'true'
+    pipe 2 res_word=NONE followup=SEQ
+  pipe 2 res_word=NONE followup=(null)
+pipe 1 res_word=NONE followup=SEQ
+
+"for v in a b; do echo $v; true; done" -
+pipe 0 res_word=NONE followup=SEQ
+ prog 0 group {}:
+  pipe 0 res_word=FOR followup=SEQ prog[0] 'v'
+  pipe 1 res_word=IN followup=SEQ prog[0] 'a' 'b'
+  pipe 2 res_word=DO followup=SEQ prog[0] 'echo' '$v'
+  pipe 3 res_word=DO followup=SEQ prog[0] 'true'
+  pipe 4 res_word=DONE followup=SEQ
+  pipe 5 res_word=NONE followup=(null)
+pipe 1 res_word=NONE followup=SEQ
+
+Note how "THEN" and "DO" does not just mark the first pipe,
+it "sticks" to all pipes in the body. This is used when
+hush executes parsed pipes.
+
+Dummy trailing pipes with no commands are artifacts of imperfect
+parsing algorithm - done_pipe() appends new pipe struct beforehand
+and last one ends up empty and unused.
+
+"for" and "case" statements (ab)use progs[] to keep their data
+instead of argv vector progs[] usually do. "for" keyword is forcing
+pipe termination after first word, which makes hush see
+"for v in..." as "for v; in...". "case" keyword does the same.
+Other judiciuosly placed hacks make hush see
+"case word in a) cmd1;; b) cmd2;; esac" as if it was
+"case word; match a; cmd; match b; cmd2; esac"
+("match" is a fictitious keyword here):
+
+"case word in a) cmd1;; b) cmd2; esac" -
+pipe 0 res_word=NONE followup=1 SEQ
+ prog 0 group {}:
+  pipe 0 res_word=CASE followup=SEQ prog[0] 'word'
+  pipe 1 res_word=MATCH followup=SEQ prog[0] 'a'
+  pipe 2 res_word=CASEI followup=SEQ prog[0] 'cmd1'
+  pipe 3 res_word=MATCH followup=SEQ prog[0] 'b'
+  pipe 4 res_word=CASEI followup=SEQ prog[0] 'cmd2'
+  pipe 5 res_word=CASEI followup=SEQ prog[0] 'cmd3'
+  pipe 6 res_word=ESAC followup=SEQ
+  pipe 7 res_word=NONE followup=(null)
+pipe 1 res_word=NONE followup=SEQ
+
+
+2008-01
+
+       Command execution
+
+/* callsite: process_command_subs */
+generate_stream_from_list(struct pipe *head) - handles `cmds`
+  create UNIX pipe
+  [v]fork
+  child:
+  redirect pipe output to stdout
+  _exit(run_list(head));   /* leaks memory */
+  parent:
+  return UNIX pipe's output fd
+  /* head is freed by the caller */
+
+/* callsite: parse_and_run_stream */
+run_and_free_list(struct pipe *)
+  run_list(struct pipe *)
+  free_pipe_list(struct pipe *)
+
+/* callsites: generate_stream_from_list, run_and_free_list, pseudo_exec, run_pipe */
+run_list(struct pipe *) - handles "cmd; cmd2 && cmd3", while/for/do loops
+  run_pipe - for every pipe in list
+
+/* callsite: run_list */
+run_pipe - runs "cmd1 | cmd2 | cmd3 [&]"
+  run_list - used if only one cmd and it is of the form "{cmds;}"
+  forks for every cmd if more than one cmd or if & is there
+  pseudo_exec - runs each "cmdN" (handles builtins etc)
+
+/* callsite: run_pipe */
+pseudo_exec - runs "cmd" (handles builtins etc)
+  exec - execs external programs
+  run_list - used if cmdN is "(cmds)" or "{cmds;}"
+  /* problem: putenv's malloced strings into environ -
+  ** with vfork they will leak into parent process
+  */
+  /* problem with ENABLE_FEATURE_SH_STANDALONE:
+  ** run_applet_no_and_exit(a, argv) uses exit - this can interfere
+  ** with vfork - switch to _exit there?
+  */
diff --git a/shell/hush_leaktool.sh b/shell/hush_leaktool.sh
new file mode 100755 (executable)
index 0000000..f8e47ae
--- /dev/null
@@ -0,0 +1,13 @@
+#!/bin/sh
+
+# hush's stderr with leak debug enabled
+output=output
+
+freelist=`grep 'free 0x' "$output" | cut -d' ' -f2 | sort | uniq | xargs`
+
+grep -v free "$output" >"$output.leaked"
+for freed in $freelist; do
+    echo Dropping $freed
+    grep -v $freed <"$output.leaked" >"$output.temp"
+    mv "$output.temp" "$output.leaked"
+done
diff --git a/shell/hush_test/hush-arith/arith.right b/shell/hush_test/hush-arith/arith.right
new file mode 100644 (file)
index 0000000..8cde0ee
--- /dev/null
@@ -0,0 +1,138 @@
+Format: 'expected actual'
+163 163
+4 4
+16 16
+8 8
+2 2
+4 4
+2 2
+2 2
+1 1
+0 0
+0 0
+0 0
+1 1
+1 1
+2 2
+-3 -3
+-2 -2
+1 1
+0 0
+2 2
+131072 131072
+29 29
+33 33
+49 49
+1 1
+1 1
+0 0
+0 0
+1 1
+1 1
+1 1
+2 2
+3 3
+1 1
+58 58
+2 2
+60 60
+1 1
+256 256
+16 16
+62 62
+4 4
+29 29
+5 5
+-4 -4
+4 4
+1 1
+32 32
+32 32
+1 1
+1 1
+32 32
+20 20
+30 30
+20 20
+30 30
+hush: error in arithmetic
+6 6
+6,5,3 6,5,3
+263 263
+255 255
+40 40
+hush: error in arithmetic
+hush: divide by 0
+hush: can't exec 'let': No such file or directory
+hush: error in arithmetic
+hush: can't exec 'let': No such file or directory
+abc
+def
+ghi
+hush: error in arithmetic
+16 16
+hush: error in arithmetic
+hush: error in arithmetic
+hush: error in arithmetic
+9 9
+hush: error in arithmetic
+hush: error in arithmetic
+9 9
+9 9
+9 9
+7 7
+7
+4 4
+32767 32767
+32768 32768
+131072 131072
+2147483647 2147483647
+1 1
+4 4
+4 4
+5 5
+5 5
+4 4
+3 3
+3 3
+4 4
+4 4
+hush: error in arithmetic
+hush: error in arithmetic
+hush: error in arithmetic
+hush: error in arithmetic
+hush: error in arithmetic
+4 4
+7 7
+-7 -7
+hush: error in arithmetic
+hush: error in arithmetic
+hush: error in arithmetic
+hush: error in arithmetic
+6 6
+3 3
+7 7
+4 4
+0 0
+3 3
+7 7
+2 2
+-2 -2
+1 1
+hush: error in arithmetic
+hush: error in arithmetic
+hush: error in arithmetic
+hush: error in arithmetic
+hush: error in arithmetic
+5 5
+1 1
+4 4
+0 0
+hush: error in arithmetic
+hush: error in arithmetic
+8 12
+hush: error in arithmetic
+42
+42
+42
+hush: can't exec 'a[b[c]d]=e': No such file or directory
diff --git a/shell/hush_test/hush-arith/arith.tests b/shell/hush_test/hush-arith/arith.tests
new file mode 100755 (executable)
index 0000000..57e66e8
--- /dev/null
@@ -0,0 +1,302 @@
+#ash# set +o posix
+#ash# declare -i iv jv
+
+echo "Format: 'expected actual'"
+
+iv=$(( 3 + 5 * 32 ))
+echo 163 $iv
+#ash# iv=iv+3
+#ash# echo 166 $iv
+iv=2
+jv=iv
+
+: $((jv *= 2)) ##hush## let "jv *= 2"
+echo 4 $jv
+jv=$(( $jv << 2 ))
+echo 16 $jv
+
+: $((jv=$jv / 2)) ##hush## let jv="$jv / 2"
+echo 8 $jv
+#ash# jv="jv >> 2"
+: $((jv=jv >> 2)) ##hush## let jv="jv >> 2"
+echo 2 $jv
+
+iv=$((iv+ $jv))
+echo 4 $iv
+echo 2 $((iv -= jv))
+echo 2 $iv
+echo 1 $(( iv == jv ))
+echo 0 $(( iv != $jv ))
+echo 0 $(( iv < jv ))
+echo 0 $(( $iv > $jv ))
+echo 1 $(( iv <= $jv ))
+echo 1 $(( $iv >= jv ))
+
+echo 2 $jv
+echo -3 $(( ~$jv ))
+echo -2 $(( ~1 ))
+echo 1 $(( ! 0 ))
+
+echo 0 $(( jv % 2 ))
+echo 2 $(( $iv % 4 ))
+
+echo 131072 $(( iv <<= 16 ))
+echo 29 $(( iv %= 33 ))
+
+echo 33 $(( 33 & 55 ))
+echo 49 $(( 33 | 17 ))
+
+echo 1 $(( iv && $jv ))
+echo 1 $(( $iv || jv ))
+
+echo 0 $(( iv && 0 ))
+echo 0 $(( iv & 0 ))
+echo 1 $(( iv && 1 ))
+echo 1 $(( iv & 1 ))
+
+echo 1 $(( $jv || 0 ))
+echo 2 $(( jv | 0 ))
+echo 3 $(( jv | 1 ))
+echo 1 $(( $jv || 1 ))
+
+: $((iv *= jv)) ##hush## let 'iv *= jv'
+echo 58 $iv
+echo 2 $jv
+: $((jv += $iv)) ##hush## let "jv += $iv"
+echo 60 $jv
+
+echo 1 $(( jv /= iv ))
+echo 256 $(( jv <<= 8 ))
+echo 16 $(( jv >>= 4 ))
+
+echo 62 $(( iv |= 4 ))
+echo 4 $(( iv &= 4 ))
+
+echo 29 $(( iv += (jv + 9)))
+echo 5 $(( (iv + 4) % 7 ))
+
+# unary plus, minus
+echo -4 $(( +4 - 8 ))
+echo 4 $(( -4 + 8 ))
+
+# conditional expressions
+echo 1 $(( 4<5 ? 1 : 32))
+echo 32 $(( 4>5 ? 1 : 32))
+echo 32 $(( 4>(2+3) ? 1 : 32))
+echo 1 $(( 4<(2+3) ? 1 : 32))
+echo 1 $(( (2+2)<(2+3) ? 1 : 32))
+echo 32 $(( (2+2)>(2+3) ? 1 : 32))
+
+# check that the unevaluated part of the ternary operator does not do
+# evaluation or assignment
+x=i+=2
+y=j+=2
+#ash# declare -i i=1 j=1
+      i=1
+      j=1
+echo 20 $((1 ? 20 : (x+=2)))
+#ash# echo $i,$x             # ash mishandles this
+echo 30 $((0 ? (y+=2) : 30))
+#ash# echo $j,$y             # ash mishandles this
+
+x=i+=2
+y=j+=2
+#ash# declare -i i=1 j=1
+      i=1
+      j=1
+echo 20 $((1 ? 20 : (x+=2)))
+#ash# echo $i,$x             # ash mishandles this
+echo 30 $((0 ? (y+=2) : 30))
+#ash# echo $i,$y             # ash mishandles this
+
+# check precedence of assignment vs. conditional operator
+# should be an error
+#ash# declare -i x=2
+      x=2
+#ashnote# bash reports error but continues, ash aborts - using subshell to 'emulate' bash:
+(  y=$((1 ? 20 : x+=2))  )
+
+# check precedence of assignment vs. conditional operator
+#ash# declare -i x=2
+      x=2
+# ash says "line NNN: syntax error: 0 ? x+=2 : 20"
+#ash# echo 20 $((0 ? x+=2 : 20))
+
+# associativity of assignment-operator operator
+#ash# declare -i i=1 j=2 k=3
+i=1
+j=2
+k=3
+echo 6 $((i += j += k))
+echo 6,5,3 $i,$j,$k
+
+# octal, hex
+echo 263 $(( 0x100 | 007 ))
+echo 255 $(( 0xff ))
+#ash# echo 255 $(( 16#ff ))
+#ash# echo 127 $(( 16#FF/2 ))
+#ash# echo 36 $(( 8#44 ))
+
+echo 40 $(( 8 ^ 32 ))
+
+#ash# # other bases
+#ash# echo 10 $(( 16#a ))
+#ash# echo 10 $(( 32#a ))
+#ash# echo 10 $(( 56#a ))
+#ash# echo 10 $(( 64#a ))
+#ash#
+#ash# echo 10 $(( 16#A ))
+#ash# echo 10 $(( 32#A ))
+#ash# echo 36 $(( 56#A ))
+#ash# echo 36 $(( 64#A ))
+#ash#
+#ash# echo 62 $(( 64#@ ))
+#ash# echo 63 $(( 64#_ ))
+
+#ash# # weird bases (error)
+#ash# echo $(( 3425#56 ))
+
+#ash# # missing number after base
+#ash# echo 0 $(( 2# ))
+
+# these should generate errors
+(  echo $(( 7 = 43 ))      )
+#ash# echo $(( 2#44 ))
+(  echo $(( 44 / 0 ))      )
+(  let 'jv += $iv'         )
+(  echo $(( jv += \$iv ))  )
+(  let 'rv = 7 + (43 * 6'  )
+
+#ash# # more errors
+#ash# declare -i i
+#ash# i=0#4
+#ash# i=2#110#11
+
+((echo abc; echo def;); echo ghi)
+
+#ash# if (((4+4) + (4 + 7))); then
+#ash#  echo ok
+#ash# fi
+
+#ash# (())     # make sure the null expression works OK
+
+#ash# a=(0 2 4 6)
+#ash# echo 6 $(( a[1] + a[2] ))
+#ash# echo 1 $(( (a[1] + a[2]) == a[3] ))
+#ash# (( (a[1] + a[2]) == a[3] )) ; echo 0 $?
+
+# test pushing and popping the expression stack
+unset A
+A="4 + "
+(  echo A $(( ( 4 + A ) + 4 ))  )
+A="3 + 5"
+echo 16 $(( ( 4 + A ) + 4 ))
+
+# badly-formed conditional expressions
+(  echo $(( 4 ? : $A ))  )
+(  echo $(( 1 ? 20 ))    )
+(  echo $(( 4 ? 20 : ))  )
+
+# precedence and short-circuit evaluation
+B=9
+echo 9 $B
+
+# error
+(  echo $(( 0 && B=42 )); echo 9 $B  )
+
+# error
+(  echo $(( 1 || B=88 )); echo 9 $B  )
+
+# ash mistakenly evaluates B=... below
+#ash# echo 0 $(( 0 && (B=42) ))
+echo 9 $B
+#ash# echo 0 $(( (${$} - $$) && (B=42) ))
+echo 9 $B
+#ash# echo 1 $(( 1 || (B=88) ))
+echo 9 $B
+
+
+# until command with (( )) command
+x=7
+
+echo 7 $x
+#ash# until (( x == 4 ))
+      until test "$x" = 4
+do
+       echo $x
+       x=4
+done
+
+echo 4 $x
+
+# exponentiation
+echo 32767 $(( 2**15 - 1))
+echo 32768 $(( 2**(16-1)))
+echo 131072 $(( 2**16*2 ))
+echo 2147483647 $(( 2**31-1))
+echo 1 $(( 2**0 ))
+
+# {pre,post}-{inc,dec}rement and associated errors
+
+x=4
+
+echo 4 $x
+echo 4 $(( x++ ))
+echo 5 $x
+echo 5 $(( x-- ))
+echo 4 $x
+
+echo 3 $(( --x ))
+echo 3 $x
+
+echo 4 $(( ++x ))
+echo 4 $x
+
+# bash 3.2 apparently thinks that ++7 is 7
+#ash# echo 7 $(( ++7 ))
+(  echo $(( 7-- ))    )
+
+(  echo $(( --x=7 ))  )
+(  echo $(( ++x=7 ))  )
+
+(  echo $(( x++=7 ))  )
+(  echo $(( x--=7 ))  )
+
+echo 4 $x
+
+echo 7 $(( +7 ))
+echo -7 $(( -7 ))
+
+# bash 3.2 apparently thinks that ++7 is 7
+#ash# echo $(( ++7 ))
+#ash# echo $(( --7 ))
+
+${THIS_SH} ./arith1.sub
+${THIS_SH} ./arith2.sub
+
+x=4
+y=7
+
+#ash# (( x=8 , y=12 ))
+      x=8
+      y=12
+echo $x $y
+
+#ash# # should be an error
+#ash# (( x=9 y=41 ))
+
+# These are errors
+unset b
+(  echo $((a b))  )
+#ash# ((a b))
+
+n=42
+printf "%d\n" $n
+printf "%i\n" $n
+#ash# echo $(( 8#$(printf "%o\n" $n) ))
+printf "%u\n" $n
+#ash# echo $(( 16#$(printf "%x\n" $n) ))
+#ash# echo $(( 16#$(printf "%X\n" $n) ))
+
+# causes longjmp botches through bash-2.05b
+a[b[c]d]=e
diff --git a/shell/hush_test/hush-arith/arith1.sub b/shell/hush_test/hush-arith/arith1.sub
new file mode 100755 (executable)
index 0000000..80aa999
--- /dev/null
@@ -0,0 +1,40 @@
+# test of redone post-increment and post-decrement code
+(  echo $(( 4-- ))   )
+(  echo $(( 4++ ))   )
+(  echo $(( 4 -- ))  )
+(  echo $(( 4 ++ ))  )
+
+#ash# (( array[0]++ ))
+#ash# echo ${array}
+
+#ash# (( array[0] ++ ))
+#ash# echo ${array}
+
+#ash# (( a++ ))
+#ash# echo $a
+#ash# (( a ++ ))
+#ash# echo $a
+      a=2
+
+echo 6 $(( a ++ + 4 ))
+echo 3 $a
+
+echo 7 $(( a+++4 ))
+echo 4 $a
+
+echo 0 $(( a---4 ))
+echo 3 $a
+
+echo 7 $(( a -- + 4 ))
+echo 2 $a
+
+echo -2 $(( a -- - 4 ))
+echo 1 $a
+
+#ash# (( ++ + 7 ))
+
+#ash# (( ++ ))
+(  echo $(( +++7 ))  )
+# bash 3.2 apparently thinks that ++ +7 is 7
+#ash# echo $(( ++ + 7 ))
+#ash# (( -- ))
diff --git a/shell/hush_test/hush-arith/arith2.sub b/shell/hush_test/hush-arith/arith2.sub
new file mode 100755 (executable)
index 0000000..f7e3c92
--- /dev/null
@@ -0,0 +1,57 @@
+# bash 3.2 apparently thinks that ++7 is 7 etc
+(  echo $(( --7 ))   )
+(  echo $(( ++7 ))   )
+(  echo $(( -- 7 ))  )
+(  echo $(( ++ 7 ))  )
+
+#ash# ((++array[0] ))
+#ash# echo 1 $array
+#ash# (( ++ array[0] ))
+#ash# echo 2 $array
+
+#ash# (( ++a ))
+#ash# echo 1 $a
+#ash# (( ++ a ))
+#ash# echo 2 $a
+
+#ash# (( --a ))
+#ash# echo 1 $a
+#ash# (( -- a ))
+#ash# echo 0 $a
+      a=0
+
+echo 5 $(( 4 + ++a ))
+echo 1 $a
+
+# ash doesn't handle it right...
+#ash# echo 6 $(( 4+++a ))
+#ash# echo 2 $a
+      a=2
+
+# ash doesn't handle it right...
+#ash# echo 3 $(( 4---a ))
+#ash# echo 1 $a
+      a=1
+
+echo 4 $(( 4 - -- a ))
+echo 0 $a
+
+#ash# (( -- ))
+# bash 3.2 apparently thinks that ---7 is -7
+#ash# echo $(( ---7 ))
+(  echo $(( -- - 7 ))  )
+
+#ash# (( ++ ))
+# bash 3.2: 7
+#ash# echo 7 $(( ++7 ))
+(  echo $(( ++ + 7 ))  )
+
+# bash 3.2: -7
+#ash# echo -7 $(( ++-7 ))
+# bash 3.2: -7
+#ash# echo -7 $(( ++ - 7 ))
+
+# bash 3.2: 7
+#ash# echo 7 $(( +--7 ))
+# bash 3.2: 7
+#ash# echo 7 $(( -- + 7 ))
diff --git a/shell/hush_test/hush-bugs/and_or_and_backgrounding.right b/shell/hush_test/hush-bugs/and_or_and_backgrounding.right
new file mode 100644 (file)
index 0000000..90ce63e
--- /dev/null
@@ -0,0 +1,4 @@
+First
+Second
+Third
+Done
diff --git a/shell/hush_test/hush-bugs/and_or_and_backgrounding.tests b/shell/hush_test/hush-bugs/and_or_and_backgrounding.tests
new file mode 100755 (executable)
index 0000000..05acfb8
--- /dev/null
@@ -0,0 +1,31 @@
+# UNFIXED BUG: hush thinks that ; && || & have the same precedence.
+# According to this doc, && || have higher precedence than ; &.
+# See example below.
+# Precedence of ; is not a problem in practice. Precedence of & is.
+#
+#http://www.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html
+#
+#2.9.3 Lists
+#
+#An AND-OR list is a sequence of one or more pipelines separated by
+#the operators "&&" and "||" .
+#
+#A list is a sequence of one or more AND-OR lists separated by the operators
+#';' and '&' and optionally terminated by ';', '&', or <newline>.
+#
+#The operators "&&" and "||" shall have equal precedence and shall be
+#evaluated with left associativity. For example, both of the following
+#commands write solely bar to standard output:
+#
+#    false && echo foo || echo bar
+#    true || echo foo && echo bar
+#
+#A ';' or <newline> terminator shall cause the preceding AND-OR list
+#to be executed sequentially; an '&' shall cause asynchronous execution
+#of the preceding AND-OR list.
+
+echo First && sleep 0.2 && echo Third &
+sleep 0.1
+echo Second
+wait
+echo Done
diff --git a/shell/hush_test/hush-glob/glob1.right b/shell/hush_test/hush-glob/glob1.right
new file mode 100644 (file)
index 0000000..f29ab4e
--- /dev/null
@@ -0,0 +1,2 @@
+glob1.tests
+glob1.tests
diff --git a/shell/hush_test/hush-glob/glob1.tests b/shell/hush_test/hush-glob/glob1.tests
new file mode 100755 (executable)
index 0000000..f980ce0
--- /dev/null
@@ -0,0 +1,2 @@
+echo *glob1?t[e]sts*
+echo "glob1"?'t'[e]s*
diff --git a/shell/hush_test/hush-glob/glob_and_assign.right b/shell/hush_test/hush-glob/glob_and_assign.right
new file mode 100644 (file)
index 0000000..d46e443
--- /dev/null
@@ -0,0 +1,6 @@
+ZVAR=z.tmp ZVAR=*.tmp ZVAR=[z].tmp
+ZVAR=z.tmp ZVAR=*.tmp ZVAR=[z].tmp
+*.tmp
+ZVAR=z.tmp z.tmp
+ZVAR=z.tmp ZVAR=*.tmp ZVAR=[z].tmp
+ZVAR=z.tmp ZVAR=*.tmp ZVAR=[z].tmp
diff --git a/shell/hush_test/hush-glob/glob_and_assign.tests b/shell/hush_test/hush-glob/glob_and_assign.tests
new file mode 100755 (executable)
index 0000000..0b158f2
--- /dev/null
@@ -0,0 +1,10 @@
+>ZVAR=z.tmp
+>z.tmp
+ZVAR=*.tmp echo ZVAR=*.tmp "ZVAR=*.tmp" "ZVAR=[z].tmp"
+ZVAR=*.tmp /bin/echo ZVAR=*.tmp "ZVAR=*.tmp" "ZVAR=[z].tmp"
+ZVAR=*.tmp
+echo "$ZVAR"
+echo $ZVAR
+echo ZVAR=*.tmp "ZVAR=*.tmp" "ZVAR=[z].tmp"
+/bin/echo ZVAR=*.tmp "ZVAR=*.tmp" "ZVAR=[z].tmp"
+rm ZVAR=z.tmp z.tmp
diff --git a/shell/hush_test/hush-glob/glob_redir.right b/shell/hush_test/hush-glob/glob_redir.right
new file mode 100644 (file)
index 0000000..fbd0309
--- /dev/null
@@ -0,0 +1,2 @@
+z.tmp:
+?.tmp: TEST
diff --git a/shell/hush_test/hush-glob/glob_redir.tests b/shell/hush_test/hush-glob/glob_redir.tests
new file mode 100755 (executable)
index 0000000..621d120
--- /dev/null
@@ -0,0 +1,9 @@
+# Redirections are not globbed.
+# bash:
+# if run as "sh", they are not globbed, but
+# if run as "bash", they are!
+>z.tmp
+echo TEST >?.tmp
+echo 'z.tmp:' `cat 'z.tmp'`
+echo '?.tmp:' `cat '?.tmp'`
+rm 'z.tmp' '?.tmp'
diff --git a/shell/hush_test/hush-leak/leak_argv1.right b/shell/hush_test/hush-leak/leak_argv1.right
new file mode 100644 (file)
index 0000000..7bccc1e
--- /dev/null
@@ -0,0 +1,2 @@
+Measuring memory leak...
+vsz does not grow
diff --git a/shell/hush_test/hush-leak/leak_argv1.tests b/shell/hush_test/hush-leak/leak_argv1.tests
new file mode 100755 (executable)
index 0000000..34991ce
--- /dev/null
@@ -0,0 +1,117 @@
+pid=$$
+
+# Warm up
+beg=`ps -o pid,vsz | grep "^ *$pid "`
+i=1
+while test $i != X; do
+    set -- 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
+    shift
+    shift 2
+    shift 5
+    shift 11
+    set -- 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
+    shift 3
+    shift 7
+    i=1$i
+    if test $i = 1111111111111111111111111111111111111111111111; then i=2; fi
+    if test $i = 1111111111111111111111111111111111111111111112; then i=3; fi
+    if test $i = 1111111111111111111111111111111111111111111113; then i=4; fi
+    if test $i = 1111111111111111111111111111111111111111111114; then i=5; fi
+    if test $i = 1111111111111111111111111111111111111111111115; then i=6; fi
+    if test $i = 1111111111111111111111111111111111111111111116; then i=7; fi
+    if test $i = 1111111111111111111111111111111111111111111117; then i=8; fi
+    if test $i = 1111111111111111111111111111111111111111111118; then i=9; fi
+    if test $i = 1111111111111111111111111111111111111111111119; then i=a; fi
+    if test $i = 111111111111111111111111111111111111111111111a; then i=b; fi
+    if test $i = 111111111111111111111111111111111111111111111b; then i=c; fi
+    if test $i = 111111111111111111111111111111111111111111111c; then i=d; fi
+    if test $i = 111111111111111111111111111111111111111111111d; then i=e; fi
+    if test $i = 111111111111111111111111111111111111111111111e; then i=f; fi
+    if test $i = 111111111111111111111111111111111111111111111f; then i=g; fi
+    if test $i = 111111111111111111111111111111111111111111111g; then i=h; fi
+    if test $i = 111111111111111111111111111111111111111111111h; then i=i; fi
+    if test $i = 111111111111111111111111111111111111111111111i; then i=j; fi
+    if test $i = 111111111111111111111111111111111111111111111j; then i=X; fi
+done
+end=`ps -o pid,vsz | grep "^ *$pid "`
+
+# Warm up again (I do need it on my machine)
+beg=`ps -o pid,vsz | grep "^ *$pid "`
+i=1
+while test $i != X; do
+    set -- 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
+    shift
+    shift 2
+    shift 5
+    shift 11
+    set -- 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
+    shift 3
+    shift 7
+    i=1$i
+    if test $i = 1111111111111111111111111111111111111111111111; then i=2; fi
+    if test $i = 1111111111111111111111111111111111111111111112; then i=3; fi
+    if test $i = 1111111111111111111111111111111111111111111113; then i=4; fi
+    if test $i = 1111111111111111111111111111111111111111111114; then i=5; fi
+    if test $i = 1111111111111111111111111111111111111111111115; then i=6; fi
+    if test $i = 1111111111111111111111111111111111111111111116; then i=7; fi
+    if test $i = 1111111111111111111111111111111111111111111117; then i=8; fi
+    if test $i = 1111111111111111111111111111111111111111111118; then i=9; fi
+    if test $i = 1111111111111111111111111111111111111111111119; then i=a; fi
+    if test $i = 111111111111111111111111111111111111111111111a; then i=b; fi
+    if test $i = 111111111111111111111111111111111111111111111b; then i=c; fi
+    if test $i = 111111111111111111111111111111111111111111111c; then i=d; fi
+    if test $i = 111111111111111111111111111111111111111111111d; then i=e; fi
+    if test $i = 111111111111111111111111111111111111111111111e; then i=f; fi
+    if test $i = 111111111111111111111111111111111111111111111f; then i=g; fi
+    if test $i = 111111111111111111111111111111111111111111111g; then i=h; fi
+    if test $i = 111111111111111111111111111111111111111111111h; then i=i; fi
+    if test $i = 111111111111111111111111111111111111111111111i; then i=j; fi
+    if test $i = 111111111111111111111111111111111111111111111j; then i=X; fi
+done
+end=`ps -o pid,vsz | grep "^ *$pid "`
+if test "$beg" != "$end"; then
+    true echo "vsz grows: $beg -> $end"
+else
+    true echo "vsz does not grow"
+fi
+
+echo "Measuring memory leak..."
+beg=`ps -o pid,vsz | grep "^ *$pid "`
+i=1
+while test $i != X; do
+    set -- 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
+    shift
+    shift 2
+    shift 5
+    shift 11
+    set -- 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
+    shift 3
+    shift 7
+    i=1$i
+    if test $i = 1111111111111111111111111111111111111111111111; then i=2; fi
+    if test $i = 1111111111111111111111111111111111111111111112; then i=3; fi
+    if test $i = 1111111111111111111111111111111111111111111113; then i=4; fi
+    if test $i = 1111111111111111111111111111111111111111111114; then i=5; fi
+    if test $i = 1111111111111111111111111111111111111111111115; then i=6; fi
+    if test $i = 1111111111111111111111111111111111111111111116; then i=7; fi
+    if test $i = 1111111111111111111111111111111111111111111117; then i=8; fi
+    if test $i = 1111111111111111111111111111111111111111111118; then i=9; fi
+    if test $i = 1111111111111111111111111111111111111111111119; then i=a; fi
+    if test $i = 111111111111111111111111111111111111111111111a; then i=b; fi
+    if test $i = 111111111111111111111111111111111111111111111b; then i=c; fi
+    if test $i = 111111111111111111111111111111111111111111111c; then i=d; fi
+    if test $i = 111111111111111111111111111111111111111111111d; then i=e; fi
+    if test $i = 111111111111111111111111111111111111111111111e; then i=f; fi
+    if test $i = 111111111111111111111111111111111111111111111f; then i=g; fi
+    if test $i = 111111111111111111111111111111111111111111111g; then i=h; fi
+    if test $i = 111111111111111111111111111111111111111111111h; then i=i; fi
+    if test $i = 111111111111111111111111111111111111111111111i; then i=j; fi
+    if test $i = 111111111111111111111111111111111111111111111j; then i=X; fi
+done
+end=`ps -o pid,vsz | grep "^ *$pid "`
+
+if test "$beg" != "$end"; then
+    echo "vsz grows: $beg -> $end"
+else
+    echo "vsz does not grow"
+fi
diff --git a/shell/hush_test/hush-misc/and-or.right b/shell/hush_test/hush-misc/and-or.right
new file mode 100644 (file)
index 0000000..f9fa5fb
--- /dev/null
@@ -0,0 +1,18 @@
+a1
+a4
+b1
+b3
+b4
+b6
+c4
+c5
+c7
+c8
+ff1
+ff3
+ft2
+ft3
+tf2
+tf3
+tt2
+tt4
diff --git a/shell/hush_test/hush-misc/and-or.tests b/shell/hush_test/hush-misc/and-or.tests
new file mode 100755 (executable)
index 0000000..485458a
--- /dev/null
@@ -0,0 +1,34 @@
+false || echo a1
+false && echo a2
+true || echo a3
+true && echo a4
+
+false || echo b1 || echo b2
+false || echo b3 && echo b4
+false && echo b5 || echo b6
+false && echo b7 && echo b8
+
+true || echo c1 || echo c2
+true || echo c3 && echo c4
+true && echo c5 || echo c6
+true && echo c7 && echo c8
+
+false || false || echo ff1
+false || false && echo ff2
+false && false || echo ff3
+false && false && echo ff4
+
+false || true || echo ft1
+false || true && echo ft2
+false && true || echo ft3
+false && true && echo ft4
+
+true || false || echo tf1
+true || false && echo tf2
+true && false || echo tf3
+true && false && echo tf4
+
+true || true || echo tt1
+true || true && echo tt2
+true && true || echo tt3
+true && true && echo tt4
diff --git a/shell/hush_test/hush-misc/assignment1.right b/shell/hush_test/hush-misc/assignment1.right
new file mode 100644 (file)
index 0000000..d0a13d3
--- /dev/null
@@ -0,0 +1,9 @@
+if1:0
+while1:0
+until1:0
+if2:0
+while2:0
+until2:0
+if3:0
+while3:0
+until3:0
diff --git a/shell/hush_test/hush-misc/assignment1.tests b/shell/hush_test/hush-misc/assignment1.tests
new file mode 100755 (executable)
index 0000000..033b352
--- /dev/null
@@ -0,0 +1,42 @@
+# Assignments after some keywords should still work
+
+if a=1 true; then a=1 true; elif a=1 true; then a=1 true; else a=1 true; fi
+echo if1:$?
+while a=1 true; do a=1 true; break; done
+echo while1:$?
+until a=1 false; do a=1 true; break; done
+echo until1:$?
+
+if a=1 true
+ then a=1 true
+ elif a=1 true
+ then a=1 true
+ else a=1 true
+ fi
+echo if2:$?
+while a=1 true
+ do a=1 true
+ break
+ done
+echo while2:$?
+until a=1 false
+ do a=1 true
+ break
+ done
+echo until2:$?
+
+if
+ a=1 true; then
+ a=1 true; elif
+ a=1 true; then
+ a=1 true; else
+ a=1 true; fi
+echo if3:$?
+while
+ a=1 true; do
+ a=1 true; break; done
+echo while3:$?
+until
+ a=1 false; do
+ a=1 true; break; done
+echo until3:$?
diff --git a/shell/hush_test/hush-misc/assignment2.rigth b/shell/hush_test/hush-misc/assignment2.rigth
new file mode 100644 (file)
index 0000000..591552c
--- /dev/null
@@ -0,0 +1,2 @@
+hush: can't exec 'a=b': No such file or directory
+1
diff --git a/shell/hush_test/hush-misc/assignment2.tests b/shell/hush_test/hush-misc/assignment2.tests
new file mode 100755 (executable)
index 0000000..540e01e
--- /dev/null
@@ -0,0 +1,4 @@
+# This must not be interpreted as an assignment
+a''=b true
+echo $?
+# (buglet: $? should be 127. it is currently 1)
diff --git a/shell/hush_test/hush-misc/break1.right b/shell/hush_test/hush-misc/break1.right
new file mode 100644 (file)
index 0000000..04a4b17
--- /dev/null
@@ -0,0 +1,2 @@
+A
+OK:0
diff --git a/shell/hush_test/hush-misc/break1.tests b/shell/hush_test/hush-misc/break1.tests
new file mode 100755 (executable)
index 0000000..912f149
--- /dev/null
@@ -0,0 +1,3 @@
+while true; do echo A; break; echo B; done
+echo OK:$?
+
diff --git a/shell/hush_test/hush-misc/break2.right b/shell/hush_test/hush-misc/break2.right
new file mode 100644 (file)
index 0000000..8a15cb9
--- /dev/null
@@ -0,0 +1,3 @@
+A
+AA
+OK:0
diff --git a/shell/hush_test/hush-misc/break2.tests b/shell/hush_test/hush-misc/break2.tests
new file mode 100755 (executable)
index 0000000..7da9faf
--- /dev/null
@@ -0,0 +1,6 @@
+while true; do
+    echo A
+    while true; do echo AA; break 2; echo BB; done
+    echo B
+done
+echo OK:$?
diff --git a/shell/hush_test/hush-misc/break3.right b/shell/hush_test/hush-misc/break3.right
new file mode 100644 (file)
index 0000000..04a4b17
--- /dev/null
@@ -0,0 +1,2 @@
+A
+OK:0
diff --git a/shell/hush_test/hush-misc/break3.tests b/shell/hush_test/hush-misc/break3.tests
new file mode 100755 (executable)
index 0000000..d138dca
--- /dev/null
@@ -0,0 +1,2 @@
+v=break; while true; do echo A; $v; echo B; break; echo C; done
+echo OK:$?
diff --git a/shell/hush_test/hush-misc/break4.right b/shell/hush_test/hush-misc/break4.right
new file mode 100644 (file)
index 0000000..6f41c14
--- /dev/null
@@ -0,0 +1,6 @@
+A
+AA
+TRUE
+A
+AA
+OK:0
diff --git a/shell/hush_test/hush-misc/break4.tests b/shell/hush_test/hush-misc/break4.tests
new file mode 100755 (executable)
index 0000000..67da288
--- /dev/null
@@ -0,0 +1,12 @@
+cond=true
+while $cond; do
+    echo A
+    if test "$cond" = true; then
+       cond='echo TRUE'
+    else
+       cond=false
+    fi
+    while true; do echo AA; continue 2; echo BB; done
+    echo B
+done
+echo OK:$?
diff --git a/shell/hush_test/hush-misc/break5.right b/shell/hush_test/hush-misc/break5.right
new file mode 100644 (file)
index 0000000..0b9df2a
--- /dev/null
@@ -0,0 +1,13 @@
+A
+B
+0
+A:a
+B
+D
+A:b
+B
+D
+A:c
+B
+D
+0
diff --git a/shell/hush_test/hush-misc/break5.tests b/shell/hush_test/hush-misc/break5.tests
new file mode 100755 (executable)
index 0000000..273e040
--- /dev/null
@@ -0,0 +1,4 @@
+while true; do echo A; { echo B; break; echo C; }; echo D; done
+echo $?
+for v in a b c; do echo A:$v; (echo B; break; echo C); echo D; done
+echo $?
diff --git a/shell/hush_test/hush-misc/builtin1.right b/shell/hush_test/hush-misc/builtin1.right
new file mode 100644 (file)
index 0000000..2e55ecb
--- /dev/null
@@ -0,0 +1,2 @@
+VARIABLE=export
+OK:0
diff --git a/shell/hush_test/hush-misc/builtin1.tests b/shell/hush_test/hush-misc/builtin1.tests
new file mode 100755 (executable)
index 0000000..1a2941f
--- /dev/null
@@ -0,0 +1,6 @@
+# builtins, unlike keywords like "while", can be constructed
+# with substitutions
+VARIABLE=export
+$VARIABLE VARIABLE
+env | grep ^VARIABLE
+echo OK:$?
diff --git a/shell/hush_test/hush-misc/case1.right b/shell/hush_test/hush-misc/case1.right
new file mode 100644 (file)
index 0000000..e9e371a
--- /dev/null
@@ -0,0 +1,14 @@
+OK_1
+OK_1
+OK_21
+OK_22
+OK_23
+OK_31
+OK_32
+OK_41
+OK_42
+OK_43
+OK_44
+OK_51
+OK_52
+OK_53
diff --git a/shell/hush_test/hush-misc/case1.tests b/shell/hush_test/hush-misc/case1.tests
new file mode 100755 (executable)
index 0000000..0174893
--- /dev/null
@@ -0,0 +1,25 @@
+case w in a) echo SKIP;; w) echo OK_1;; w) echo WRONG;; esac
+
+case w in
+ a) echo SKIP;;
+ w)echo OK_1 ;;
+ w)
+ echo WRONG
+ ;;
+esac
+
+t=w
+case $t in a) echo SKIP;; w) echo OK_21;; w) echo WRONG;; esac;
+case "$t" in a) echo SKIP;; w) echo OK_22;; w) echo WRONG;; esac;
+case w in a) echo SKIP;; $t) echo OK_23;; "$t") echo WRONG;; esac;
+
+case '' in a) echo SKIP;; w) echo WRONG;; *) echo OK_31;; esac;
+case '' in a) echo SKIP;; '') echo OK_32;; *) echo WRONG;; esac;
+
+case `echo w` in a) echo SKIP;; w) echo OK_41;; w) echo WRONG;; esac;
+case "`echo w`" in a) echo SKIP;; w) echo OK_42;; w) echo WRONG;; esac;
+case `echo w w` in a) echo SKIP;; w) echo WRONG;; 'w w') echo OK_43;; esac;
+case `echo w w` in a) echo SKIP;; w) echo WRONG;; w*) echo OK_44;; esac;
+
+case w in `echo w`) echo OK_51;; `echo WRONG >&2`w) echo WRONG;; esac;
+case w in `echo OK_52 >&2`) echo SKIP;; `echo`w) echo OK_53;; esac;
diff --git a/shell/hush_test/hush-misc/colon.right b/shell/hush_test/hush-misc/colon.right
new file mode 100644 (file)
index 0000000..2a87d02
--- /dev/null
@@ -0,0 +1,2 @@
+0
+OK: 0
diff --git a/shell/hush_test/hush-misc/colon.tests b/shell/hush_test/hush-misc/colon.tests
new file mode 100755 (executable)
index 0000000..cb8ab53
--- /dev/null
@@ -0,0 +1,5 @@
+false
+:
+echo $?
+(while :; do exit; done)
+echo OK: $?
diff --git a/shell/hush_test/hush-misc/continue1.right b/shell/hush_test/hush-misc/continue1.right
new file mode 100644 (file)
index 0000000..c4a5565
--- /dev/null
@@ -0,0 +1,8 @@
+A:a
+A:b
+A:c
+OK1
+A:a
+A:b
+A:c
+OK2
diff --git a/shell/hush_test/hush-misc/continue1.tests b/shell/hush_test/hush-misc/continue1.tests
new file mode 100755 (executable)
index 0000000..72d3566
--- /dev/null
@@ -0,0 +1,4 @@
+for v in a b c; do echo A:$v; continue 666; done
+echo OK1
+for v in a b c; do echo A:$v; continue 666; done
+echo OK2
diff --git a/shell/hush_test/hush-misc/continue2.right b/shell/hush_test/hush-misc/continue2.right
new file mode 100644 (file)
index 0000000..49d3ebd
--- /dev/null
@@ -0,0 +1 @@
+Ok:1
diff --git a/shell/hush_test/hush-misc/continue2.tests b/shell/hush_test/hush-misc/continue2.tests
new file mode 100755 (executable)
index 0000000..c2df071
--- /dev/null
@@ -0,0 +1,3 @@
+e=''
+(while test $e && exit 1; true; do e=1; continue; done)
+echo Ok:$?
diff --git a/shell/hush_test/hush-misc/continue3.right b/shell/hush_test/hush-misc/continue3.right
new file mode 100644 (file)
index 0000000..aa47d0d
--- /dev/null
@@ -0,0 +1,2 @@
+0
+0
diff --git a/shell/hush_test/hush-misc/continue3.tests b/shell/hush_test/hush-misc/continue3.tests
new file mode 100755 (executable)
index 0000000..0aff867
--- /dev/null
@@ -0,0 +1,3 @@
+# Test that "continue" does affect exitcode (sets to 0)
+e=''
+while echo $?; test $e && exit; true; do e=1; false; continue; done
diff --git a/shell/hush_test/hush-misc/empty_for.right b/shell/hush_test/hush-misc/empty_for.right
new file mode 100644 (file)
index 0000000..290d39b
--- /dev/null
@@ -0,0 +1 @@
+OK: 0
diff --git a/shell/hush_test/hush-misc/empty_for.tests b/shell/hush_test/hush-misc/empty_for.tests
new file mode 100755 (executable)
index 0000000..0cb52e8
--- /dev/null
@@ -0,0 +1,3 @@
+false
+for a in; do echo "HELLO"; done
+echo OK: $?
diff --git a/shell/hush_test/hush-misc/empty_for2.right b/shell/hush_test/hush-misc/empty_for2.right
new file mode 100644 (file)
index 0000000..1acee9e
--- /dev/null
@@ -0,0 +1,4 @@
+PARAM:abc
+PARAM:d e
+PARAM:123
+OK: 0
diff --git a/shell/hush_test/hush-misc/empty_for2.tests b/shell/hush_test/hush-misc/empty_for2.tests
new file mode 100755 (executable)
index 0000000..2b12ec2
--- /dev/null
@@ -0,0 +1,6 @@
+if test $# = 0; then
+    exec "$THIS_SH" $0 abc "d e" 123
+fi
+false
+for v; do echo "PARAM:$v"; done
+echo OK: $?
diff --git a/shell/hush_test/hush-misc/exec.right b/shell/hush_test/hush-misc/exec.right
new file mode 100644 (file)
index 0000000..a0de608
--- /dev/null
@@ -0,0 +1,6 @@
+pass fd out open
+pass fd out dup
+pass fd out close
+pass fd in open
+pass fd in dup
+pass fd in close
diff --git a/shell/hush_test/hush-misc/exec.tests b/shell/hush_test/hush-misc/exec.tests
new file mode 100755 (executable)
index 0000000..6de50fa
--- /dev/null
@@ -0,0 +1,30 @@
+# make sure we have a way of checking these things
+cd /proc/$$/fd || cd /dev/fd || exit 1
+
+[ -e 44 ] && exit 1
+exec 44>/dev/null
+[ -e 44 ] || exit 1
+echo pass fd out open
+
+[ -e 55 ] && exit 1
+exec 55>&44
+[ -e 55 ] || exit 1
+echo pass fd out dup
+
+exec 44>&-
+[ -e 44 ] && exit 1
+echo pass fd out close
+
+[ -e 66 ] && exit 1
+exec 66</dev/null
+[ -e 66 ] || exit 1
+echo pass fd in open
+
+[ -e 77 ] && exit 1
+exec 77<&66
+[ -e 77 ] || exit 1
+echo pass fd in dup
+
+exec 66<&-
+[ -e 66 ] && exit 1
+echo pass fd in close
diff --git a/shell/hush_test/hush-misc/exit1.right b/shell/hush_test/hush-misc/exit1.right
new file mode 100644 (file)
index 0000000..dd2cfc2
--- /dev/null
@@ -0,0 +1 @@
+Once
diff --git a/shell/hush_test/hush-misc/exit1.tests b/shell/hush_test/hush-misc/exit1.tests
new file mode 100755 (executable)
index 0000000..41e0d09
--- /dev/null
@@ -0,0 +1,4 @@
+trap "echo Not shown" EXIT
+(exit)  # must be silent
+trap "echo Once; exit" EXIT
+{ exit; }
diff --git a/shell/hush_test/hush-misc/export.right b/shell/hush_test/hush-misc/export.right
new file mode 100644 (file)
index 0000000..4df2e38
--- /dev/null
@@ -0,0 +1,6 @@
+export aaa1="'''"
+export aaa2=
+export aaa3="'''"'abc'
+export aaa4='def'"'''"
+export aaa5="'''"'abc'"'''"'def'"'''"
+Done
diff --git a/shell/hush_test/hush-misc/export.tests b/shell/hush_test/hush-misc/export.tests
new file mode 100755 (executable)
index 0000000..87a27ec
--- /dev/null
@@ -0,0 +1,7 @@
+export aaa1="'''"
+export aaa2=""
+export aaa3="'''abc"
+export aaa4="def'''"
+export aaa5="'''abc'''def'''"
+export | grep aaa.=
+echo Done
diff --git a/shell/hush_test/hush-misc/for_with_bslashes.right b/shell/hush_test/hush-misc/for_with_bslashes.right
new file mode 100644 (file)
index 0000000..02d9669
--- /dev/null
@@ -0,0 +1,8 @@
+a
+b\c
+b\\c
+b"c
+b'c
+b$c
+b`true`c
+Zero:0
diff --git a/shell/hush_test/hush-misc/for_with_bslashes.tests b/shell/hush_test/hush-misc/for_with_bslashes.tests
new file mode 100755 (executable)
index 0000000..363f3d8
--- /dev/null
@@ -0,0 +1,10 @@
+# UNFIXED BUG.
+# commented-out words contain ^C character.
+# It's a SPECIAL_VAR_SYMBOL, for now hush does not escape it.
+# When it is fixed, update this test.
+
+for a in 'a' 'b\c' 'b\\c' 'b"c' "b'c" 'b$c' 'b`true`c' ### 'b\ 3#\ 3c'
+do
+    echo $a
+done
+echo Zero:$?
diff --git a/shell/hush_test/hush-misc/for_with_keywords.right b/shell/hush_test/hush-misc/for_with_keywords.right
new file mode 100644 (file)
index 0000000..eb04e9a
--- /dev/null
@@ -0,0 +1,4 @@
+do
+done
+then
+OK: 0
diff --git a/shell/hush_test/hush-misc/for_with_keywords.tests b/shell/hush_test/hush-misc/for_with_keywords.tests
new file mode 100755 (executable)
index 0000000..a8b8e42
--- /dev/null
@@ -0,0 +1,2 @@
+for if in do done then; do echo $if; done
+echo OK: $?
diff --git a/shell/hush_test/hush-misc/func1.right b/shell/hush_test/hush-misc/func1.right
new file mode 100644 (file)
index 0000000..e21665a
--- /dev/null
@@ -0,0 +1,6 @@
+Hello
+Zero: 0
+One: 1 Param1: World
+Zero: 0 Param1: Restored
+Multi line function
+One: 1
diff --git a/shell/hush_test/hush-misc/func1.tests b/shell/hush_test/hush-misc/func1.tests
new file mode 100755 (executable)
index 0000000..ffb269f
--- /dev/null
@@ -0,0 +1,16 @@
+f() { echo Hello; }
+g () { echo One: $# Param1: $1; }
+h ( )
+{
+    echo -n 'Multi ' && echo -n 'line '
+    echo function
+    false
+}
+
+f
+echo Zero: $?
+set -- Restored
+{ g World; }
+echo Zero: $? Param1: $1
+( h )
+echo One: $?
diff --git a/shell/hush_test/hush-misc/func2.right b/shell/hush_test/hush-misc/func2.right
new file mode 100644 (file)
index 0000000..f2a041d
--- /dev/null
@@ -0,0 +1,5 @@
+First 0
+Second 0
+First 1
+Second 1
+Done
diff --git a/shell/hush_test/hush-misc/func2.tests b/shell/hush_test/hush-misc/func2.tests
new file mode 100755 (executable)
index 0000000..763203f
--- /dev/null
@@ -0,0 +1,9 @@
+i=0
+while test $i != 2; do
+    f() { echo First $i; }
+    f
+    f() { echo Second $i; }
+    f
+    : $((i++))
+done
+echo Done
diff --git a/shell/hush_test/hush-misc/heredoc1.right b/shell/hush_test/hush-misc/heredoc1.right
new file mode 100644 (file)
index 0000000..7fc68f3
--- /dev/null
@@ -0,0 +1,5 @@
+qwe
+asd
+123
+456
+Ok
diff --git a/shell/hush_test/hush-misc/heredoc1.tests b/shell/hush_test/hush-misc/heredoc1.tests
new file mode 100755 (executable)
index 0000000..2eeb472
--- /dev/null
@@ -0,0 +1,9 @@
+cat <<000; cat <<www; cat <<eee
+000
+qwe
+asd
+www
+123
+456
+eee
+echo Ok
diff --git a/shell/hush_test/hush-misc/heredoc2.right b/shell/hush_test/hush-misc/heredoc2.right
new file mode 100644 (file)
index 0000000..74110e3
--- /dev/null
@@ -0,0 +1,9 @@
+exit EOF-f
+"
+echo 1
+echo Hello World
+moo     
+ EOF-f
+EOF-f   f
+EOF-f 
+Ok
diff --git a/shell/hush_test/hush-misc/heredoc2.tests b/shell/hush_test/hush-misc/heredoc2.tests
new file mode 100755 (executable)
index 0000000..e619bde
--- /dev/null
@@ -0,0 +1,12 @@
+f=1
+  cat <<- EOF-f
+               exit EOF-f
+"
+echo $f
+echo `echo Hello World`
+               moo      
+ EOF-f
+EOF-f   f
+EOF-f 
+EOF-f
+echo Ok
diff --git a/shell/hush_test/hush-misc/heredoc3.right b/shell/hush_test/hush-misc/heredoc3.right
new file mode 100644 (file)
index 0000000..6ed517f
--- /dev/null
@@ -0,0 +1,9 @@
+exit EOF-f
+"
+echo $f
+echo `echo Hello World`
+moo     
+ EOF-f
+EOF-f   f
+EOF-f 
+Ok
diff --git a/shell/hush_test/hush-misc/heredoc3.tests b/shell/hush_test/hush-misc/heredoc3.tests
new file mode 100755 (executable)
index 0000000..938577a
--- /dev/null
@@ -0,0 +1,12 @@
+f=1
+  cat <<- EOF-f""
+               exit EOF-f
+"
+echo $f
+echo `echo Hello World`
+               moo      
+ EOF-f
+EOF-f   f
+EOF-f 
+EOF-f
+echo Ok
diff --git a/shell/hush_test/hush-misc/heredoc_huge.right b/shell/hush_test/hush-misc/heredoc_huge.right
new file mode 100644 (file)
index 0000000..11740f6
--- /dev/null
@@ -0,0 +1,3 @@
+546ed3f5c81c780d3ab86ada14824237  -
+546ed3f5c81c780d3ab86ada14824237  -
+End
diff --git a/shell/hush_test/hush-misc/heredoc_huge.tests b/shell/hush_test/hush-misc/heredoc_huge.tests
new file mode 100755 (executable)
index 0000000..c2ec281
--- /dev/null
@@ -0,0 +1,9 @@
+# This creates 120k heredoc
+echo 'cat <<HERE | md5sum' >"$0.tmp"
+yes "123456789 123456789 123456789 123456789" | head -3000 >>"$0.tmp"
+echo 'HERE' >>"$0.tmp"
+
+yes "123456789 123456789 123456789 123456789" | head -3000 | md5sum
+. "$0.tmp"
+rm "$0.tmp"
+echo End
diff --git a/shell/hush_test/hush-misc/if_false_exitcode.right b/shell/hush_test/hush-misc/if_false_exitcode.right
new file mode 100644 (file)
index 0000000..7b24a35
--- /dev/null
@@ -0,0 +1 @@
+Ok:0
diff --git a/shell/hush_test/hush-misc/if_false_exitcode.tests b/shell/hush_test/hush-misc/if_false_exitcode.tests
new file mode 100755 (executable)
index 0000000..01b36b1
--- /dev/null
@@ -0,0 +1,2 @@
+if false; then echo Bad; fi
+echo Ok:$?
diff --git a/shell/hush_test/hush-misc/pid.right b/shell/hush_test/hush-misc/pid.right
new file mode 100644 (file)
index 0000000..573541a
--- /dev/null
@@ -0,0 +1 @@
+0
diff --git a/shell/hush_test/hush-misc/pid.tests b/shell/hush_test/hush-misc/pid.tests
new file mode 100755 (executable)
index 0000000..eaeaa71
--- /dev/null
@@ -0,0 +1 @@
+test `(echo $$)` = `echo $$`; echo $?
diff --git a/shell/hush_test/hush-misc/read.right b/shell/hush_test/hush-misc/read.right
new file mode 100644 (file)
index 0000000..0e50e2a
--- /dev/null
@@ -0,0 +1,4 @@
+read
+cat
+echo "REPLY=$REPLY"
+REPLY=exec <read.tests
diff --git a/shell/hush_test/hush-misc/read.tests b/shell/hush_test/hush-misc/read.tests
new file mode 100755 (executable)
index 0000000..ff1acbd
--- /dev/null
@@ -0,0 +1,4 @@
+exec <read.tests
+read
+cat
+echo "REPLY=$REPLY"
diff --git a/shell/hush_test/hush-misc/redir1.right b/shell/hush_test/hush-misc/redir1.right
new file mode 100644 (file)
index 0000000..15515d1
--- /dev/null
@@ -0,0 +1,12 @@
+Test 0:  var:ok
+File created:ok
+Test 1:  var:ok
+File created:ok
+Test 2:  var:ok
+File created:ok
+Test 3:  var:ok
+File created:ok
+Test 4:  var:ok
+File created:ok
+Test 5:  var:ok
+File created:ok
diff --git a/shell/hush_test/hush-misc/redir1.tests b/shell/hush_test/hush-misc/redir1.tests
new file mode 100755 (executable)
index 0000000..ef2fbfb
--- /dev/null
@@ -0,0 +1,40 @@
+rm shell_test_$$ 2>/dev/null
+var=bad
+>shell_test_$$ var=ok
+echo "Test 0:  var:$var"
+test -f shell_test_$$ && echo "File created:ok"
+
+rm shell_test_$$ 2>/dev/null
+var=bad
+var=ok >shell_test_$$
+echo "Test 1:  var:$var"
+test -f shell_test_$$ && echo "File created:ok"
+
+rm shell_test_$$ 2>/dev/null
+var=ok
+true | var=bad >shell_test_$$
+echo "Test 2:  var:$var"
+test -f shell_test_$$ && echo "File created:ok"
+
+rm shell_test_$$ 2>/dev/null
+var=bad
+{ var=ok >shell_test_$$; }
+echo "Test 3:  var:$var"
+test -f shell_test_$$ && echo "File created:ok"
+
+rm shell_test_$$ 2>/dev/null
+var=ok
+{ var=bad >shell_test_$$; } &
+# cant use usleep as it isnt standard in $PATH --
+# we fail when testing busybox compiled solely as "hush"
+wait
+echo "Test 4:  var:$var"
+test -f shell_test_$$ && echo "File created:ok"
+
+rm shell_test_$$ 2>/dev/null
+var=ok
+( var=bad >shell_test_$$ )
+echo "Test 5:  var:$var"
+test -f shell_test_$$ && echo "File created:ok"
+
+rm shell_test_$$ 2>/dev/null
diff --git a/shell/hush_test/hush-misc/redir2.right b/shell/hush_test/hush-misc/redir2.right
new file mode 100644 (file)
index 0000000..7326d96
--- /dev/null
@@ -0,0 +1 @@
+Ok
diff --git a/shell/hush_test/hush-misc/redir2.tests b/shell/hush_test/hush-misc/redir2.tests
new file mode 100755 (executable)
index 0000000..81983ca
--- /dev/null
@@ -0,0 +1,2 @@
+echo NOT SHOWN \2>/dev/null
+echo Ok
diff --git a/shell/hush_test/hush-misc/redir3.right b/shell/hush_test/hush-misc/redir3.right
new file mode 100644 (file)
index 0000000..3d20bbf
--- /dev/null
@@ -0,0 +1,14 @@
+hush: can't open '/does/not/exist': No such file or directory
+One:1
+hush: can't open '/cant/be/created': No such file or directory
+One:1
+Ok
+hush: can't open '/cant/be/created': No such file or directory
+Zero:0
+hush: can't open '/cant/be/created': No such file or directory
+One:1
+hush: can't open '/cant/be/created': No such file or directory
+One:1
+hush: can't open '/cant/be/created': No such file or directory
+Zero:0
+Done
diff --git a/shell/hush_test/hush-misc/redir3.tests b/shell/hush_test/hush-misc/redir3.tests
new file mode 100755 (executable)
index 0000000..7c28e43
--- /dev/null
@@ -0,0 +1,9 @@
+echo Error >/does/not/exist; echo One:$?
+t=BAD
+t=Ok >>/cant/be/created; echo One:$?
+echo $t
+! >/cant/be/created; echo Zero:$?
+exec >/cant/be/created; echo One:$?
+exec /bin/true >/cant/be/created; echo One:$?
+! exec /bin/true >/cant/be/created; echo Zero:$?
+echo Done
diff --git a/shell/hush_test/hush-misc/redir4.right b/shell/hush_test/hush-misc/redir4.right
new file mode 100644 (file)
index 0000000..ead25f6
--- /dev/null
@@ -0,0 +1,18 @@
+shell_test
+\shell_test
+\shell_test
+\shell_test
+Here1
+Ok1
+Here2
+Ok2
+Here3
+Ok3
+Here4
+Ok4
+Now with variable refs
+shell_test_1
+\shell_test_1
+\shell_test_1
+\shell_test_1
+Done
diff --git a/shell/hush_test/hush-misc/redir4.tests b/shell/hush_test/hush-misc/redir4.tests
new file mode 100755 (executable)
index 0000000..c50b8ce
--- /dev/null
@@ -0,0 +1,85 @@
+rm *shell_test* 2>/dev/null
+
+>\shell_test
+echo *shell_test*
+rm *shell_test*
+
+>\\shell_test
+echo *shell_test*
+rm *shell_test*
+
+>"\shell_test"
+echo *shell_test*
+rm *shell_test*
+
+>"\\shell_test"
+echo *shell_test*
+rm *shell_test*
+
+
+cat <<\shell_test
+Here1
+shell_test
+echo Ok1
+
+cat <<\\shell_test
+Here2
+\shell_test
+echo Ok2
+
+cat <<"\shell_test"
+Here3
+\shell_test
+echo Ok3
+
+cat <<"\\shell_test"
+Here4
+\shell_test
+echo Ok4
+
+
+echo Now with variable refs
+i=1
+
+
+>\shell_test_$i
+echo *shell_test*
+rm *shell_test*
+
+>\\shell_test_$i
+echo *shell_test*
+rm *shell_test*
+
+>"\shell_test_$i"
+echo *shell_test*
+rm *shell_test*
+
+>"\\shell_test_$i"
+echo *shell_test*
+rm *shell_test*
+
+echo Done;exit
+# UNFIXED BUG. bash apparently will expand $i even in terminating delimiter.
+# http://www.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html
+# does not mandate this behavior.
+# This is not likely to be used much in real-world.
+
+cat <<\shell_test_$i
+Here1
+shell_test_$i
+echo Ok1
+
+cat <<\\shell_test_$i
+Here2
+\shell_test_$i
+echo Ok2
+
+cat <<"\shell_test_$i"
+Here3
+\shell_test_$i
+echo Ok3
+
+cat <<"\\shell_test_$i"
+Here4
+\shell_test_$i
+echo Ok4
diff --git a/shell/hush_test/hush-misc/shift.right b/shell/hush_test/hush-misc/shift.right
new file mode 100644 (file)
index 0000000..d281e35
--- /dev/null
@@ -0,0 +1,6 @@
+./shift.tests abc d e
+./shift.tests d e 123
+./shift.tests d e 123
+./shift.tests
+./shift.tests
+./shift.tests
diff --git a/shell/hush_test/hush-misc/shift.tests b/shell/hush_test/hush-misc/shift.tests
new file mode 100755 (executable)
index 0000000..53ef249
--- /dev/null
@@ -0,0 +1,14 @@
+if test $# = 0; then
+    exec "$THIS_SH" $0 abc "d e" 123
+fi
+echo $0 $1 $2
+shift
+echo $0 $1 $2
+shift 999
+echo $0 $1 $2
+shift 2
+echo $0 $1 $2
+shift 2
+echo $0 $1 $2
+shift
+echo $0 $1 $2
diff --git a/shell/hush_test/hush-misc/syntax_err.right b/shell/hush_test/hush-misc/syntax_err.right
new file mode 100644 (file)
index 0000000..680e796
--- /dev/null
@@ -0,0 +1,4 @@
+shown
+hush: syntax error: unterminated '
+test
+not shown
diff --git a/shell/hush_test/hush-misc/syntax_err.tests b/shell/hush_test/hush-misc/syntax_err.tests
new file mode 100755 (executable)
index 0000000..d10ed42
--- /dev/null
@@ -0,0 +1,3 @@
+echo shown
+echo test `echo 'aa`
+echo not shown
diff --git a/shell/hush_test/hush-misc/syntax_err_negate.right b/shell/hush_test/hush-misc/syntax_err_negate.right
new file mode 100644 (file)
index 0000000..8c70106
--- /dev/null
@@ -0,0 +1,2 @@
+bash 3.2 fails this
+hush: syntax error: ! ! command
diff --git a/shell/hush_test/hush-misc/syntax_err_negate.tests b/shell/hush_test/hush-misc/syntax_err_negate.tests
new file mode 100755 (executable)
index 0000000..d61b1b0
--- /dev/null
@@ -0,0 +1,2 @@
+echo bash 3.2 fails this
+! ! true
diff --git a/shell/hush_test/hush-misc/until1.right b/shell/hush_test/hush-misc/until1.right
new file mode 100644 (file)
index 0000000..be2daad
--- /dev/null
@@ -0,0 +1,3 @@
+1
+1
+Ok:0
diff --git a/shell/hush_test/hush-misc/until1.tests b/shell/hush_test/hush-misc/until1.tests
new file mode 100755 (executable)
index 0000000..10ab283
--- /dev/null
@@ -0,0 +1,11 @@
+x=1
+until test "$x" = 4; do echo $x; x=4; done
+
+# We had a bug in multi-line form
+x=1
+until test "$x" = 4; do
+        echo $x
+        x=4
+done
+
+echo Ok:$?
diff --git a/shell/hush_test/hush-misc/while1.right b/shell/hush_test/hush-misc/while1.right
new file mode 100644 (file)
index 0000000..7c4d7be
--- /dev/null
@@ -0,0 +1 @@
+OK:0
diff --git a/shell/hush_test/hush-misc/while1.tests b/shell/hush_test/hush-misc/while1.tests
new file mode 100755 (executable)
index 0000000..11e201e
--- /dev/null
@@ -0,0 +1,2 @@
+while false; do echo NOT SHOWN; done
+echo OK:$?
diff --git a/shell/hush_test/hush-misc/while2.right b/shell/hush_test/hush-misc/while2.right
new file mode 100644 (file)
index 0000000..07207cc
--- /dev/null
@@ -0,0 +1,2 @@
+Hello
+OK:0
diff --git a/shell/hush_test/hush-misc/while2.tests b/shell/hush_test/hush-misc/while2.tests
new file mode 100755 (executable)
index 0000000..2247adc
--- /dev/null
@@ -0,0 +1,2 @@
+while echo Hello; false; do echo NOT SHOWN; done
+echo OK:$?
diff --git a/shell/hush_test/hush-misc/while_in_subshell.right b/shell/hush_test/hush-misc/while_in_subshell.right
new file mode 100644 (file)
index 0000000..290d39b
--- /dev/null
@@ -0,0 +1 @@
+OK: 0
diff --git a/shell/hush_test/hush-misc/while_in_subshell.tests b/shell/hush_test/hush-misc/while_in_subshell.tests
new file mode 100755 (executable)
index 0000000..def8e09
--- /dev/null
@@ -0,0 +1,2 @@
+(while true; do exit; done)
+echo OK: $?
diff --git a/shell/hush_test/hush-parsing/argv0.right b/shell/hush_test/hush-parsing/argv0.right
new file mode 100644 (file)
index 0000000..d86bac9
--- /dev/null
@@ -0,0 +1 @@
+OK
diff --git a/shell/hush_test/hush-parsing/argv0.tests b/shell/hush_test/hush-parsing/argv0.tests
new file mode 100755 (executable)
index 0000000..f5c40f6
--- /dev/null
@@ -0,0 +1,4 @@
+if test $# = 0; then
+    exec "$THIS_SH" "$0" arg
+fi
+echo OK
diff --git a/shell/hush_test/hush-parsing/escape1.right b/shell/hush_test/hush-parsing/escape1.right
new file mode 100644 (file)
index 0000000..1899b87
--- /dev/null
@@ -0,0 +1,4 @@
+\
+a\b
+\\
+c\\d
diff --git a/shell/hush_test/hush-parsing/escape1.tests b/shell/hush_test/hush-parsing/escape1.tests
new file mode 100755 (executable)
index 0000000..25ac96b
--- /dev/null
@@ -0,0 +1,6 @@
+test "$CONFIG_FEATURE_FANCY_ECHO" = "y" || exit 77
+
+echo "\\"
+echo a"\\"b
+echo '\\'
+echo c'\\'d
diff --git a/shell/hush_test/hush-parsing/escape2.right b/shell/hush_test/hush-parsing/escape2.right
new file mode 100644 (file)
index 0000000..f55fd4a
--- /dev/null
@@ -0,0 +1,4 @@
+*?[a]*
+a*?[a]*b
+*?[a]*
+c*?[a]*d
diff --git a/shell/hush_test/hush-parsing/escape2.tests b/shell/hush_test/hush-parsing/escape2.tests
new file mode 100755 (executable)
index 0000000..ee71801
--- /dev/null
@@ -0,0 +1,4 @@
+echo "*?[a]*"
+echo a"*?[a]*"b
+echo '*?[a]*'
+echo c'*?[a]*'d
diff --git a/shell/hush_test/hush-parsing/escape3.right b/shell/hush_test/hush-parsing/escape3.right
new file mode 100644 (file)
index 0000000..da02a97
--- /dev/null
@@ -0,0 +1,23 @@
+v: a \ b \\ c \\\ d \\\\ e
+v: a \ b \\ c \\\ d \\\\ e
+Unquoted:
+.a.
+.\.
+.b.
+.\\.
+.c.
+.\\\.
+.d.
+.\\\\.
+.e.
+Quoted:
+.a.
+.\.
+.b.
+.\\.
+.c.
+.\\\.
+.d.
+.\\\\.
+.e.
+done
diff --git a/shell/hush_test/hush-parsing/escape3.tests b/shell/hush_test/hush-parsing/escape3.tests
new file mode 100755 (executable)
index 0000000..18705bd
--- /dev/null
@@ -0,0 +1,10 @@
+test "$CONFIG_FEATURE_FANCY_ECHO" = "y" || exit 77
+
+v='a \ b \\ c \\\ d \\\\ e'
+echo v: $v
+echo v: "$v"
+echo Unquoted:
+for a in $v; do echo .$a.; done
+echo Quoted:
+for a in $v; do echo ".$a."; done
+echo done
diff --git a/shell/hush_test/hush-parsing/escape5.right b/shell/hush_test/hush-parsing/escape5.right
new file mode 100644 (file)
index 0000000..3cdd393
--- /dev/null
@@ -0,0 +1,9 @@
+a\nb\nc\n
+a
+b
+c
+a\nb\nc\n
+a
+b
+c
+Done
diff --git a/shell/hush_test/hush-parsing/escape5.tests b/shell/hush_test/hush-parsing/escape5.tests
new file mode 100755 (executable)
index 0000000..337a98e
--- /dev/null
@@ -0,0 +1,7 @@
+v="a\nb\nc\n"
+echo "$v"
+printf "$v"
+v='a\nb\nc\n'
+echo "$v"
+printf "$v"
+echo Done
diff --git a/shell/hush_test/hush-parsing/negate.right b/shell/hush_test/hush-parsing/negate.right
new file mode 100644 (file)
index 0000000..0116601
--- /dev/null
@@ -0,0 +1,35 @@
+! printing !
+0
+1
+1
+0
+0
+0
+!
+a
+b
+c
+! 1
+a 1
+b 1
+c 1
+! 1
+a 1
+b 1
+c 1
+0
+0
+0
+0
+1
+1
+1
+1
+0
+0
+0
+0
+1
+1
+1
+1
diff --git a/shell/hush_test/hush-parsing/negate.tests b/shell/hush_test/hush-parsing/negate.tests
new file mode 100755 (executable)
index 0000000..c25127d
--- /dev/null
@@ -0,0 +1,16 @@
+echo ! printing !
+! false
+echo $?
+! true
+echo $?
+if ! false; then false; echo $?; fi
+echo $?
+if ! false; then ! false; echo $?; fi
+echo $?
+for a in ! a b c; do echo $a; done
+for a in ! a b c; do ! printf "$a "; echo $?; done
+for a in ! a b c; do ! /usr/bin/printf "$a "; echo $?; done
+for a in ! a b c; do ! printf "$a " | false; echo $?; done
+for a in ! a b c; do ! printf "$a " | true; echo $?; done
+for a in ! a b c; do ! { printf "$a " | false; }; echo $?; done
+for a in ! a b c; do ! { printf "$a " | true; }; echo $?; done
diff --git a/shell/hush_test/hush-parsing/noeol.right b/shell/hush_test/hush-parsing/noeol.right
new file mode 100644 (file)
index 0000000..e427984
--- /dev/null
@@ -0,0 +1 @@
+HELLO
diff --git a/shell/hush_test/hush-parsing/noeol.tests b/shell/hush_test/hush-parsing/noeol.tests
new file mode 100755 (executable)
index 0000000..a93113a
--- /dev/null
@@ -0,0 +1,2 @@
+# next line has no EOL!
+echo HELLO
\ No newline at end of file
diff --git a/shell/hush_test/hush-parsing/noeol2.right b/shell/hush_test/hush-parsing/noeol2.right
new file mode 100644 (file)
index 0000000..d00491f
--- /dev/null
@@ -0,0 +1 @@
+1
diff --git a/shell/hush_test/hush-parsing/noeol2.tests b/shell/hush_test/hush-parsing/noeol2.tests
new file mode 100755 (executable)
index 0000000..1220f05
--- /dev/null
@@ -0,0 +1,7 @@
+# last line has no EOL!
+if true
+then
+  echo 1
+else
+  echo 2
+fi
\ No newline at end of file
diff --git a/shell/hush_test/hush-parsing/noeol3.right b/shell/hush_test/hush-parsing/noeol3.right
new file mode 100644 (file)
index 0000000..56f8515
--- /dev/null
@@ -0,0 +1 @@
+hush: syntax error: unterminated "
diff --git a/shell/hush_test/hush-parsing/noeol3.tests b/shell/hush_test/hush-parsing/noeol3.tests
new file mode 100755 (executable)
index 0000000..ec958ed
--- /dev/null
@@ -0,0 +1,2 @@
+# last line has no EOL!
+echo "unterminated
\ No newline at end of file
diff --git a/shell/hush_test/hush-parsing/process_subst.right b/shell/hush_test/hush-parsing/process_subst.right
new file mode 100644 (file)
index 0000000..397bc80
--- /dev/null
@@ -0,0 +1,3 @@
+TESTzzBEST
+TEST$(echo zz)BEST
+TEST'BEST
diff --git a/shell/hush_test/hush-parsing/process_subst.tests b/shell/hush_test/hush-parsing/process_subst.tests
new file mode 100755 (executable)
index 0000000..21996bc
--- /dev/null
@@ -0,0 +1,3 @@
+echo "TEST`echo zz;echo;echo`BEST"
+echo "TEST`echo '$(echo zz)'`BEST"
+echo "TEST`echo "'"`BEST"
diff --git a/shell/hush_test/hush-parsing/quote1.right b/shell/hush_test/hush-parsing/quote1.right
new file mode 100644 (file)
index 0000000..cb38205
--- /dev/null
@@ -0,0 +1 @@
+'1'
diff --git a/shell/hush_test/hush-parsing/quote1.tests b/shell/hush_test/hush-parsing/quote1.tests
new file mode 100755 (executable)
index 0000000..f558954
--- /dev/null
@@ -0,0 +1,2 @@
+a=1
+echo "'$a'"
diff --git a/shell/hush_test/hush-parsing/quote2.right b/shell/hush_test/hush-parsing/quote2.right
new file mode 100644 (file)
index 0000000..3bc9edc
--- /dev/null
@@ -0,0 +1 @@
+>1
diff --git a/shell/hush_test/hush-parsing/quote2.tests b/shell/hush_test/hush-parsing/quote2.tests
new file mode 100755 (executable)
index 0000000..bd966f3
--- /dev/null
@@ -0,0 +1,2 @@
+a=1
+echo ">$a"
diff --git a/shell/hush_test/hush-parsing/quote3.right b/shell/hush_test/hush-parsing/quote3.right
new file mode 100644 (file)
index 0000000..bbe46df
--- /dev/null
@@ -0,0 +1,12 @@
+Testing: in ""
+..
+Testing: in ''
+..
+Testing: in $empty
+Testing: in $empty""
+..
+Testing: in $empty''
+..
+Testing: in "$empty"
+..
+Finished
diff --git a/shell/hush_test/hush-parsing/quote3.tests b/shell/hush_test/hush-parsing/quote3.tests
new file mode 100755 (executable)
index 0000000..b5fd597
--- /dev/null
@@ -0,0 +1,21 @@
+empty=''
+
+echo 'Testing: in ""'
+for a in ""; do echo ".$a."; done
+
+echo 'Testing: in '"''"
+for a in ''; do echo ".$a."; done
+
+echo 'Testing: in $empty'
+for a in $empty; do echo ".$a."; done
+
+echo 'Testing: in $empty""'
+for a in $empty""; do echo ".$a."; done
+
+echo 'Testing: in $empty'"''"
+for a in $empty''; do echo ".$a."; done
+
+echo 'Testing: in "$empty"'
+for a in "$empty"; do echo ".$a."; done
+
+echo Finished
diff --git a/shell/hush_test/hush-parsing/quote4.right b/shell/hush_test/hush-parsing/quote4.right
new file mode 100644 (file)
index 0000000..b2901ea
--- /dev/null
@@ -0,0 +1 @@
+a b
diff --git a/shell/hush_test/hush-parsing/quote4.tests b/shell/hush_test/hush-parsing/quote4.tests
new file mode 100755 (executable)
index 0000000..f1dabfa
--- /dev/null
@@ -0,0 +1,2 @@
+a_b='a b'
+echo "$a_b"
diff --git a/shell/hush_test/hush-parsing/redir_space.right b/shell/hush_test/hush-parsing/redir_space.right
new file mode 100644 (file)
index 0000000..0842952
--- /dev/null
@@ -0,0 +1,3 @@
+z1.tmp: 1
+z2.tmp: 1
+"z1.tmp z2.tmp": TEST 0
diff --git a/shell/hush_test/hush-parsing/redir_space.tests b/shell/hush_test/hush-parsing/redir_space.tests
new file mode 100755 (executable)
index 0000000..c0b5430
--- /dev/null
@@ -0,0 +1,6 @@
+v='z1.tmp z2.tmp'
+echo TEST >$v
+echo 'z1.tmp:' `cat 'z1.tmp' 2>/dev/null; echo $?`
+echo 'z2.tmp:' `cat 'z2.tmp' 2>/dev/null; echo $?`
+echo '"z1.tmp z2.tmp":' `cat 'z1.tmp z2.tmp' 2>/dev/null; echo $?`
+rm z*.tmp
diff --git a/shell/hush_test/hush-parsing/starquoted.right b/shell/hush_test/hush-parsing/starquoted.right
new file mode 100644 (file)
index 0000000..b56323f
--- /dev/null
@@ -0,0 +1,8 @@
+.1 abc d e f.
+.1.
+.abc.
+.d e f.
+.-1 abc d e f-.
+.-1.
+.abc.
+.d e f-.
diff --git a/shell/hush_test/hush-parsing/starquoted.tests b/shell/hush_test/hush-parsing/starquoted.tests
new file mode 100755 (executable)
index 0000000..2fe49b1
--- /dev/null
@@ -0,0 +1,8 @@
+if test $# = 0; then
+    exec "$THIS_SH" "$0" 1 abc 'd e f'
+fi
+
+for a in "$*"; do echo ".$a."; done
+for a in "$@"; do echo ".$a."; done
+for a in "-$*-"; do echo ".$a."; done
+for a in "-$@-"; do echo ".$a."; done
diff --git a/shell/hush_test/hush-parsing/starquoted2.right b/shell/hush_test/hush-parsing/starquoted2.right
new file mode 100644 (file)
index 0000000..e1562ed
--- /dev/null
@@ -0,0 +1,5 @@
+Should be printed
+Should be printed
+Empty:
+Empty:
+Empty:
diff --git a/shell/hush_test/hush-parsing/starquoted2.tests b/shell/hush_test/hush-parsing/starquoted2.tests
new file mode 100755 (executable)
index 0000000..f305c4c
--- /dev/null
@@ -0,0 +1,19 @@
+if test $# != 0; then
+    exec "$THIS_SH" "$0"
+fi
+
+# No params!
+for a in "$*"; do echo Should be printed; done
+for a in "$@"; do echo Should not be printed; done
+# Yes, believe it or not, bash is mesmerized by "$@" and stops
+# treating "" as "this word cannot be expanded to nothing,
+# but must be at least null string". Now it can be expanded to nothing.
+for a in "$@"""; do echo Should not be printed; done
+for a in """$@"; do echo Should not be printed; done
+for a in """$@"''"$@"''; do echo Should not be printed; done
+for a in ""; do echo Should be printed; done
+
+# Bug 207: "$@" expands to nothing, and we erroneously glob "%s\n" twice:
+printf 'Empty:%s\n' "$@"
+printf "Empty:%s\n" "$@"
+printf "Empty:%s\\n" "$@"
diff --git a/shell/hush_test/hush-psubst/tick.right b/shell/hush_test/hush-psubst/tick.right
new file mode 100644 (file)
index 0000000..6ed281c
--- /dev/null
@@ -0,0 +1,2 @@
+1
+1
diff --git a/shell/hush_test/hush-psubst/tick.tests b/shell/hush_test/hush-psubst/tick.tests
new file mode 100755 (executable)
index 0000000..1f749a9
--- /dev/null
@@ -0,0 +1,4 @@
+true
+false; echo `echo $?`
+true
+{ false; echo `echo $?`; }
diff --git a/shell/hush_test/hush-psubst/tick2.right b/shell/hush_test/hush-psubst/tick2.right
new file mode 100644 (file)
index 0000000..216c883
--- /dev/null
@@ -0,0 +1 @@
+BAZ
diff --git a/shell/hush_test/hush-psubst/tick2.tests b/shell/hush_test/hush-psubst/tick2.tests
new file mode 100755 (executable)
index 0000000..db4e944
--- /dev/null
@@ -0,0 +1,5 @@
+if false; then
+    echo "FOO"
+    tmp=`echo BAR >&2`
+fi
+echo BAZ
diff --git a/shell/hush_test/hush-psubst/tick3.right b/shell/hush_test/hush-psubst/tick3.right
new file mode 100644 (file)
index 0000000..dc84e92
--- /dev/null
@@ -0,0 +1,6 @@
+\TESTZZBEST
+$TEST
+Q
+a\bc
+a"c
+done:0
diff --git a/shell/hush_test/hush-psubst/tick3.tests b/shell/hush_test/hush-psubst/tick3.tests
new file mode 100755 (executable)
index 0000000..469c43c
--- /dev/null
@@ -0,0 +1,12 @@
+test "$CONFIG_FEATURE_FANCY_ECHO" = "y" || exit 77
+
+TEST=Q
+# \` is special
+echo `echo '\'TEST\`echo ZZ\`BEST`
+# \$ and \\ are special
+echo `echo \\$TEST`
+echo `echo \$TEST`
+echo a`echo \\\\b`c
+# \" etc are NOT special (passed verbatim WITH \)!
+echo a`echo \"`c
+echo done:$?
diff --git a/shell/hush_test/hush-psubst/tick4.right b/shell/hush_test/hush-psubst/tick4.right
new file mode 100644 (file)
index 0000000..d8030ea
--- /dev/null
@@ -0,0 +1,7 @@
+(TEST) BEST
+TEST) BEST
+((TEST) BEST
+)
+abc
+a)c
+OK: 0
diff --git a/shell/hush_test/hush-psubst/tick4.tests b/shell/hush_test/hush-psubst/tick4.tests
new file mode 100755 (executable)
index 0000000..f2305fb
--- /dev/null
@@ -0,0 +1,7 @@
+echo $(echo '(TEST)' BEST)
+echo $(echo 'TEST)' BEST)
+echo $(echo \(\(TEST\) BEST)
+echo $(echo \))
+echo $(echo a"`echo "b"`"c )
+echo $(echo a"`echo ")"`"c )
+echo OK: $?
diff --git a/shell/hush_test/hush-psubst/tick_huge.right b/shell/hush_test/hush-psubst/tick_huge.right
new file mode 100644 (file)
index 0000000..11740f6
--- /dev/null
@@ -0,0 +1,3 @@
+546ed3f5c81c780d3ab86ada14824237  -
+546ed3f5c81c780d3ab86ada14824237  -
+End
diff --git a/shell/hush_test/hush-psubst/tick_huge.tests b/shell/hush_test/hush-psubst/tick_huge.tests
new file mode 100755 (executable)
index 0000000..acce92f
--- /dev/null
@@ -0,0 +1,7 @@
+# This creates 120k file
+yes "123456789 123456789 123456789 123456789" | head -3000 >>"$0.tmp"
+
+echo "`cat $0.tmp`" | md5sum
+rm "$0.tmp"
+yes "123456789 123456789 123456789 123456789" | head -3000 | md5sum
+echo End
diff --git a/shell/hush_test/hush-trap/catch.right b/shell/hush_test/hush-trap/catch.right
new file mode 100644 (file)
index 0000000..9e34c4c
--- /dev/null
@@ -0,0 +1,4 @@
+sending USR2
+caught
+sending USR2
+sending USR2
diff --git a/shell/hush_test/hush-trap/catch.tests b/shell/hush_test/hush-trap/catch.tests
new file mode 100755 (executable)
index 0000000..d2a21d1
--- /dev/null
@@ -0,0 +1,20 @@
+# avoid ugly warnings about signals not being caught
+trap ":" USR1 USR2
+
+"$THIS_SH" -c '
+trap "echo caught" USR2
+echo "sending USR2"
+kill -USR2 $$
+
+trap "" USR2
+echo "sending USR2"
+kill -USR2 $$
+
+trap "-" USR2
+echo "sending USR2"
+kill -USR2 $$
+
+echo "not reached"
+'
+
+trap "-" USR1 USR2
diff --git a/shell/hush_test/hush-trap/exit.right b/shell/hush_test/hush-trap/exit.right
new file mode 100644 (file)
index 0000000..b4932fb
--- /dev/null
@@ -0,0 +1,2 @@
+cow
+moo
diff --git a/shell/hush_test/hush-trap/exit.tests b/shell/hush_test/hush-trap/exit.tests
new file mode 100755 (executable)
index 0000000..092543c
--- /dev/null
@@ -0,0 +1,3 @@
+"$THIS_SH" -c 'trap "echo cow" 0'
+"$THIS_SH" -c 'trap "echo moo" EXIT'
+"$THIS_SH" -c 'trap "echo no" 0; trap 0'
diff --git a/shell/hush_test/hush-trap/save-ret.right b/shell/hush_test/hush-trap/save-ret.right
new file mode 100644 (file)
index 0000000..a3e12ce
--- /dev/null
@@ -0,0 +1,2 @@
+YEAH
+0
diff --git a/shell/hush_test/hush-trap/save-ret.tests b/shell/hush_test/hush-trap/save-ret.tests
new file mode 100755 (executable)
index 0000000..0786b6d
--- /dev/null
@@ -0,0 +1,4 @@
+# make sure we do not corrupt $? across traps
+trap "echo YEAH; false" USR1
+kill -USR1 $$
+echo $?
diff --git a/shell/hush_test/hush-trap/usage.right b/shell/hush_test/hush-trap/usage.right
new file mode 100644 (file)
index 0000000..c0dbd6c
--- /dev/null
@@ -0,0 +1,14 @@
+___
+___
+___
+trap -- 'a' EXIT
+trap -- 'a' INT
+trap -- 'a' USR1
+trap -- 'a' USR2
+___
+___
+trap -- 'a' USR1
+trap -- 'a' USR2
+___
+___
+trap -- 'a' USR2
diff --git a/shell/hush_test/hush-trap/usage.tests b/shell/hush_test/hush-trap/usage.tests
new file mode 100755 (executable)
index 0000000..d29c6e7
--- /dev/null
@@ -0,0 +1,23 @@
+# no output -- default state
+echo ___
+trap
+
+# assign some traps
+echo ___
+trap "a" EXIT INT USR1 USR2
+
+# show them all
+echo ___
+trap
+
+# clear one
+echo ___
+trap 0 INT
+echo ___
+trap
+
+# clear another
+echo ___
+trap "-" USR1
+echo ___
+trap
diff --git a/shell/hush_test/hush-vars/empty.right b/shell/hush_test/hush-vars/empty.right
new file mode 100644 (file)
index 0000000..2cb3c70
--- /dev/null
@@ -0,0 +1,3 @@
+a b c d e f 1 2 3 4 5 6 7 8 9 0 A B C D E F
+a b c d e f 1 2 3 4 5 6 7 8 9 0 A B C D E F
+a b c d e f 1 2 3 4 5 6 7 8 9 0 A B C D E F
diff --git a/shell/hush_test/hush-vars/empty.tests b/shell/hush_test/hush-vars/empty.tests
new file mode 100755 (executable)
index 0000000..a9c247e
--- /dev/null
@@ -0,0 +1,5 @@
+e=
+
+echo a b c d e f 1 2 3 4 5 6 7 8 9 0 A B C D E F
+echo a $e b $e c $e d $e e $e f $e 1 $e 2 $e 3 $e 4 $e 5 $e 6 $e 7 $e 8 $e 9 $e 0 $e A $e B $e C $e D $e E $e F
+echo $e a $e b $e c $e d $e e $e f $e 1 $e 2 $e 3 $e 4 $e 5 $e 6 $e 7 $e 8 $e 9 $e 0 $e A $e B $e C $e D $e E $e F
diff --git a/shell/hush_test/hush-vars/glob_and_vars.right b/shell/hush_test/hush-vars/glob_and_vars.right
new file mode 100644 (file)
index 0000000..3ac7ec5
--- /dev/null
@@ -0,0 +1 @@
+./glob_and_vars.right ./glob_and_vars.tests
diff --git a/shell/hush_test/hush-vars/glob_and_vars.tests b/shell/hush_test/hush-vars/glob_and_vars.tests
new file mode 100755 (executable)
index 0000000..482cf9d
--- /dev/null
@@ -0,0 +1,2 @@
+v=.
+echo $v/glob_and_vars.[tr]*
diff --git a/shell/hush_test/hush-vars/param_expand_alt.right b/shell/hush_test/hush-vars/param_expand_alt.right
new file mode 100644 (file)
index 0000000..4d2197a
--- /dev/null
@@ -0,0 +1,8 @@
+hush: syntax error: unterminated ${name}
+hush: syntax error: unterminated ${name}
+_0 _0
+_ _ _ _ _
+_aaaa _ _ _word _word
+_ _ _ _ _
+_ _ _ _word _
+_fff _ _ _word _word
diff --git a/shell/hush_test/hush-vars/param_expand_alt.tests b/shell/hush_test/hush-vars/param_expand_alt.tests
new file mode 100755 (executable)
index 0000000..dcdca86
--- /dev/null
@@ -0,0 +1,22 @@
+# first try some invalid patterns (do in subshell due to parsing error)
+"$THIS_SH" -c 'echo ${+}  ; echo moo'
+"$THIS_SH" -c 'echo ${:+} ; echo moo'
+
+# now some funky ones
+echo _${#+} _${#:+}
+
+# now some valid ones
+set --
+echo _$1 _${1+} _${1:+} _${1+word} _${1:+word}
+
+set -- aaaa
+echo _$1 _${1+} _${1:+} _${1+word} _${1:+word}
+
+unset f
+echo _$f _${f+} _${f:+} _${f+word} _${f:+word}
+
+f=
+echo _$f _${f+} _${f:+} _${f+word} _${f:+word}
+
+f=fff
+echo _$f _${f+} _${f:+} _${f+word} _${f:+word}
diff --git a/shell/hush_test/hush-vars/param_expand_assign.right b/shell/hush_test/hush-vars/param_expand_assign.right
new file mode 100644 (file)
index 0000000..d5b2580
--- /dev/null
@@ -0,0 +1,27 @@
+hush: syntax error: unterminated ${name}
+hush: syntax error: unterminated ${name}
+0
+0
+hush: $1: cannot assign in this way
+hush: $1: cannot assign in this way
+hush: $1: cannot assign in this way
+hush: $1: cannot assign in this way
+_aa
+_aa
+_aa
+_aa
+_
+_
+_
+_word
+_word
+_
+_
+_
+_
+_word
+_fff
+_fff
+_fff
+_fff
+_fff
diff --git a/shell/hush_test/hush-vars/param_expand_assign.tests b/shell/hush_test/hush-vars/param_expand_assign.tests
new file mode 100755 (executable)
index 0000000..149cb20
--- /dev/null
@@ -0,0 +1,38 @@
+# first try some invalid patterns (do in subshell due to parsing error)
+"$THIS_SH" -c 'echo ${=}'
+"$THIS_SH" -c 'echo ${:=}'
+
+# now some funky ones
+"$THIS_SH" -c 'echo ${#=}'
+"$THIS_SH" -c 'echo ${#:=}'
+
+# should error out
+"$THIS_SH" -c 'set --; echo _${1=}'
+"$THIS_SH" -c 'set --; echo _${1:=}'
+"$THIS_SH" -c 'set --; echo _${1=word}'
+"$THIS_SH" -c 'set --; echo _${1:=word}'
+
+# should not error
+"$THIS_SH" -c 'set aa; echo _${1=}'
+"$THIS_SH" -c 'set aa; echo _${1:=}'
+"$THIS_SH" -c 'set aa; echo _${1=word}'
+"$THIS_SH" -c 'set aa; echo _${1:=word}'
+
+# should work fine
+unset f; echo _$f
+unset f; echo _${f=}
+unset f; echo _${f:=}
+unset f; echo _${f=word}
+unset f; echo _${f:=word}
+
+f=; echo _$f
+f=; echo _${f=}
+f=; echo _${f:=}
+f=; echo _${f=word}
+f=; echo _${f:=word}
+
+f=fff; echo _$f
+f=fff; echo _${f=}
+f=fff; echo _${f:=}
+f=fff; echo _${f=word}
+f=fff; echo _${f:=word}
diff --git a/shell/hush_test/hush-vars/param_expand_default.right b/shell/hush_test/hush-vars/param_expand_default.right
new file mode 100644 (file)
index 0000000..acc7172
--- /dev/null
@@ -0,0 +1,8 @@
+hush: syntax error: unterminated ${name}
+hush: syntax error: unterminated ${name}
+_0 _0
+_ _ _ _word _word
+_aaaa _aaaa _aaaa _aaaa _aaaa
+_ _ _ _word _word
+_ _ _ _ _word
+_fff _fff _fff _fff _fff
diff --git a/shell/hush_test/hush-vars/param_expand_default.tests b/shell/hush_test/hush-vars/param_expand_default.tests
new file mode 100755 (executable)
index 0000000..1ea0517
--- /dev/null
@@ -0,0 +1,22 @@
+# first try some invalid patterns (do in subshell due to parsing error)
+"$THIS_SH" -c 'echo ${-}'
+"$THIS_SH" -c 'echo ${:-}'
+
+# now some funky ones
+echo _${#-} _${#:-}
+
+# now some valid ones
+set --
+echo _$1 _${1-} _${1:-} _${1-word} _${1:-word}
+
+set -- aaaa
+echo _$1 _${1-} _${1:-} _${1-word} _${1:-word}
+
+unset f
+echo _$f _${f-} _${f:-} _${f-word} _${f:-word}
+
+f=
+echo _$f _${f-} _${f:-} _${f-word} _${f:-word}
+
+f=fff
+echo _$f _${f-} _${f:-} _${f-word} _${f:-word}
diff --git a/shell/hush_test/hush-vars/param_expand_indicate_error.right b/shell/hush_test/hush-vars/param_expand_indicate_error.right
new file mode 100644 (file)
index 0000000..590bb20
--- /dev/null
@@ -0,0 +1,28 @@
+hush: syntax error: unterminated ${name}
+0
+0
+_
+hush: 1: parameter null or not set
+hush: 1: parameter null or not set
+hush: 1: message1
+hush: 1: message1
+_aaaa
+_aaaa
+_aaaa
+_aaaa
+_aaaa
+_
+hush: f: parameter null or not set
+hush: f: parameter null or not set
+hush: f: message3
+hush: f: message3
+_
+_
+hush: f: parameter null or not set
+_
+hush: f: message4
+_fff
+_fff
+_fff
+_fff
+_fff
diff --git a/shell/hush_test/hush-vars/param_expand_indicate_error.tests b/shell/hush_test/hush-vars/param_expand_indicate_error.tests
new file mode 100755 (executable)
index 0000000..bccba3e
--- /dev/null
@@ -0,0 +1,40 @@
+# do all of these in subshells since it's supposed to error out
+
+# first try some invalid patterns
+#"$THIS_SH" -c 'echo ${?}' -- this is valid as it's the same as $?
+"$THIS_SH" -c 'echo ${:?}'
+
+# then some funky ones
+"$THIS_SH" -c 'echo ${#?}'
+"$THIS_SH" -c 'echo ${#:?}'
+
+# now some valid ones
+"$THIS_SH" -c 'set --; echo _$1'
+"$THIS_SH" -c 'set --; echo _${1?}'
+"$THIS_SH" -c 'set --; echo _${1:?}'
+"$THIS_SH" -c 'set --; echo _${1?message1}'
+"$THIS_SH" -c 'set --; echo _${1:?message1}'
+
+"$THIS_SH" -c 'set -- aaaa; echo _$1'
+"$THIS_SH" -c 'set -- aaaa; echo _${1?}'
+"$THIS_SH" -c 'set -- aaaa; echo _${1:?}'
+"$THIS_SH" -c 'set -- aaaa; echo _${1?word}'
+"$THIS_SH" -c 'set -- aaaa; echo _${1:?word}'
+
+"$THIS_SH" -c 'unset f; echo _$f'
+"$THIS_SH" -c 'unset f; echo _${f?}'
+"$THIS_SH" -c 'unset f; echo _${f:?}'
+"$THIS_SH" -c 'unset f; echo _${f?message3}'
+"$THIS_SH" -c 'unset f; echo _${f:?message3}'
+
+"$THIS_SH" -c 'f=; echo _$f'
+"$THIS_SH" -c 'f=; echo _${f?}'
+"$THIS_SH" -c 'f=; echo _${f:?}'
+"$THIS_SH" -c 'f=; echo _${f?word}'
+"$THIS_SH" -c 'f=; echo _${f:?message4}'
+
+"$THIS_SH" -c 'f=fff; echo _$f'
+"$THIS_SH" -c 'f=fff; echo _${f?}'
+"$THIS_SH" -c 'f=fff; echo _${f:?}'
+"$THIS_SH" -c 'f=fff; echo _${f?word}'
+"$THIS_SH" -c 'f=fff; echo _${f:?word}'
diff --git a/shell/hush_test/hush-vars/param_expand_len.right b/shell/hush_test/hush-vars/param_expand_len.right
new file mode 100644 (file)
index 0000000..2d633a1
--- /dev/null
@@ -0,0 +1,4 @@
+0 0
+4 4
+4 3 2 1 0 0
+0 3 0
diff --git a/shell/hush_test/hush-vars/param_expand_len.tests b/shell/hush_test/hush-vars/param_expand_len.tests
new file mode 100755 (executable)
index 0000000..90f47d2
--- /dev/null
@@ -0,0 +1,12 @@
+# make sure len parsing doesnt break arg count
+set --
+echo $# ${#}
+set -- aaaa bbb cc d
+echo $# ${#}
+
+echo ${#1} ${#2} ${#3} ${#4} ${#5} ${#6}
+
+unset e
+f=abc
+g=
+echo ${#e} ${#f} ${#g}
diff --git a/shell/hush_test/hush-vars/param_glob.right b/shell/hush_test/hush-vars/param_glob.right
new file mode 100644 (file)
index 0000000..bdee8fe
--- /dev/null
@@ -0,0 +1,4 @@
+param_glob.tests
+param_glob.tests
+param_glob.t*
+param_glob.t*
diff --git a/shell/hush_test/hush-vars/param_glob.tests b/shell/hush_test/hush-vars/param_glob.tests
new file mode 100755 (executable)
index 0000000..801d58e
--- /dev/null
@@ -0,0 +1,10 @@
+if test $# = 0; then
+    #BUG in builtin_exec! will glob param!
+    #exec "$THIS_SH" "$0" 'param_glob.t*'
+    "$THIS_SH" "$0" 'param_glob.t*'
+    exit
+fi
+echo $*
+echo $@
+echo "$*"
+echo "$@"
diff --git a/shell/hush_test/hush-vars/param_subshell.right b/shell/hush_test/hush-vars/param_subshell.right
new file mode 100644 (file)
index 0000000..f3c3767
--- /dev/null
@@ -0,0 +1,7 @@
+1=1
+2=2
+3=3
+4=4
+5=5
+6=6
+7=7
diff --git a/shell/hush_test/hush-vars/param_subshell.tests b/shell/hush_test/hush-vars/param_subshell.tests
new file mode 100755 (executable)
index 0000000..27fdc5b
--- /dev/null
@@ -0,0 +1,15 @@
+if test $# = 0; then
+    "$THIS_SH" "$0" 1 2 3 4 5 6 7 8 9
+    exit
+fi
+echo 1=$1
+{ echo 2=$2; }
+{ echo 3=$3; } &
+# cant use usleep as it isnt standard in $PATH --
+# we fail when testing busybox compiled solely as "hush"
+wait
+( echo 4=$4 )
+( echo 5=$5 ) &
+wait
+true | echo 6=$6 | cat
+true | { echo 7=$7; } | cat
diff --git a/shell/hush_test/hush-vars/star.right b/shell/hush_test/hush-vars/star.right
new file mode 100644 (file)
index 0000000..0ecc55b
--- /dev/null
@@ -0,0 +1,6 @@
+.1.
+.abc.
+.d.
+.e.
+.f.
+.1 abc d e f.
diff --git a/shell/hush_test/hush-vars/star.tests b/shell/hush_test/hush-vars/star.tests
new file mode 100755 (executable)
index 0000000..5554c40
--- /dev/null
@@ -0,0 +1,8 @@
+if test $# = 0; then
+    exec "$THIS_SH" star.tests 1 abc 'd e f'
+fi
+# 'd e f' should be split into 3 separate args:
+for a in $*; do echo ".$a."; done
+
+# must produce .1 abc d e f.
+for a in "$*"; do echo ".$a."; done
diff --git a/shell/hush_test/hush-vars/unset.right b/shell/hush_test/hush-vars/unset.right
new file mode 100644 (file)
index 0000000..8dea7c4
--- /dev/null
@@ -0,0 +1,19 @@
+hush: unset: -: invalid option
+1
+hush: unset: -m: invalid option
+1
+0
+___
+0 f g
+0 g
+0
+___
+0 f g
+0
+0 f g
+0
+___
+hush: HUSH_VERSION: readonly variable
+1 f g
+hush: HUSH_VERSION: readonly variable
+1
diff --git a/shell/hush_test/hush-vars/unset.tests b/shell/hush_test/hush-vars/unset.tests
new file mode 100755 (executable)
index 0000000..f59ce59
--- /dev/null
@@ -0,0 +1,36 @@
+# check invalid options are rejected
+unset -
+echo $?
+unset -m a b c
+echo $?
+
+# check funky usage
+unset
+echo $?
+
+# check normal usage
+echo ___
+f=f g=g
+echo $? $f $g
+unset f
+echo $? $f $g
+unset g
+echo $? $f $g
+
+echo ___
+f=f g=g
+echo $? $f $g
+unset f g
+echo $? $f $g
+f=f g=g
+echo $? $f $g
+unset -v f g
+echo $? $f $g
+
+# check read only vars
+echo ___
+f=f g=g
+unset HUSH_VERSION
+echo $? $f $g
+unset f HUSH_VERSION g
+echo $? $f $g
diff --git a/shell/hush_test/hush-vars/var1.right b/shell/hush_test/hush-vars/var1.right
new file mode 100644 (file)
index 0000000..194e7db
--- /dev/null
@@ -0,0 +1,4 @@
+http://busybox.net
+http://busybox.net_abc
+1 1
+1 1
diff --git a/shell/hush_test/hush-vars/var1.tests b/shell/hush_test/hush-vars/var1.tests
new file mode 100755 (executable)
index 0000000..48a6782
--- /dev/null
@@ -0,0 +1,9 @@
+URL=http://busybox.net
+
+echo $URL
+echo ${URL}_abc
+
+true
+false; echo $? ${?}
+true
+{ false; echo $? ${?}; }
diff --git a/shell/hush_test/hush-vars/var2.right b/shell/hush_test/hush-vars/var2.right
new file mode 100644 (file)
index 0000000..40bf4bf
--- /dev/null
@@ -0,0 +1,2 @@
+http://busybox.net
+http://busybox.net_abc
diff --git a/shell/hush_test/hush-vars/var2.tests b/shell/hush_test/hush-vars/var2.tests
new file mode 100755 (executable)
index 0000000..1292410
--- /dev/null
@@ -0,0 +1,4 @@
+_1=http://busybox.net
+
+echo $_1
+echo ${_1}_abc
diff --git a/shell/hush_test/hush-vars/var3.right b/shell/hush_test/hush-vars/var3.right
new file mode 100644 (file)
index 0000000..5e28d2f
--- /dev/null
@@ -0,0 +1,2 @@
+hush: syntax error: unterminated ${name}
+hush: syntax error: unterminated ${name}
diff --git a/shell/hush_test/hush-vars/var3.tests b/shell/hush_test/hush-vars/var3.tests
new file mode 100755 (executable)
index 0000000..aea36d6
--- /dev/null
@@ -0,0 +1,4 @@
+# reject invalid vars
+"$THIS_SH" -c 'echo ${1q}'
+"$THIS_SH" -c 'echo ${&}'
+#"$THIS_SH" -c 'echo ${$}' -- this is valid as it's the same as $$
diff --git a/shell/hush_test/hush-vars/var_expand_in_assign.right b/shell/hush_test/hush-vars/var_expand_in_assign.right
new file mode 100644 (file)
index 0000000..352210d
--- /dev/null
@@ -0,0 +1,5 @@
+. .
+.abc d e.
+.abc d e.
+.abc d e.
+.abc d e.
diff --git a/shell/hush_test/hush-vars/var_expand_in_assign.tests b/shell/hush_test/hush-vars/var_expand_in_assign.tests
new file mode 100755 (executable)
index 0000000..18cdc74
--- /dev/null
@@ -0,0 +1,15 @@
+if test $# = 0; then
+    exec "$THIS_SH" "$0" abc "d e"
+fi
+
+space=' '
+echo .$space.
+
+a=$*
+echo .$a.
+a=$@
+echo .$a.
+a="$*"
+echo .$a.
+a="$@"
+echo .$a.
diff --git a/shell/hush_test/hush-vars/var_expand_in_redir.right b/shell/hush_test/hush-vars/var_expand_in_redir.right
new file mode 100644 (file)
index 0000000..423299c
--- /dev/null
@@ -0,0 +1,3 @@
+TEST1
+TEST2
+TEST3
diff --git a/shell/hush_test/hush-vars/var_expand_in_redir.tests b/shell/hush_test/hush-vars/var_expand_in_redir.tests
new file mode 100755 (executable)
index 0000000..bda6bdd
--- /dev/null
@@ -0,0 +1,13 @@
+if test $# = 0; then
+    exec "$THIS_SH" "$0" abc "d e"
+fi
+
+echo TEST1 >"$1.out"
+echo TEST2 >"$2.out"
+# bash says: "$@.out": ambiguous redirect
+# ash handles it as if it is '$*' - we do the same
+echo TEST3 >"$@.out"
+
+cat abc.out "d e.out" "abc d e.out"
+
+rm abc.out "d e.out" "abc d e.out"
diff --git a/shell/hush_test/hush-vars/var_in_pipes.right b/shell/hush_test/hush-vars/var_in_pipes.right
new file mode 100644 (file)
index 0000000..faf65be
--- /dev/null
@@ -0,0 +1,6 @@
+b=1
+b=2
+b=3
+b=4
+b=5
+b=6
diff --git a/shell/hush_test/hush-vars/var_in_pipes.tests b/shell/hush_test/hush-vars/var_in_pipes.tests
new file mode 100755 (executable)
index 0000000..3f8cd27
--- /dev/null
@@ -0,0 +1,7 @@
+b=1 env | grep ^b=
+true | b=2 env | grep ^b=
+a=1 true | b=3 env | grep ^b=
+
+(b=4 env) | grep ^b=
+(true | b=5 env) | grep ^b=
+(a=1 true | b=6 env) | grep ^b=
diff --git a/shell/hush_test/hush-vars/var_leaks.right b/shell/hush_test/hush-vars/var_leaks.right
new file mode 100644 (file)
index 0000000..d86bac9
--- /dev/null
@@ -0,0 +1 @@
+OK
diff --git a/shell/hush_test/hush-vars/var_leaks.tests b/shell/hush_test/hush-vars/var_leaks.tests
new file mode 100755 (executable)
index 0000000..27c8c65
--- /dev/null
@@ -0,0 +1,14 @@
+# external program
+a=b /bin/true
+env | grep ^a=
+
+# builtin
+a=b true
+env | grep ^a=
+
+# exec with redirection only
+# in bash, this leaks!
+a=b exec 1>&1
+env | grep ^a=
+
+echo OK
diff --git a/shell/hush_test/hush-vars/var_posix1.right b/shell/hush_test/hush-vars/var_posix1.right
new file mode 100644 (file)
index 0000000..373b16c
--- /dev/null
@@ -0,0 +1,33 @@
+Empty:
+Empty:
+Empty:
+Empty:
+Empty:
+Empty:
+Empty:
+Empty:
+Empty:
+Empty:
+Empty:
+Empty:
+Empty:
+Empty:
+Empty:
+Empty:
+abcdcd
+abcdcd
+abcdcd
+cdcd
+babcdcd
+babcdcd
+ababcdcd
+Empty:
+ababcd
+ababcd
+ababcd
+abab
+ababcdc
+ababcdc
+ababcdcd
+Empty:
+end
diff --git a/shell/hush_test/hush-vars/var_posix1.tests b/shell/hush_test/hush-vars/var_posix1.tests
new file mode 100755 (executable)
index 0000000..0ce531d
--- /dev/null
@@ -0,0 +1,43 @@
+unset var
+
+echo Empty:${var#}
+echo Empty:${var##}
+echo Empty:${var#*}
+echo Empty:${var##*}
+echo Empty:${var%}
+echo Empty:${var%%}
+echo Empty:${var%*}
+echo Empty:${var%%*}
+
+var=
+
+echo Empty:${var#}
+echo Empty:${var##}
+echo Empty:${var#*}
+echo Empty:${var##*}
+echo Empty:${var%}
+echo Empty:${var%%}
+echo Empty:${var%*}
+echo Empty:${var%%*}
+
+var=ababcdcd
+
+echo ${var#ab}
+echo ${var##ab}
+echo ${var#a*b}
+echo ${var##a*b}
+echo ${var#?}
+echo ${var##?}
+echo ${var#*}
+echo Empty:${var##*}
+
+echo ${var%cd}
+echo ${var%%cd}
+echo ${var%c*d}
+echo ${var%%c*d}
+echo ${var%?}
+echo ${var%%?}
+echo ${var%*}
+echo Empty:${var%%*}
+
+echo end
diff --git a/shell/hush_test/hush-vars/var_preserved.right b/shell/hush_test/hush-vars/var_preserved.right
new file mode 100644 (file)
index 0000000..2a9917c
--- /dev/null
@@ -0,0 +1,4 @@
+a=b
+a=b
+a=b
+OK
diff --git a/shell/hush_test/hush-vars/var_preserved.tests b/shell/hush_test/hush-vars/var_preserved.tests
new file mode 100755 (executable)
index 0000000..1bddd87
--- /dev/null
@@ -0,0 +1,16 @@
+export a=b
+
+# external program
+a=c /bin/true
+env | grep ^a=
+
+# builtin
+a=d true
+env | grep ^a=
+
+# exec with redirection only
+# in bash, this leaks!
+a=e exec 1>&1
+env | grep ^a=
+
+echo OK
diff --git a/shell/hush_test/hush-vars/var_subst_in_for.right b/shell/hush_test/hush-vars/var_subst_in_for.right
new file mode 100644 (file)
index 0000000..c8aca1c
--- /dev/null
@@ -0,0 +1,40 @@
+Testing: in x y z
+.x.
+.y.
+.z.
+Testing: in u $empty v
+.u.
+.v.
+Testing: in u " $empty" v
+.u.
+. .
+.v.
+Testing: in u $empty $empty$a v
+.u.
+.a.
+.v.
+Testing: in $a_b
+.a.
+.b.
+Testing: in $*
+.abc.
+.d.
+.e.
+Testing: in $@
+.abc.
+.d.
+.e.
+Testing: in -$*-
+.-abc.
+.d.
+.e-.
+Testing: in -$@-
+.-abc.
+.d.
+.e-.
+Testing: in $a_b -$a_b-
+.a.
+.b.
+.-a.
+.b-.
+Finished
diff --git a/shell/hush_test/hush-vars/var_subst_in_for.tests b/shell/hush_test/hush-vars/var_subst_in_for.tests
new file mode 100755 (executable)
index 0000000..433c606
--- /dev/null
@@ -0,0 +1,40 @@
+if test $# = 0; then
+    exec "$THIS_SH" "$0" abc "d e"
+fi
+
+echo 'Testing: in x y z'
+for a in x y z; do echo ".$a."; done
+
+echo 'Testing: in u $empty v'
+empty=''
+for a in u $empty v; do echo ".$a."; done
+
+echo 'Testing: in u " $empty" v'
+empty=''
+for a in u " $empty" v; do echo ".$a."; done
+
+echo 'Testing: in u $empty $empty$a v'
+a='a'
+for a in u $empty $empty$a v; do echo ".$a."; done
+
+echo 'Testing: in $a_b'
+a_b='a b'
+for a in $a_b; do echo ".$a."; done
+
+echo 'Testing: in $*'
+for a in $*; do echo ".$a."; done
+
+echo 'Testing: in $@'
+for a in $@; do echo ".$a."; done
+
+echo 'Testing: in -$*-'
+for a in -$*-; do echo ".$a."; done
+
+echo 'Testing: in -$@-'
+for a in -$@-; do echo ".$a."; done
+
+echo 'Testing: in $a_b -$a_b-'
+a_b='a b'
+for a in $a_b -$a_b-; do echo ".$a."; done
+
+echo Finished
diff --git a/shell/hush_test/hush-z_slow/leak_all1.right b/shell/hush_test/hush-z_slow/leak_all1.right
new file mode 100644 (file)
index 0000000..c6f0334
--- /dev/null
@@ -0,0 +1,3 @@
+Warm up
+Measuring memory leak...
+Ok
diff --git a/shell/hush_test/hush-z_slow/leak_all1.tests b/shell/hush_test/hush-z_slow/leak_all1.tests
new file mode 100755 (executable)
index 0000000..21fdb0d
--- /dev/null
@@ -0,0 +1,145 @@
+# "Check many leaks" test #1
+# Cramming all kinds of weird commands in here.
+# As you find leaks, please create separate, small test
+# for each leak.
+# Narrowing down the leak using this large test may be difficult.
+# It is intended to be a blanket "is everything ok?" test
+
+echo "Warm up"
+i=1
+l=1
+t=1
+export t
+while test $i != 99; do
+    t=value1_$i; t=value2_$i true; t=value3_$i /bin/true; t=value4_$i exec 1>&1
+    { t=value3_$i /bin/true; } </dev/null
+    if true; t=valueA_$i false >>/dev/null; true; then
+       : << HERE >/dev/null; true <<HERE
+Hello builtin :
+HERE
+Hello $i true
+HERE
+    elif false; then
+       true; echo Doesnt run
+    else
+       { true; }; echo Doesnt run too >>/foo/bar
+    fi
+    { : /bin/*; }
+    unset var
+    echo >/dev/null ${var#}
+    echo >/dev/null ${var##}
+    echo >/dev/null ${var#*}
+    echo >/dev/null ${var##*}
+    echo >/dev/null ${var%}
+    echo >/dev/null ${var%%}
+    echo >/dev/null ${var%*}
+    echo >/dev/null ${var%%*}
+    var=
+    echo >/dev/null ${var#}
+    echo >/dev/null ${var##}
+    echo >/dev/null ${var#*}
+    echo >/dev/null ${var##*}
+    echo >/dev/null ${var%}
+    echo >/dev/null ${var%%}
+    echo >/dev/null ${var%*}
+    echo >/dev/null ${var%%*}
+    var=ababcdcd
+    echo >/dev/null ${var#ab}
+    echo >/dev/null ${var##ab}
+    echo >/dev/null ${var#a*b}
+    echo >/dev/null ${var##a*b}
+    echo >/dev/null ${var#?}
+    echo >/dev/null ${var##?}
+    echo >/dev/null ${var#*}
+    echo >/dev/null ${var##*}
+    echo >/dev/null ${var%cd}
+    echo >/dev/null ${var%%cd}
+    echo >/dev/null ${var%c*d}
+    echo >/dev/null ${var%%c*d}
+    echo >/dev/null ${var%?}
+    echo >/dev/null ${var%%?}
+    echo >/dev/null ${var%*}
+    echo >/dev/null ${var%%*}
+    set -- par1_$i par2_$i par3_$i par4_$i
+    trap "echo trap$i" WINCH
+    f() { true; true; true; true; true; true; true; true; }
+    f() { true; true; true; true; true; true; true; true; echo $1; }
+    f >/dev/null
+    : $((i++))
+done
+
+memleak
+
+echo "Measuring memory leak..."
+# Please copy the entire block from above verbatim
+i=1
+l=1
+t=1
+export t
+while test $i != 99; do
+    t=value1_$i; t=value2_$i true; t=value3_$i /bin/true; t=value4_$i exec 1>&1
+    { t=value3_$i /bin/true; } </dev/null
+    if true; t=valueA_$i false >>/dev/null; true; then
+       : << HERE >/dev/null; true <<HERE
+Hello builtin :
+HERE
+Hello $i true
+HERE
+    elif false; then
+       true; echo Doesnt run
+    else
+       { true; }; echo Doesnt run too >>/foo/bar
+    fi
+    { : /bin/*; }
+    unset var
+    echo >/dev/null ${var#}
+    echo >/dev/null ${var##}
+    echo >/dev/null ${var#*}
+    echo >/dev/null ${var##*}
+    echo >/dev/null ${var%}
+    echo >/dev/null ${var%%}
+    echo >/dev/null ${var%*}
+    echo >/dev/null ${var%%*}
+    var=
+    echo >/dev/null ${var#}
+    echo >/dev/null ${var##}
+    echo >/dev/null ${var#*}
+    echo >/dev/null ${var##*}
+    echo >/dev/null ${var%}
+    echo >/dev/null ${var%%}
+    echo >/dev/null ${var%*}
+    echo >/dev/null ${var%%*}
+    var=ababcdcd
+    echo >/dev/null ${var#ab}
+    echo >/dev/null ${var##ab}
+    echo >/dev/null ${var#a*b}
+    echo >/dev/null ${var##a*b}
+    echo >/dev/null ${var#?}
+    echo >/dev/null ${var##?}
+    echo >/dev/null ${var#*}
+    echo >/dev/null ${var##*}
+    echo >/dev/null ${var%cd}
+    echo >/dev/null ${var%%cd}
+    echo >/dev/null ${var%c*d}
+    echo >/dev/null ${var%%c*d}
+    echo >/dev/null ${var%?}
+    echo >/dev/null ${var%%?}
+    echo >/dev/null ${var%*}
+    echo >/dev/null ${var%%*}
+    set -- par1_$i par2_$i par3_$i par4_$i
+    trap "echo trap$i" WINCH
+    f() { true; true; true; true; true; true; true; true; }
+    f() { true; true; true; true; true; true; true; true; echo $1; }
+    f >/dev/null
+    : $((i++))
+done
+
+
+memleak
+kb=$?
+# Observed some variability, bumped to 12k
+if test $kb -le 12; then
+    echo Ok #$kb
+else
+    echo "Bad: $kb kb (or more) leaked"
+fi
diff --git a/shell/hush_test/hush-z_slow/leak_all2.right b/shell/hush_test/hush-z_slow/leak_all2.right
new file mode 100644 (file)
index 0000000..c6f0334
--- /dev/null
@@ -0,0 +1,3 @@
+Warm up
+Measuring memory leak...
+Ok
diff --git a/shell/hush_test/hush-z_slow/leak_all2.tests b/shell/hush_test/hush-z_slow/leak_all2.tests
new file mode 100755 (executable)
index 0000000..d51ea80
--- /dev/null
@@ -0,0 +1,93 @@
+# "Check many leaks" test #2
+# Cramming all kinds of weird commands in here.
+# As you find leaks, please create separate, small test
+# for each leak.
+# Narrowing down the leak using this large test may be difficult.
+# It is intended to be a blanket "is everything ok?" test
+
+echo "Warm up"
+local_var="local val"
+export dev_null="/dev/null"
+>$dev_null
+echo hi1 $local_var `echo ho` >>/dev/null
+echo hi2 $local_var </dev/null | echo 2>&- | cat 1<>/dev/null
+{ echo hi4 $local_var `echo ho` 1<>/dev/null; }
+( echo hi4 $local_var `echo ho` 1<>/dev/null )
+if echo $local_var; false
+    then echo not run
+    elif false <$dev_null
+    then none
+    else cat 0<>$dev_null 1<>"$dev_null"
+fi >>/dev/null
+{
+    if echo $local_var; then cat <<HERE
+Hi cat
+HERE
+    fi >>/dev/null
+} 1<>/dev/null
+while { echo $dev_null >>$dev_null; }; do cat <"$dev_null"; break; done
+( until { echo $dev_null >>$dev_null | false; }; do cat <"$dev_null"; break; done ) <$dev_null
+f() { echo $1; }
+f >/dev/null
+
+memleak
+
+echo "Measuring memory leak..."
+# Please copy the entire block from above verbatim
+local_var="local val"
+export dev_null="/dev/null"
+>$dev_null
+echo hi1 $local_var `echo ho` >>/dev/null
+echo hi2 $local_var </dev/null | echo 2>&- | cat 1<>/dev/null
+{ echo hi4 $local_var `echo ho` 1<>/dev/null; }
+( echo hi4 $local_var `echo ho` 1<>/dev/null )
+if echo $local_var; false
+    then echo not run
+    elif false <$dev_null
+    then none
+    else cat 0<>$dev_null 1<>"$dev_null"
+fi >>/dev/null
+{
+    if echo $local_var; then cat <<HERE
+Hi cat
+HERE
+    fi >>/dev/null
+} 1<>/dev/null
+while { echo $dev_null >>$dev_null; }; do cat <"$dev_null"; break; done
+( until { echo $dev_null >>$dev_null | false; }; do cat <"$dev_null"; break; done ) <$dev_null
+f() { echo $1; }
+f >/dev/null
+
+# And same again
+
+local_var="local val"
+export dev_null="/dev/null"
+>$dev_null
+echo hi1 $local_var `echo ho` >>/dev/null
+echo hi2 $local_var </dev/null | echo 2>&- | cat 1<>/dev/null
+{ echo hi4 $local_var `echo ho` 1<>/dev/null; }
+( echo hi4 $local_var `echo ho` 1<>/dev/null )
+if echo $local_var; false
+    then echo not run
+    elif false <$dev_null
+    then none
+    else cat 0<>$dev_null 1<>"$dev_null"
+fi >>/dev/null
+{
+    if echo $local_var; then cat <<HERE
+Hi cat
+HERE
+    fi >>/dev/null
+} 1<>/dev/null
+while { echo $dev_null >>$dev_null; }; do cat <"$dev_null"; break; done
+( until { echo $dev_null >>$dev_null | false; }; do cat <"$dev_null"; break; done ) <$dev_null
+f() { echo $1; }
+f >/dev/null
+
+memleak
+kb=$?
+if test $kb -le 4; then
+    echo Ok #$kb
+else
+    echo "Bad: $kb kb (or more) leaked"
+fi
diff --git a/shell/hush_test/hush-z_slow/leak_heredoc1.right b/shell/hush_test/hush-z_slow/leak_heredoc1.right
new file mode 100644 (file)
index 0000000..c6f0334
--- /dev/null
@@ -0,0 +1,3 @@
+Warm up
+Measuring memory leak...
+Ok
diff --git a/shell/hush_test/hush-z_slow/leak_heredoc1.tests b/shell/hush_test/hush-z_slow/leak_heredoc1.tests
new file mode 100755 (executable)
index 0000000..26cbb28
--- /dev/null
@@ -0,0 +1,34 @@
+echo "Warm up"
+i=1
+while test $i != 99; do
+    : <<HERE
+Hello $i `echo builtin_$i`
+HERE
+    : $((i++))
+done
+
+memleak
+
+echo "Measuring memory leak..."
+i=1
+while test $i != 99; do
+    : <<HERE
+Hello $i `echo builtin_$i`
+HERE
+    : $((i++))
+done
+i=1
+while test $i != 99; do
+    : <<HERE
+Hello $i `echo builtin_$i`
+HERE
+    : $((i++))
+done
+
+memleak
+kb=$?
+if test $kb -le 4; then
+    echo Ok #$kb
+else
+    echo "Bad: $kb kb (or more) leaked"
+fi
diff --git a/shell/hush_test/hush-z_slow/leak_var.right b/shell/hush_test/hush-z_slow/leak_var.right
new file mode 100644 (file)
index 0000000..1d4d6ff
--- /dev/null
@@ -0,0 +1,2 @@
+Measuring memory leak...
+Ok
diff --git a/shell/hush_test/hush-z_slow/leak_var.tests b/shell/hush_test/hush-z_slow/leak_var.tests
new file mode 100755 (executable)
index 0000000..41c09e4
--- /dev/null
@@ -0,0 +1,47 @@
+echo "Measuring memory leak..."
+i=1
+while test $i != X; do
+    unset t
+    t=111111111111111111111111111111111111111111111111111111111111111111111111
+    export t
+    unset t
+    t=111111111111111111111111111111111111111111111111111111111111111111111111
+    export t
+    unset t
+    t=111111111111111111111111111111111111111111111111111111111111111111111111
+    export t
+    unset t
+    t=111111111111111111111111111111111111111111111111111111111111111111111111
+    export t
+    unset t
+    t=111111111111111111111111111111111111111111111111111111111111111111111111
+    export t
+    i=1$i
+    if test $i = 1111111111111111111111111111111111111111111111; then i=2; fi
+    if test $i = 1111111111111111111111111111111111111111111112; then i=3; fi
+    if test $i = 1111111111111111111111111111111111111111111113; then i=4; fi
+    if test $i = 1111111111111111111111111111111111111111111114; then i=5; fi
+    if test $i = 1111111111111111111111111111111111111111111115; then i=6; fi
+    if test $i = 1111111111111111111111111111111111111111111116; then i=7; fi
+    if test $i = 1111111111111111111111111111111111111111111117; then i=8; fi
+    if test $i = 1111111111111111111111111111111111111111111118; then i=9; fi
+    if test $i = 1111111111111111111111111111111111111111111119; then i=a; fi
+    if test $i = 111111111111111111111111111111111111111111111a; then i=b; fi
+    if test $i = 111111111111111111111111111111111111111111111b; then i=c; fi
+    if test $i = 111111111111111111111111111111111111111111111c; then i=d; fi
+    if test $i = 111111111111111111111111111111111111111111111d; then i=e; fi
+    if test $i = 111111111111111111111111111111111111111111111e; then i=f; fi
+    if test $i = 111111111111111111111111111111111111111111111f; then i=g; fi
+    if test $i = 111111111111111111111111111111111111111111111g; then i=h; fi
+    if test $i = 111111111111111111111111111111111111111111111h; then i=i; fi
+    if test $i = 111111111111111111111111111111111111111111111i; then i=j; fi
+    if test $i = 111111111111111111111111111111111111111111111j; then i=X; fi
+    memleak
+done
+memleak
+kb=$?
+if test $kb -le 4; then
+    echo Ok
+else
+    echo "Bad: $kb kb (or more) leaked"
+fi
diff --git a/shell/hush_test/hush-z_slow/leak_var2.right b/shell/hush_test/hush-z_slow/leak_var2.right
new file mode 100644 (file)
index 0000000..c6f0334
--- /dev/null
@@ -0,0 +1,3 @@
+Warm up
+Measuring memory leak...
+Ok
diff --git a/shell/hush_test/hush-z_slow/leak_var2.tests b/shell/hush_test/hush-z_slow/leak_var2.tests
new file mode 100755 (executable)
index 0000000..0ab1315
--- /dev/null
@@ -0,0 +1,40 @@
+t=1
+export t
+
+echo "Warm up"
+i=1
+while test $i != X; do
+    t=111111111111111111111111111111111111111111111111111111111111111111111110$i
+    t=111111111111111111111111111111111111111111111111111111111111111111111111$i true
+    t=111111111111111111111111111111111111111111111111111111111111111111111112$i /bin/true
+    t=111111111111111111111111111111111111111111111111111111111111111111111113$i exec 1>&1
+    i=1$i
+    if test $i = 1111111111111111111111111111111111111111111111; then i=2; fi
+    if test $i = 1111111111111111111111111111111111111111111112; then i=3; fi
+    if test $i = 1111111111111111111111111111111111111111111113; then i=4; fi
+    if test $i = 1111111111111111111111111111111111111111111114; then i=X; fi
+done
+
+memleak
+
+echo "Measuring memory leak..."
+i=1
+while test $i != X; do
+    t=111111111111111111111111111111111111111111111111111111111111111111111110$i
+    t=111111111111111111111111111111111111111111111111111111111111111111111111$i true
+    t=111111111111111111111111111111111111111111111111111111111111111111111112$i /bin/true
+    t=111111111111111111111111111111111111111111111111111111111111111111111113$i exec 1>&1
+    i=1$i
+    if test $i = 1111111111111111111111111111111111111111111111; then i=2; fi
+    if test $i = 1111111111111111111111111111111111111111111112; then i=3; fi
+    if test $i = 1111111111111111111111111111111111111111111113; then i=4; fi
+    if test $i = 1111111111111111111111111111111111111111111114; then i=X; fi
+done
+
+memleak
+kb=$?
+if test $kb -le 4; then
+    echo Ok
+else
+    echo "Bad: $kb kb (or more) leaked"
+fi
diff --git a/shell/hush_test/hush-z_slow/leak_var3.right b/shell/hush_test/hush-z_slow/leak_var3.right
new file mode 100644 (file)
index 0000000..c6f0334
--- /dev/null
@@ -0,0 +1,3 @@
+Warm up
+Measuring memory leak...
+Ok
diff --git a/shell/hush_test/hush-z_slow/leak_var3.tests b/shell/hush_test/hush-z_slow/leak_var3.tests
new file mode 100755 (executable)
index 0000000..9554c42
--- /dev/null
@@ -0,0 +1,41 @@
+# Was seen leaking on NOMMU build
+
+echo "Warm up"
+i=1; t=1; export t
+while test $i != 400; do
+    t=valueA_$i true
+    : $((i++))
+done
+
+memleak
+echo "Measuring memory leak..."
+
+# Please copy the entire block from above verbatim
+i=1; t=1; export t
+while test $i != 400; do
+    t=valueA_$i true
+    : $((i++))
+done
+i=1; t=1; export t
+while test $i != 400; do
+    t=valueA_$i true
+    : $((i++))
+done
+i=1; t=1; export t
+while test $i != 400; do
+    t=valueA_$i true
+    : $((i++))
+done
+i=1; t=1; export t
+while test $i != 400; do
+    t=valueA_$i true
+    : $((i++))
+done
+
+memleak
+kb=$?
+if test $kb -le 4; then
+    echo Ok #$kb
+else
+    echo "Bad: $kb kb (or more) leaked"
+fi
diff --git a/shell/hush_test/run-all b/shell/hush_test/run-all
new file mode 100755 (executable)
index 0000000..57a5c25
--- /dev/null
@@ -0,0 +1,89 @@
+#!/bin/sh
+
+unset LANG LANGUAGE
+unset LC_COLLATE
+unset LC_CTYPE
+unset LC_MONETARY
+unset LC_MESSAGES
+unset LC_NUMERIC
+unset LC_TIME
+unset LC_ALL
+
+test -x hush || {
+    echo "No ./hush - creating a link to ../../busybox"
+    ln -s ../../busybox hush
+}
+if test -e ../../.config ; then
+       eval $(sed -e '/^#/d' -e '/^$/d' -e 's:^:export :' ../../.config)
+fi
+
+PATH="$PWD:$PATH" # for hush and recho/zecho/printenv
+export PATH
+
+THIS_SH="$PWD/hush"
+export THIS_SH
+
+do_test()
+{
+    test -d "$1" || return 0
+    d=${d%/}
+#   echo Running tests in directory "$1"
+    (
+    tret=0
+    cd "$1" || { echo "cannot cd $1!"; exit 1; }
+    for x in run-*; do
+       test -f "$x" || continue
+       case "$x" in
+           "$0"|run-minimal|run-gprof) ;;
+           *.orig|*~) ;;
+           #*) echo $x ; sh $x ;;
+           *)
+           sh "$x" >"../$1-$x.fail" 2>&1 && \
+           { echo "$1/$x: ok"; rm "../$1-$x.fail"; } || echo "$1/$x: fail";
+           ;;
+       esac
+    done
+    # Many bash run-XXX scripts just do this,
+    # no point in duplication it all over the place
+    for x in *.tests; do
+       test -x "$x" || continue
+       name="${x%%.tests}"
+       test -f "$name.right" || continue
+#      echo Running test: "$x"
+       (
+           "$THIS_SH" "./$x" >"$name.xx" 2>&1
+           test $? -eq 77 && rm -f "../$1-$x.fail" && exit 77
+           diff -u "$name.xx" "$name.right" >"../$1-$x.fail" && rm -f "$name.xx" "../$1-$x.fail"
+       )
+       case $? in
+               0)  echo "$1/$x: ok";;
+               77) echo "$1/$x: skip (feature disabled)";;
+               *)  echo "$1/$x: fail"; tret=1;;
+       esac
+    done
+    exit ${tret}
+    )
+}
+
+# Main part of this script
+# Usage: run-all [directories]
+
+ret=0
+
+if [ $# -lt 1 ]; then
+    # All sub directories
+    modules=`ls -d hush-*`
+
+    for module in $modules; do
+       do_test $module || ret=1
+    done
+else
+    while [ $# -ge 1 ]; do
+       if [ -d $1 ]; then
+           do_test $1 || ret=1
+       fi
+       shift
+    done
+fi
+
+exit ${ret}
diff --git a/shell/lash_unused.c b/shell/lash_unused.c
new file mode 100644 (file)
index 0000000..21ea547
--- /dev/null
@@ -0,0 +1,1571 @@
+/* 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 <getopt.h>
+#include <glob.h>
+
+#include "libbb.h"
+
+#define expand_t       glob_t
+
+/* Always enable for the moment... */
+#define CONFIG_LASH_PIPE_N_REDIRECTS
+#define CONFIG_LASH_JOB_CONTROL
+#define ENABLE_LASH_PIPE_N_REDIRECTS 1
+#define ENABLE_LASH_JOB_CONTROL      1
+
+
+enum { MAX_READ = 128 }; /* size of input buffer for 'read' builtin */
+#define JOB_STATUS_FORMAT "[%d] %-22s %.40s\n"
+
+
+#if ENABLE_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)
+
+#if ENABLE_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 */
+#if ENABLE_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 {
+       const char *cmd;   /* name */
+       const 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_bufsiz(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) 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 const 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},
+       /* These were "forked applets", but distinction was nuked */
+       /* Original comment retained: */
+       /* Table of forking built-in functions (things that fork cannot change global
+        * variables in the parent process, such as the current working directory) */
+       {"pwd"   , "Print current directory", builtin_pwd},
+       {"help"  , "List shell built-in commands", builtin_help},
+       /* to do: add ulimit */
+};
+
+
+#define VEC_LAST(v) v[ARRAY_SIZE(v)-1]
+
+
+static int shell_context;  /* Type prompt trigger (PS1 or PS2) */
+
+
+/* Globals that are static to this file */
+static char *cwd;
+static char *local_pending_command;
+static struct jobset job_list = { NULL, NULL };
+static int global_argc;
+static char **global_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 const char *PS1;
+static const 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 UNUSED_PARAM *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
+ */
+
+
+static void update_cwd(void)
+{
+       cwd = xrealloc_getcwd_or_warn(cwd);
+       if (!cwd)
+               cwd = xstrdup(bb_msg_unknown);
+}
+
+/* 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;
+       }
+       update_cwd();
+       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;
+
+       /* 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) {
+                               goto found;
+                       }
+               }
+               bb_error_msg("%s: no current job", child->argv[0]);
+               return EXIT_FAILURE;
+       }
+       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) {
+                       goto found;
+               }
+       }
+       bb_error_msg("%s: %d: no such job", child->argv[0], jobnum);
+       return EXIT_FAILURE;
+ found:
+       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;
+
+       i = kill(- job->pgrp, SIGCONT);
+       if (i < 0) {
+               if (errno == 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 UNUSED_PARAM *dummy)
+{
+       const struct built_in_command *x;
+
+       printf("\n"
+               "Built-in commands:\n"
+               "------------------\n");
+       for (x = bltins; x <= &VEC_LAST(bltins); x++) {
+               if (x->descr == NULL)
+                       continue;
+               printf("%s\t%s\n", x->cmd, x->descr);
+       }
+       bb_putchar('\n');
+       return EXIT_SUCCESS;
+}
+
+/* built-in 'jobs' handler */
+static int builtin_jobs(struct child_prog *child)
+{
+       struct job *job;
+       const 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 UNUSED_PARAM *dummy)
+{
+       update_cwd();
+       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");
+#if ENABLE_FEATURE_EDITING_FANCY_PROMPT
+       if (strncmp(v, "PS1=", 4) == 0)
+               PS1 = getenv("PS1");
+#endif
+
+#if ENABLE_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;
+               s = strdup(string);
+               if (s)
+                       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;
+}
+
+#if ENABLE_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);
+#if ENABLE_LASH_PIPE_N_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
+
+#if ENABLE_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_or_warn(redir->filename, mode);
+               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!) */
+                       return 1;
+               }
+
+               if (openfd != redir->fd) {
+                       if (squirrel && redir->fd < 3) {
+                               squirrel[redir->fd] = dup(redir->fd);
+                               close_on_exec_on(squirrel[redir->fd]);
+                       }
+                       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)
+{
+#if !ENABLE_FEATURE_EDITING_FANCY_PROMPT
+       PS1 = NULL;
+#else
+       PS1 = getenv("PS1");
+       if (PS1 == 0)
+               PS1 = "\\w \\$ ";
+#endif
+}
+
+static inline const char* setup_prompt_string(void)
+{
+#if !ENABLE_FEATURE_EDITING_FANCY_PROMPT
+       /* Set up the prompt */
+       if (shell_context == 0) {
+               char *ns;
+               free((char*)PS1);
+               ns = xmalloc(strlen(cwd)+4);
+               sprintf(ns, "%s %c ", cwd, (geteuid() != 0) ? '$': '#');
+               PS1 = ns;
+               return ns;
+       } else {
+               return PS2;
+       }
+#else
+       return (shell_context == 0)? PS1 : PS2;
+#endif
+}
+
+#if ENABLE_FEATURE_EDITING
+static line_input_t *line_input_state;
+#endif
+
+static int get_command_bufsiz(FILE *source, char *command)
+{
+       const char *prompt_str;
+
+       if (source == NULL) {
+               if (local_pending_command) {
+                       /* a command specified (-c option): return it & mark it done */
+                       strncpy(command, local_pending_command, BUFSIZ);
+                       local_pending_command = NULL;
+                       return 0;
+               }
+               return 1;
+       }
+
+       if (source == stdin) {
+               prompt_str = setup_prompt_string();
+
+#if ENABLE_FEATURE_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)
+               */
+               read_line_input(prompt_str, command, BUFSIZ, line_input_state);
+               return 0;
+#else
+               fputs(prompt_str, stdout);
+#endif
+       }
+
+       if (!fgets(command, BUFSIZ - 2, source)) {
+               if (source == stdin)
+                       bb_putchar('\n');
+               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)
+{
+       static const char out_of_space[] ALIGN1 = "out of space during expansion";
+
+       int total_length = 0, length, i, retval, ix = 0;
+       expand_t expand_result;
+       char *tmpcmd, *cmd, *cmd_copy;
+       char *src, *dst, *var;
+       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(global_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 >= global_argc) {
+                                               var = '\0';
+                                       } else {
+                                               var = global_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 = (char*)"";
+               }
+               {
+                       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;
+#if ENABLE_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;
+#if ENABLE_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;
+
+#if ENABLE_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
+
+#if ENABLE_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)
+{
+       const 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.
+        */
+       /* Check if the command matches any of the forking builtins. */
+       for (x = bltins; x <= &VEC_LAST(bltins); x++) {
+               if (strcmp(child->argv[0], x->cmd) == 0) {
+                       _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_nostrip().  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) {
+               run_applet_and_exit(child->argv[0], 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_simple_perror_msg(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;
+
+#if ENABLE_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 */
+       const struct built_in_command *x;
+       struct child_prog *child;
+
+       nextin = 0;
+       for (i = 0; i < newjob->num_progs; i++) {
+               child = &(newjob->progs[i]);
+
+               nextout = 1;
+               if ((i + 1) < newjob->num_progs) {
+                       xpipe(pipefds);
+                       nextout = pipefds[1];
+               } else if (outpipe[1] != -1) {
+                       nextout = outpipe[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) {
+                       int rcode;
+                       int squirrel[] = {-1, -1, -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 <= &VEC_LAST(bltins); x++) {
+                               if (strcmp(child->argv[0], x->cmd) == 0) {
+                                       setup_redirects(child, squirrel);
+                                       rcode = x->function(child);
+                                       restore_redirects(squirrel);
+                                       return rcode;
+                               }
+                       }
+#if ENABLE_FEATURE_SH_STANDALONE
+                       {
+                               int a = find_applet_by_name(child->argv[i]);
+                               if (a >= 0 && APPLET_IS_NOFORK(a)) {
+                                       setup_redirects(child, squirrel);
+                                       rcode = run_nofork_applet(a, child->argv + i);
+                                       restore_redirects(squirrel);
+                                       return rcode;
+                               }
+                       }
+#endif
+               }
+
+#if BB_MMU
+               child->pid = fork();
+#else
+               child->pid = vfork();
+#endif
+               if (!child->pid) {
+                       /* 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;
+#if ENABLE_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_bufsiz(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 = 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;
+                               }
+                       }
+#if ENABLE_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);
+
+#if ENABLE_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;
+}
+
+#if ENABLE_FEATURE_CLEAN_UP
+static void free_memory(void)
+{
+       free(cwd);
+
+       if (job_list.fg && !job_list.fg->running_progs) {
+               remove_job(&job_list, job_list.fg);
+       }
+}
+#else
+void free_memory(void);
+#endif
+
+#if ENABLE_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) {
+               shell_pgrp = getpgrp();
+               if (status == shell_pgrp) {
+                       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, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int lash_main(int argc, char **argv)
+{
+       unsigned opt;
+       FILE *input = stdin;
+
+       global_argc = argc;
+       global_argv = argv;
+
+#if ENABLE_FEATURE_EDITING
+       line_input_state = new_line_input_t(FOR_SHELL);
+#endif
+
+       /* 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 (global_argv[0] && global_argv[0][0] == '-') {
+               FILE *prof_input;
+               prof_input = fopen_for_read("/etc/profile");
+               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(argv, "+ic:", &local_pending_command);
+#define LASH_OPT_i (1<<0)
+#define LASH_OPT_c (1<<1)
+       if (opt & LASH_OPT_c) {
+               input = NULL;
+               optind++;
+               global_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 (global_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 && global_argv[optind]) {
+               //printf( "optind=%d  argv[optind]='%s'\n", optind, argv[optind]);
+               input = xfopen_for_read(global_argv[optind]);
+               /* be lazy, never mark this closed */
+               llist_add_to(&close_me_list, (void *)(long)fileno(input));
+       }
+
+       /* initialize the cwd -- this is never freed...*/
+       update_cwd();
+
+       if (ENABLE_FEATURE_CLEAN_UP) atexit(free_memory);
+
+       if (ENABLE_FEATURE_EDITING) cmdedit_set_initial_prompt();
+       else PS1 = NULL;
+
+       return busy_loop(input);
+}
diff --git a/shell/match.c b/shell/match.c
new file mode 100644 (file)
index 0000000..47038d6
--- /dev/null
@@ -0,0 +1,140 @@
+/*
+ * ##/%% variable matching code ripped out of ash shell for code sharing
+ *
+ * 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.
+ */
+#ifdef STANDALONE
+# include <stdbool.h>
+# include <stdio.h>
+# include <stdlib.h>
+# include <string.h>
+# include <unistd.h>
+#else
+# include "libbb.h"
+#endif
+#include <fnmatch.h>
+#include "match.h"
+
+#define pmatch(a, b) !fnmatch((a), (b), 0)
+
+char *scanleft(char *string, char *pattern, bool zero)
+{
+       char c;
+       char *loc = string;
+
+       do {
+               int match;
+               const char *s;
+
+               c = *loc;
+               if (zero) {
+                       *loc = '\0';
+                       s = string;
+               } else
+                       s = loc;
+               match = pmatch(pattern, s);
+               *loc = c;
+
+               if (match)
+                       return loc;
+
+               loc++;
+       } while (c);
+
+       return NULL;
+}
+
+char *scanright(char *string, char *pattern, bool zero)
+{
+       char c;
+       char *loc = string + strlen(string);
+
+       while (loc >= string) {
+               int match;
+               const char *s;
+
+               c = *loc;
+               if (zero) {
+                       *loc = '\0';
+                       s = string;
+               } else
+                       s = loc;
+               match = pmatch(pattern, s);
+               *loc = c;
+
+               if (match)
+                       return loc;
+
+               loc--;
+       }
+
+       return NULL;
+}
+
+#ifdef STANDALONE
+int main(int argc, char *argv[])
+{
+       char *string;
+       char *op;
+       char *pattern;
+       bool zero;
+       char *loc;
+
+       int i;
+
+       if (argc == 1) {
+               puts(
+                       "Usage: match <test> [test...]\n\n"
+                       "Where a <test> is the form: <string><op><match>\n"
+                       "This is to test the shell ${var#val} expression type.\n\n"
+                       "e.g. `match 'abc#a*'` -> bc"
+               );
+               return 1;
+       }
+
+       for (i = 1; i < argc; ++i) {
+               size_t off;
+               scan_t scan;
+
+               printf("'%s': ", argv[i]);
+
+               string = strdup(argv[i]);
+               off = strcspn(string, "#%");
+               if (!off) {
+                       printf("invalid format\n");
+                       free(string);
+                       continue;
+               }
+               op = string + off;
+               scan = pick_scan(op[0], op[1], &zero);
+               pattern = op + 1;
+               if (op[0] == op[1])
+                       op[1] = '\0', ++pattern;
+               op[0] = '\0';
+
+               loc = scan(string, pattern, zero);
+
+               if (zero) {
+                       printf("'%s'\n", loc);
+               } else {
+                       *loc = '\0';
+                       printf("'%s'\n", string);
+               }
+
+               free(string);
+       }
+
+       return 0;
+}
+#endif
diff --git a/shell/match.h b/shell/match.h
new file mode 100644 (file)
index 0000000..3fc4de3
--- /dev/null
@@ -0,0 +1,26 @@
+/* match.h - interface to shell ##/%% matching code */
+
+PUSH_AND_SET_FUNCTION_VISIBILITY_TO_HIDDEN
+
+typedef char *(*scan_t)(char *string, char *match, bool zero);
+
+char *scanleft(char *string, char *match, bool zero);
+char *scanright(char *string, char *match, bool zero);
+
+static inline scan_t pick_scan(char op1, char op2, bool *zero)
+{
+       /* #  - scanleft
+        * ## - scanright
+        * %  - scanright
+        * %% - scanleft
+        */
+       if (op1 == '#') {
+               *zero = true;
+               return op1 == op2 ? scanright : scanleft;
+       } else {
+               *zero = false;
+               return op1 == op2 ? scanleft : scanright;
+       }
+}
+
+POP_SAVED_FUNCTION_VISIBILITY
diff --git a/shell/math.c b/shell/math.c
new file mode 100644 (file)
index 0000000..cc298bd
--- /dev/null
@@ -0,0 +1,700 @@
+/*
+ * arithmetic code ripped out of ash shell for code sharing
+ *
+ * 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.
+ */
+#include "libbb.h"
+#include "math.h"
+
+#define a_e_h_t arith_eval_hooks_t
+#define lookupvar (math_hooks->lookupvar)
+#define setvar (math_hooks->setvar)
+#define endofname (math_hooks->endofname)
+
+/* 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 {
+       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_t {
+       const char *var;
+       struct chk_var_recursive_looped_t *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, a_e_h_t *math_hooks)
+{
+       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, math_hooks);
+                       /* restore previous ptr after recursiving */
+                       prev_chk_var_recursive = cur;
+                       return errcode;
+               }
+               /* 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, a_e_h_t *math_hooks)
+{
+       v_n_t *numptr_m1;
+       arith_t numptr_val, rez;
+       int ret_arith_lookup_val;
+
+       /* There is no operator that can work without arguments */
+       if (NUMPTR == numstack) goto err;
+       numptr_m1 = NUMPTR - 1;
+
+       /* check operand is var with noninteger value */
+       ret_arith_lookup_val = arith_lookup_val(numptr_m1, math_hooks);
+       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, math_hooks);
+                       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[sizeof(arith_t)*3 + 2];
+
+               if (numptr_m1->var == NULL) {
+                       /* Hmm, 1=2 ? */
+                       goto err;
+               }
+               /* save to shell variable */
+               sprintf(buf, arith_t_fmt, rez);
+               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 be first */
+static const char op_tokens[] ALIGN1 = {
+       '<','<','=',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])
+
+arith_t
+arith(const char *expr, int *perrcode, a_e_h_t *math_hooks)
+{
+       char arithval; /* Current character under analysis */
+       operator lasttok, op;
+       operator prec;
+       operator *stack, *stackptr;
+       const char *p = endexpression;
+       int errcode;
+       v_n_t *numstack, *numstackptr;
+       unsigned 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. */
+       numstackptr = numstack = alloca((datasizes / 2) * sizeof(numstack[0]));
+       /* Stack of operator tokens */
+       stackptr = stack = alloca(datasizes * sizeof(stack[0]));
+
+       *stackptr++ = lasttok = TOK_LPAREN;     /* start off with a left paren */
+       *perrcode = errcode = 0;
+
+       while (1) {
+               arithval = *expr;
+               if (arithval == 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:
+                               *perrcode = -1;
+                               return *perrcode;
+                       }
+                       if (numstack->var) {
+                               /* expression is $((var)) only, lookup now */
+                               errcode = arith_lookup_val(numstack, math_hooks);
+                       }
+ ret:
+                       *perrcode = errcode;
+                       return numstack->val;
+               }
+
+               /* Continue processing the expression. */
+               if (arith_isspace(arithval)) {
+                       /* Skip whitespace */
+                       goto prologue;
+               }
+               p = endofname(expr);
+               if (p != 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;
+               }
+               if (isdigit(arithval)) {
+                       numstackptr->var = NULL;
+                       numstackptr->val = strto_arith_t(expr, (char **) &expr, 0);
+                       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, math_hooks);
+                               if (errcode) goto ret;
+                       }
+                       if (op == TOK_RPAREN) {
+                               goto err;
+                       }
+               }
+
+               /* Push this operator to the stack and remember it. */
+               *stackptr++ = lasttok = op;
+ prologue:
+               ++expr;
+       } /* while */
+}
+
+/*
+ * 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/math.h b/shell/math.h
new file mode 100644 (file)
index 0000000..51dbb56
--- /dev/null
@@ -0,0 +1,103 @@
+/* math.h - interface to shell math "library" -- this allows shells to share
+ *          the implementation of arithmetic $((...)) expansions.
+ *
+ * This aims to be a POSIX shell math library as documented here:
+ *     http://www.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_06_04
+ *
+ * See math.c for internal documentation.
+ */
+
+/* The math library has just one function:
+ *
+ *     arith_t arith(const char *expr, int *perrcode, arith_eval_hooks_t *hooks);
+ *
+ *     The first argument is the math string to parse.  All normal expansions must
+ *     be done already.  i.e. no dollar symbols should be present.
+ *
+ *     The second argument is a semi-detailed error description in case something
+ *     goes wrong in the parsing steps.  Currently, those values are (for
+ *     compatibility, you should assume all negative values are errors):
+ *              0 - no errors (yay!)
+ *             -1 - unspecified problem
+ *             -2 - divide by zero
+ *             -3 - exponent less than 0
+ *             -5 - expression recursion loop detected
+ *
+ *     The third argument is a struct pointer of hooks for your shell (see below).
+ *
+ *     The function returns the answer to the expression.  So if you called it
+ *     with the expression:
+ *             "1 + 2 + 3"
+ *     You would obviously get back 6.
+ */
+
+/* To add support to a shell, you need to implement three functions:
+ *
+ *     lookupvar() - look up and return the value of a variable
+ *
+ *             If the shell does:
+ *                     foo=123
+ *             Then the code:
+ *                     const char *val = lookupvar("foo");
+ *             Will result in val pointing to "123"
+ *
+ *     setvar() - set a variable to some value
+ *
+ *             If the arithmetic expansion does something like:
+ *                     $(( i = 1))
+ *             Then the math code will make a call like so:
+ *                     setvar("i", "1", 0);
+ *             The storage for the first two parameters are not allocated, so your
+ *             shell implementation will most likely need to strdup() them to save.
+ *
+ *     endofname() - return the end of a variable name from input
+ *
+ *             The arithmetic code does not know about variable naming conventions.
+ *             So when it is given an experession, it knows something is not numeric,
+ *             but it is up to the shell to dictate what is a valid identifiers.
+ *             So when it encounters something like:
+ *                     $(( some_var + 123 ))
+ *             It will make a call like so:
+ *                     end = endofname("some_var + 123");
+ *             So the shell needs to scan the input string and return a pointer to the
+ *             first non-identifier string.  In this case, it should return the input
+ *             pointer with an offset pointing to the first space.  The typical
+ *             implementation will return the offset of first char that does not match
+ *             the regex (in C locale): ^[a-zA-Z_][a-zA-Z_0-9]*
+ */
+
+/* To make your life easier when dealing with optional 64bit math support,
+ * rather than assume that the type is "signed long" and you can always
+ * use "%ld" to scan/print the value, use the arith_t helper defines.  See
+ * below for the exact things that are available.
+ */
+
+#ifndef SHELL_MATH_H
+#define SHELL_MATH_H 1
+
+PUSH_AND_SET_FUNCTION_VISIBILITY_TO_HIDDEN
+
+#if ENABLE_SH_MATH_SUPPORT_64
+typedef long long arith_t;
+#define arith_t_fmt "%lld"
+#define strto_arith_t strtoll
+#else
+typedef long arith_t;
+#define arith_t_fmt "%ld"
+#define strto_arith_t strtol
+#endif
+
+typedef const char *(*arith_var_lookup_t)(const char *name);
+typedef void (*arith_var_set_t)(const char *name, const char *val, int flags);
+typedef char *(*arith_var_endofname_t)(const char *name);
+typedef struct arith_eval_hooks {
+       arith_var_lookup_t lookupvar;
+       arith_var_set_t setvar;
+       arith_var_endofname_t endofname;
+} arith_eval_hooks_t;
+
+arith_t arith(const char *expr, int *perrcode, arith_eval_hooks_t*);
+
+POP_SAVED_FUNCTION_VISIBILITY
+
+#endif
diff --git a/shell/msh.c b/shell/msh.c
new file mode 100644 (file)
index 0000000..dffacf0
--- /dev/null
@@ -0,0 +1,5335 @@
+/* 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 <sys/times.h>
+#include <setjmp.h>
+
+#ifdef STANDALONE
+# ifndef _GNU_SOURCE
+#  define _GNU_SOURCE
+# endif
+# include <sys/types.h>
+# include <sys/stat.h>
+# include <sys/wait.h>
+# include <signal.h>
+# include <stdio.h>
+# include <stdlib.h>
+# include <unistd.h>
+# include <string.h>
+# include <errno.h>
+# include <dirent.h>
+# include <fcntl.h>
+# include <ctype.h>
+# include <assert.h>
+# define bb_dev_null "/dev/null"
+# define DEFAULT_SHELL "/proc/self/exe"
+# define bb_banner "busybox standalone"
+# define ENABLE_FEATURE_SH_STANDALONE 0
+# define bb_msg_memory_exhausted "memory exhausted"
+# define xmalloc(size) malloc(size)
+# define msh_main(argc,argv) main(argc,argv)
+# define safe_read(fd,buf,count) read(fd,buf,count)
+# define nonblock_safe_read(fd,buf,count) read(fd,buf,count)
+# define NOT_LONE_DASH(s) ((s)[0] != '-' || (s)[1])
+# define LONE_CHAR(s,c) ((s)[0] == (c) && !(s)[1])
+# define NORETURN __attribute__ ((__noreturn__))
+static int find_applet_by_name(const char *applet)
+{
+       return -1;
+}
+static char *utoa_to_buf(unsigned n, char *buf, unsigned buflen)
+{
+       unsigned i, out, res;
+       assert(sizeof(unsigned) == 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;
+                       }
+               }
+       }
+       return buf;
+}
+static char *itoa_to_buf(int n, char *buf, unsigned buflen)
+{
+       if (buflen && n < 0) {
+               n = -n;
+               *buf++ = '-';
+               buflen--;
+       }
+       return utoa_to_buf((unsigned)n, buf, buflen);
+}
+static char local_buf[12];
+static char *itoa(int n)
+{
+       *(itoa_to_buf(n, local_buf, sizeof(local_buf))) = '\0';
+       return local_buf;
+}
+#else
+# include "busybox.h" /* for applet_names */
+#endif
+
+//#define MSHDEBUG 4
+
+#ifdef MSHDEBUG
+static 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
+
+static int mshdbg_rc = 0;
+
+#define RCPRINTF(x)    if (mshdbg_rc) printf x
+
+#else
+
+#define DBGPRINTF(x)
+#define DBGPRINTF0(x) ((void)0)
+#define DBGPRINTF1(x) ((void)0)
+#define DBGPRINTF2(x) ((void)0)
+#define DBGPRINTF3(x) ((void)0)
+#define DBGPRINTF4(x) ((void)0)
+#define DBGPRINTF5(x) ((void)0)
+#define DBGPRINTF6(x) ((void)0)
+#define DBGPRINTF7(x) ((void)0)
+#define DBGPRINTF8(x) ((void)0)
+#define DBGPRINTF9(x) ((void)0)
+
+#define RCPRINTF(x) ((void)0)
+
+#endif  /* MSHDEBUG */
+
+
+#if ENABLE_FEATURE_EDITING_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        NOBLOCK ((struct op *)NULL)
+#define        NOWORD  ((char *)NULL)
+#define        NOWORDS ((char **)NULL)
+#define        NOPIPE  ((int *)NULL)
+
+/*
+ * redirection
+ */
+struct ioword {
+       smallint io_flag;               /* action (below) */
+       int io_fd;                      /* fd affected */
+       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)                  /* "default" IO fd */
+
+
+/*
+ * Description of a command or an operation on commands.
+ * Might eventually use a union.
+ */
+struct op {
+       smallint op_type;               /* operation type, see Txxxx below */
+       char **op_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 const char *const T_CMD_NAMES[] = {
+       "PLACEHOLDER",
+       "TCOM",
+       "TPAREN",
+       "TPIPE",
+       "TLIST",
+       "TOR",
+       "TAND",
+       "TFOR",
+       "TDO",
+       "TCASE",
+       "TIF",
+       "TWHILE",
+       "TUNTIL",
+       "TELIF",
+       "TPAT",
+       "TBRACE",
+       "TASYNC",
+       "TDOT",
+};
+#endif
+
+#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)
+
+
+struct brkcon {
+       jmp_buf brkpt;
+       struct brkcon *nextlev;
+};
+
+
+static smallint trapset;                        /* trap pending (signal number) */
+
+static smallint yynerrs;                        /* yacc (flag) */
+
+/* moved to G: static char line[LINELIM]; */
+
+#if ENABLE_FEATURE_EDITING
+static char *current_prompt;
+static line_input_t *line_input_state;
+#endif
+
+
+/*
+ * other functions
+ */
+static const char *rexecve(char *c, char **v, char **envp);
+static char *evalstr(char *cp, int f);
+static char *putn(int n);
+static char *unquote(char *as);
+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 next(int f);
+static void setdash(void);
+static void onecommand(void);
+static void runtrap(int i);
+
+
+/* -------- 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 */
+
+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 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);
+
+
+/* -------- 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 {
+       const char *aword;
+       char **awordlist;
+       int afile;              /* file descriptor */
+       unsigned afid;          /* buffer id */
+       off_t afpos;            /* file position */
+       struct iobuf *afbuf;    /* buffer for this file */
+};
+
+/* 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 */
+};
+/* ->task: */
+#define        XOTHER  0       /* none of the below */
+#define        XDOLL   1       /* expanding ${} */
+#define        XGRAVE  2       /* expanding `'s */
+#define        XIO     3       /* file IO */
+
+
+/*
+ * 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 *));
+
+
+static int eofc(void);
+static int readc(void);
+static void unget(int c);
+static void ioecho(char c);
+
+
+/*
+ * IO control
+ */
+static void pushio(struct ioarg *argp, int (*f) (struct ioarg *));
+#define PUSHIO(what,arg,gen) ((temparg.what = (arg)), pushio(&temparg,(gen)))
+static int remap(int fd);
+static int openpipe(int *pv);
+static void closepipe(int *pv);
+static struct io *setbase(struct io *ip);
+
+/* -------- 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);
+
+/* -------- misc stuff -------- */
+
+static int dolabel(struct op *t, char **args);
+static int dohelp(struct op *t, char **args);
+static int dochdir(struct op *t, char **args);
+static int doshift(struct op *t, char **args);
+static int dologin(struct op *t, char **args);
+static int doumask(struct op *t, char **args);
+static int doexec(struct op *t, char **args);
+static int dodot(struct op *t, char **args);
+static int dowait(struct op *t, char **args);
+static int doread(struct op *t, char **args);
+static int doeval(struct op *t, char **args);
+static int dotrap(struct op *t, char **args);
+static int dobreak(struct op *t, char **args);
+static int doexit(struct op *t, char **args);
+static int doexport(struct op *t, char **args);
+static int doreadonly(struct op *t, char **args);
+static int doset(struct op *t, char **args);
+static int dotimes(struct op *t, char **args);
+static int docontinue(struct op *t, char **args);
+
+static int forkexec(struct op *t, int *pin, int *pout, int no_fork, char **wp);
+static int execute(struct op *t, int *pin, int *pout, int no_fork);
+static int iosetup(struct ioword *iop, int pipein, int pipeout);
+static void brkset(struct brkcon *bc);
+static int getsig(char *s);
+static void setsig(int n, sighandler_t f);
+static int getn(char *as);
+static int brkcontin(char *cp, int val);
+static void rdexp(char **wp, void (*f) (struct var *), int key);
+static void badid(char *s);
+static void varput(char *s, int out);
+static int expand(const 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 void readhere(char **name, char *s, int ec);
+static int xxchar(struct ioarg *ap);
+
+struct here {
+       char *h_tag;
+       char h_dosub;
+       struct ioword *h_iop;
+       struct here *h_next;
+};
+
+static const char *const signame[] = {
+       "Signal 0",
+       "Hangup",
+       NULL,  /* interrupt */
+       "Quit",
+       "Illegal instruction",
+       "Trace/BPT trap",
+       "Abort",
+       "Bus error",
+       "Floating Point Exception",
+       "Killed",
+       "SIGUSR1",
+       "SIGSEGV",
+       "SIGUSR2",
+       NULL,  /* broken pipe */
+       "Alarm clock",
+       "Terminated"
+};
+
+
+typedef int (*builtin_func_ptr)(struct op *, char **);
+
+struct builtincmd {
+       const char *name;
+       builtin_func_ptr builtinfunc;
+};
+
+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     },
+       { NULL      , NULL       },
+};
+
+static struct op *dowholefile(int /*, int*/);
+
+
+/* Globals */
+static char **dolv;
+static int dolc;
+static uint8_t exstat;
+static smallint gflg;                   /* (seems to be a parse error indicator) */
+static smallint interactive;            /* Is this an interactive shell */
+static smallint execflg;
+static smallint isbreak;                /* "break" statement was seen */
+static int multiline;                   /* '\n' changed to ';' (counter) */
+static struct op *outtree;              /* result from parser */
+static xint *failpt;
+static xint *errpt;
+static struct brkcon *brklist;
+static struct wdblock *wdlist;
+static struct wdblock *iolist;
+
+#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 smallint intr;                   /* interrupt pending (bool) */
+static smallint heedint = 1;            /* heed interrupt signals (bool) */
+static int inparse;
+static char *null = (char*)"";          /* null value for variable */
+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; /* done in main(): = line + sizeof(line) - 5 */
+
+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;
+
+#define AFID_NOBUF     (~0)
+#define AFID_ID                0
+
+
+/*
+ * parsing & execution environment
+ */
+struct env {
+       char *linep;
+       struct io *iobase;
+       struct io *iop;
+       xint *errpt;            /* void * */
+       int iofd;
+       struct env *oenv;
+};
+
+
+struct globals {
+       struct env global_env;
+       struct ioarg temparg; // = { .afid = AFID_NOBUF };      /* temporary for PUSHIO */
+       unsigned bufid; // = AFID_ID;   /* buffer id counter */
+       char ourtrap[_NSIG + 1];
+       char *trap[_NSIG + 1];
+       struct iobuf sharedbuf; /* in main(): set to { AFID_NOBUF } */
+       struct iobuf mainbuf; /* in main(): set to { AFID_NOBUF } */
+       struct ioarg ioargstack[NPUSH];
+       /*
+        * 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
+        */
+       char flags['z' - 'a' + 1];
+       char filechar_cmdbuf[BUFSIZ];
+       char line[LINELIM];
+       char child_cmd[LINELIM];
+
+       struct io iostack[NPUSH];
+
+       char grave__var_name[LINELIM];
+       char grave__alt_value[LINELIM];
+};
+
+#define G (*ptr_to_globals)
+#define global_env      (G.global_env     )
+#define temparg         (G.temparg        )
+#define bufid           (G.bufid          )
+#define ourtrap         (G.ourtrap        )
+#define trap            (G.trap           )
+#define sharedbuf       (G.sharedbuf      )
+#define mainbuf         (G.mainbuf        )
+#define ioargstack      (G.ioargstack     )
+/* this looks weird, but is OK ... we index FLAG with 'a'...'z' */
+#define FLAG            (G.flags - 'a'    )
+#define filechar_cmdbuf (G.filechar_cmdbuf)
+#define line            (G.line           )
+#define child_cmd       (G.child_cmd      )
+#define iostack         (G.iostack        )
+#define INIT_G() do { \
+       SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
+       global_env.linep = line; \
+       global_env.iobase = iostack; \
+       global_env.iop = iostack - 1; \
+       global_env.iofd = FDBASE; \
+       temparg.afid = AFID_NOBUF; \
+       bufid = AFID_ID; \
+} while (0)
+
+
+/* in substitution */
+#define        INSUB() (global_env.iop->task == XGRAVE || global_env.iop->task == XDOLL)
+
+#define        RUN(what, arg, gen) ((temparg.what = (arg)), run(&temparg, (gen)))
+
+#ifdef MSHDEBUG
+static 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);
+}
+#endif /* MSHDEBUG */
+
+
+/*
+ * IO functions
+ */
+static void prs(const char *s)
+{
+       if (*s)
+               xwrite_str(STDERR_FILENO, s);
+}
+
+static void prn(unsigned u)
+{
+       prs(itoa(u));
+}
+
+static void echo(char **wp)
+{
+       int i;
+
+       prs("+");
+       for (i = 0; wp[i]; i++) {
+               if (i)
+                       prs(" ");
+               prs(wp[i]);
+       }
+       prs("\n");
+}
+
+static void closef(int i)
+{
+       if (i > 2)
+               close(i);
+}
+
+static void closeall(void)
+{
+       int u;
+
+       for (u = NUFILE; u < NOFILE;)
+               close(u++);
+}
+
+
+/* fail but return to process next command */
+static void fail(void) NORETURN;
+static void fail(void)
+{
+       longjmp(failpt, 1);
+       /* NOTREACHED */
+}
+
+/* abort shell (or fail in subshell) */
+static void leave(void) NORETURN;
+static void leave(void)
+{
+       DBGPRINTF(("LEAVE: leave called!\n"));
+
+       if (execflg)
+               fail();
+       scraphere();
+       freehere(1);
+       runtrap(0);
+       _exit(exstat);
+       /* NOTREACHED */
+}
+
+static void warn(const char *s)
+{
+       if (*s) {
+               prs(s);
+               if (!exstat)
+                       exstat = 255;
+       }
+       prs("\n");
+       if (FLAG['e'])
+               leave();
+}
+
+static void err(const char *s)
+{
+       warn(s);
+       if (FLAG['n'])
+               return;
+       if (!interactive)
+               leave();
+       if (global_env.errpt)
+               longjmp(global_env.errpt, 1);
+       closeall();
+       global_env.iop = global_env.iobase = iostack;
+}
+
+
+/* -------- 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;
+}
+
+static 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;
+       p = areanxt;
+       for (;;) {
+               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;
+
+       p = (struct region *) cp;
+       if (p != NULL) {
+               p--;
+               if (p < areanxt)
+                       areanxt = p;
+               p->area = FREE;
+       }
+}
+#define        DELETE(obj) freecell((char *)obj)
+
+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;
+
+       p = (struct region *) cp;
+       if (p != NULL)
+               (p - 1)->area = a;
+}
+
+static 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
+}
+
+static void *get_space(int n)
+{
+       char *cp;
+
+       cp = getcell(n);
+       if (cp == NULL)
+               err("out of string space");
+       return cp;
+}
+
+static char *strsave(const char *s, int a)
+{
+       char *cp;
+
+       cp = get_space(strlen(s) + 1);
+       if (cp == NULL) {
+// FIXME: I highly doubt this is good.
+               return (char*)"";
+       }
+       setarea(cp, a);
+       strcpy(cp, s);
+       return cp;
+}
+
+
+/* -------- var.c -------- */
+
+static int eqname(const char *n1, const char *n2)
+{
+       for (; *n1 != '=' && *n1 != '\0'; n1++)
+               if (*n2++ != *n1)
+                       return 0;
+       return *n2 == '\0' || *n2 == '=';
+}
+
+static const char *findeq(const char *cp)
+{
+       while (*cp != '\0' && *cp != '=')
+               cp++;
+       return cp;
+}
+
+/*
+ * 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(const char *n)
+{
+// FIXME: dirty hack
+       static struct var dummy;
+
+       struct var *vp;
+       const char *cp;
+       char *xp;
+       int c;
+
+       if (isdigit(*n)) {
+               dummy.name = (char*)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 = get_space(sizeof(*vp));
+       if (vp == 0 || (vp->name = get_space((int) (cp - n) + 2)) == NULL) {
+               dummy.name = dummy.value = (char*)"";
+               return &dummy;
+       }
+
+       xp = vp->name;
+       while ((*xp = *n++) != '\0' && *xp != '=')
+               xp++;
+       *xp++ = '=';
+       *xp = '\0';
+       setarea((char *) vp, 0);
+       setarea((char *) vp->name, 0);
+       vp->value = null;
+       vp->next = vlist;
+       vp->status = GETCELL;
+       vlist = vp;
+       return vp;
+}
+
+/*
+ * 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, const char *val, const char *name)
+{
+       const char *cp;
+       char *xp;
+       int fl;
+
+       if (vp->status & RONLY) {
+               xp = vp->name;
+               while (*xp && *xp != '=')
+                       fputc(*xp++, stderr);
+               err(" is read-only");
+               return;
+       }
+       fl = 0;
+       if (name == NULL) {
+               xp = get_space(strlen(vp->name) + strlen(val) + 2);
+               if (xp == NULL)
+                       return;
+               /* make string: name=value */
+               setarea(xp, 0);
+               name = xp;
+               cp = vp->name;
+               while ((*xp = *cp++) != '\0' && *xp != '=')
+                       xp++;
+               *xp++ = '=';
+               strcpy(xp, val);
+               val = xp;
+               fl = GETCELL;
+       }
+       if (vp->status & GETCELL)
+               freecell(vp->name);             /* form new string `name=value' */
+       vp->name = (char*)name;
+       vp->value = (char*)val;
+       vp->status |= fl;
+}
+
+/*
+ * give variable at `vp' the value `val'.
+ */
+static void setval(struct var *vp, const char *val)
+{
+       nameval(vp, val, NULL);
+}
+
+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(const char *s)
+{
+       unsigned char c;
+       DBGPRINTF7(("ISASSIGN: enter, s=%s\n", s));
+
+       c = *s;
+       /* no isalpha() - we shouldn't use locale */
+       /* c | 0x20 - lowercase (Latin) letters */
+       if (c != '_' && (unsigned)((c|0x20) - 'a') > 25)
+               /* not letter */
+               return 0;
+
+       while (1) {
+               c = *++s;
+               if (c == '=')
+                       return 1;
+               if (c == '\0')
+                       return 0;
+               if (c != '_'
+                && (unsigned)(c - '0') > 9  /* not number */
+                && (unsigned)((c|0x20) - 'a') > 25 /* not letter */
+               ) {
+                       return 0;
+               }
+       }
+}
+
+static int assign(const char *s, int cf)
+{
+       const 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 ? 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);
+               }
+       }
+}
+
+
+/*
+ * trap handling
+ */
+static void sig(int i)
+{
+       trapset = i;
+       signal(i, sig);
+}
+
+static void runtrap(int i)
+{
+       char *trapstr;
+
+       trapstr = trap[i];
+       if (trapstr == NULL)
+               return;
+
+       if (i == 0)
+               trap[i] = NULL;
+
+       RUN(aword, trapstr, nlchar);
+}
+
+
+static void setdash(void)
+{
+       char *cp;
+       int c;
+       char m['z' - 'a' + 1];
+
+       cp = m;
+       for (c = 'a'; c <= 'z'; c++)
+               if (FLAG[c])
+                       *cp++ = c;
+       *cp = '\0';
+       setval(lookup("-"), m);
+}
+
+static int newfile(char *s)
+{
+       int f;
+
+       DBGPRINTF7(("NEWFILE: opening %s\n", s));
+
+       f = 0;
+       if (NOT_LONE_DASH(s)) {
+               DBGPRINTF(("NEWFILE: s is %s\n", s));
+               f = open(s, O_RDONLY);
+               if (f < 0) {
+                       prs(s);
+                       err(": can't open");
+                       return 1;
+               }
+       }
+
+       next(remap(f));
+       return 0;
+}
+
+
+#ifdef UNUSED
+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->op_words == NULL)
+               return NULL;
+
+       DBGPRINTF5(("SCANTREE: checking node %p\n", head));
+
+       if ((head->op_type != TDOT) && LONE_CHAR(head->op_words[0], '.')) {
+               DBGPRINTF5(("SCANTREE: dot found in node %p\n", head));
+               return head;
+       }
+
+       return NULL;
+}
+#endif
+
+
+static void onecommand(void)
+{
+       int i;
+       jmp_buf m1;
+
+       DBGPRINTF(("ONECOMMAND: enter, outtree=%p\n", outtree));
+
+       while (global_env.oenv)
+               quitenv();
+
+       areanum = 1;
+       freehere(areanum);
+       freearea(areanum);
+       garbage();
+       wdlist = NULL;
+       iolist = NULL;
+       global_env.errpt = NULL;
+       global_env.linep = line;
+       yynerrs = 0;
+       multiline = 0;
+       inparse = 1;
+       intr = 0;
+       execflg = 0;
+
+       failpt = m1;
+       setjmp(failpt);         /* Bruce Evans' fix */
+       failpt = m1;
+       if (setjmp(failpt) || yyparse() || intr) {
+               DBGPRINTF(("ONECOMMAND: this is not good.\n"));
+
+               while (global_env.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, /* no_fork: */ 0);
+       }
+
+       if (!interactive && intr) {
+               execflg = 0;
+               leave();
+       }
+
+       i = trapset;
+       if (i != 0) {
+               trapset = 0;
+               runtrap(i);
+       }
+}
+
+static int newenv(int f)
+{
+       struct env *ep;
+
+       DBGPRINTF(("NEWENV: f=%d (indicates quitenv and return)\n", f));
+
+       if (f) {
+               quitenv();
+               return 1;
+       }
+
+       ep = get_space(sizeof(*ep));
+       if (ep == NULL) {
+               while (global_env.oenv)
+                       quitenv();
+               fail();
+       }
+       *ep = global_env;
+       global_env.oenv = ep;
+       global_env.errpt = errpt;
+
+       return 0;
+}
+
+static void quitenv(void)
+{
+       struct env *ep;
+       int fd;
+
+       DBGPRINTF(("QUITENV: global_env.oenv=%p\n", global_env.oenv));
+
+       ep = global_env.oenv;
+       if (ep != NULL) {
+               fd = global_env.iofd;
+               global_env = *ep;
+               /* should close `'d files */
+               DELETE(ep);
+               while (--fd >= global_env.iofd)
+                       close(fd);
+       }
+}
+
+/*
+ * Is character c in s?
+ */
+static int any(int c, const char *s)
+{
+       while (*s)
+               if (*s++ == c)
+                       return 1;
+       return 0;
+}
+
+/*
+ * Is any character from s1 in s2?
+ */
+static int anys(const char *s1, const char *s2)
+{
+       while (*s1)
+               if (any(*s1++, s2))
+                       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 UNUSED_PARAM) /* ANSI C requires a parameter */
+{
+       signal(SIGINT, onintr);
+       intr = 1;
+       if (interactive) {
+               if (inparse) {
+                       prs("\n");
+                       fail();
+               }
+       } else if (heedint) {
+               execflg = 0;
+               leave();
+       }
+}
+
+
+/* -------- 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 const char *cclass(const char *p, int sub)
+{
+       int c, d, not, found;
+
+       not = (*p == NOT);
+       if (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;
+}
+
+static int gmatch(const char *s, const char *p)
+{
+       int sc, pc;
+
+       if (s == NULL || p == NULL)
+               return 0;
+
+       while ((pc = *p++ & CMASK) != '\0') {
+               sc = *s++ & QMASK;
+               switch (pc) {
+               case '[':
+                       p = cclass(p, sc);
+                       if (p == 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';
+}
+
+
+/* -------- csyn.c -------- */
+/*
+ * shell: syntax (C version)
+ */
+
+static void yyerror(const char *s) NORETURN;
+static void yyerror(const char *s)
+{
+       yynerrs = 1;
+       if (interactive && global_env.iop <= iostack) {
+               multiline = 0;
+               while (eofc() == 0 && yylex(0) != '\n')
+                       continue;
+       }
+       err(s);
+       fail();
+}
+
+static void zzerr(void) NORETURN;
+static void zzerr(void)
+{
+       yyerror("syntax error");
+}
+
+int yyparse(void)
+{
+       DBGPRINTF7(("YYPARSE: enter...\n"));
+
+       startl = 1;
+       peeksym = 0;
+       yynerrs = 0;
+       outtree = c_list();
+       musthave('\n', 0);
+       return yynerrs; /* 0/1 */
+}
+
+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)) == '|') {
+                       p = command(CONTIN);
+                       if (p == NULL) {
+                               DBGPRINTF8(("PIPELINE: error!\n"));
+                               zzerr();
+                       }
+
+                       if (t->op_type != TPAREN && t->op_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) {
+                       p = pipeline(CONTIN);
+                       if (p == NULL) {
+                               DBGPRINTF8(("ANDOR: error!\n"));
+                               zzerr();
+                       }
+
+                       t = block(c == LOGAND ? TAND : TOR, t, p, NOWORDS);
+               }
+
+               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) {
+               peeksym = yylex(0);
+               if (peeksym == '&')
+                       t = block(TASYNC, t, NOBLOCK, NOWORDS);
+
+               while ((c = yylex(0)) == ';' || c == '&'
+                || (multiline && c == '\n')
+               ) {
+                       p = andor();
+                       if (p== NULL)
+                               return t;
+
+                       peeksym = yylex(0);
+                       if (peeksym == '&')
+                               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));
+
+       c = yylex(cf);
+       if (c != '<' && 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)
+{
+       peeksym = yylex(cf);
+       if (peeksym != c) {
+               DBGPRINTF7(("MUSTHAVE: error!\n"));
+               zzerr();
+       }
+
+       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->op_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;
+               t = simple();
+               if (t == NULL) {
+                       if (iolist == NULL)
+                               return NULL;
+                       t = newtp();
+                       t->op_type = TCOM;
+               }
+               break;
+
+       case '(':
+               t = nested(TPAREN, ')');
+               break;
+
+       case '{':
+               t = nested(TBRACE, '}');
+               break;
+
+       case FOR:
+               t = newtp();
+               t->op_type = TFOR;
+               musthave(WORD, 0);
+               startl = 1;
+               t->str = yylval.cp;
+               multiline++;
+               t->op_words = wordlist();
+               c = yylex(0);
+               if (c != '\n' && c != ';')
+                       peeksym = c;
+               t->left = dogroup(0);
+               multiline--;
+               break;
+
+       case WHILE:
+       case UNTIL:
+               multiline++;
+               t = newtp();
+               t->op_type = (c == WHILE ? TWHILE : TUNTIL);
+               t->left = c_list();
+               t->right = dogroup(1);
+               /* t->op_words = NULL; - newtp() did this */
+               multiline--;
+               break;
+
+       case CASE:
+               t = newtp();
+               t->op_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->op_type = TIF;
+               t->left = c_list();
+               t->right = thenpart();
+               musthave(FI, 0);
+               multiline--;
+               break;
+
+       case DOT:
+               t = newtp();
+               t->op_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->op_words = copyw();          /* dup wdlist */
+               break;
+
+       }
+
+       while (synio(0))
+               continue;
+
+       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\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)
+               zzerr();
+       mylist = c_list();
+       musthave(DONE, 0);
+       return mylist;
+}
+
+static struct op *thenpart(void)
+{
+       int c;
+       struct op *t;
+
+       c = yylex(0);
+       if (c != THEN) {
+               peeksym = c;
+               return NULL;
+       }
+       t = newtp();
+       /*t->op_type = 0; - newtp() did this */
+       t->left = c_list();
+       if (t->left == NULL)
+               zzerr();
+       t->right = elsepart();
+       return t;
+}
+
+static struct op *elsepart(void)
+{
+       int c;
+       struct op *t;
+
+       switch (c = yylex(0)) {
+       case ELSE:
+               t = c_list();
+               if (t == NULL)
+                       zzerr();
+               return t;
+
+       case ELIF:
+               t = newtp();
+               t->op_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->op_type = TPAT;
+       t->op_words = pattern();
+       musthave(')', 0);
+       t->left = c_list();
+       peeksym = yylex(CONTIN);
+       if (peeksym != 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;
+               c = yylex(0);
+       } while (c == '|');
+       peeksym = c;
+       word(NOWORD);
+
+       return copyw();
+}
+
+static char **wordlist(void)
+{
+       int c;
+
+       c = yylex(0);
+       if (c != 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->op_type = type;
+       t->left = t1;
+       t->right = t2;
+       t->op_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)
+{
+       struct res {
+               char r_name[6];
+               int16_t 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    },
+               { },
+       };
+
+       const struct res *rp;
+
+       DBGPRINTF7(("RLOOKUP: enter, n is %s\n", n));
+
+       for (rp = restab; rp->r_name[0]; 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));
+       memset(t, 0, sizeof(*t));
+
+       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->op_type], iolist));
+
+       if (iolist) {
+               iolist = addword((char *) NULL, iolist);
+               t->ioact = copyio();
+       } else
+               t->ioact = NULL;
+
+       if (t->op_type != TCOM) {
+               if (t->op_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->op_words = copyw();
+
+       return t;
+}
+
+static char **copyw(void)
+{
+       char **wd;
+
+       wd = getwords(wdlist);
+       wdlist = NULL;
+       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 = NULL;
+       return iop;
+}
+
+static struct ioword *io(int u, int f, char *cp)
+{
+       struct ioword *iop;
+
+       iop = (struct ioword *) tree(sizeof(*iop));
+       iop->io_fd = u;
+       iop->io_flag = f;
+       iop->io_name = cp;
+       iolist = addword((char *) iop, iolist);
+       return iop;
+}
+
+static int yylex(int cf)
+{
+       int c, c1;
+       int atstart;
+
+       c = peeksym;
+       if (c > 0) {
+               peeksym = 0;
+               if (c == '\n')
+                       startl = 1;
+               return c;
+       }
+
+       nlseen = 0;
+       atstart = startl;
+       startl = 0;
+       yylval.i = 0;
+       global_env.linep = line;
+
+/* MALAMO */
+       line[LINELIM - 1] = '\0';
+
+ loop:
+       while ((c = my_getc(0)) == ' ' || c == '\t')    /* Skip whitespace */
+               continue;
+
+       switch (c) {
+       default:
+               if (any(c, "0123456789")) {
+                       c1 = my_getc(0);
+                       unget(c1);
+                       if (c1 == '<' || c1 == '>') {
+                               iounit = c - '0';
+                               goto loop;
+                       }
+                       *global_env.linep++ = c;
+                       c = c1;
+               }
+               break;
+
+       case '#':       /* Comment, skip to next newline or End-of-string */
+               while ((c = my_getc(0)) != '\0' && c != '\n')
+                       continue;
+               unget(c);
+               goto loop;
+
+       case 0:
+               DBGPRINTF5(("YYLEX: return 0, c=%d\n", c));
+               return c;
+
+       case '$':
+               DBGPRINTF9(("YYLEX: found $\n"));
+               *global_env.linep++ = c;
+               c = my_getc(0);
+               if (c == '{') {
+                       c = collect(c, '}');
+                       if (c != '\0')
+                               return c;
+                       goto pack;
+               }
+               break;
+
+       case '`':
+       case '\'':
+       case '"':
+               c = collect(c, c);
+               if (c != '\0')
+                       return c;
+               goto pack;
+
+       case '|':
+       case '&':
+       case ';':
+               startl = 1;
+               /* If more chars process them, else return NULL char */
+               c1 = dual(c);
+               if (c1 != '\0')
+                       return c1;
+               return c;
+
+       case '^':
+               startl = 1;
+               return '|';
+       case '>':
+       case '<':
+               diag(c);
+               return c;
+
+       case '\n':
+               nlseen++;
+               gethere();
+               startl = 1;
+               if (multiline || cf & CONTIN) {
+                       if (interactive && global_env.iop <= iostack) {
+#if ENABLE_FEATURE_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 (global_env.linep >= elinep)
+                       err("word too long");
+               else
+                       *global_env.linep++ = c;
+       };
+
+       unget(c);
+
+       if (any(c, "\"'`$"))
+               goto loop;
+
+       *global_env.linep++ = '\0';
+
+       if (atstart) {
+               c = rlookup(line);
+               if (c != 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));
+
+       *global_env.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' && global_env.iop <= iostack) {
+#if ENABLE_FEATURE_EDITING
+                       current_prompt = cprompt->value;
+#else
+                       prs(cprompt->value);
+#endif
+               }
+               *global_env.linep++ = c;
+       }
+
+       *global_env.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;
+
+       t = getcell(size);
+       if (t == NULL) {
+               DBGPRINTF2(("TREE: getcell(%d) failed!\n", size));
+               prs("command line too complicated\n");
+               fail();
+               /* NOTREACHED */
+       }
+       return t;
+}
+
+
+/* VARARGS1 */
+/* ARGSUSED */
+
+/* -------- exec.c -------- */
+
+static struct op **find1case(struct op *t, const char *w)
+{
+       struct op *t1;
+       struct op **tp;
+       char **wp;
+       char *cp;
+
+       if (t == NULL) {
+               DBGPRINTF3(("FIND1CASE: enter, t==NULL, returning.\n"));
+               return NULL;
+       }
+
+       DBGPRINTF3(("FIND1CASE: enter, t->op_type=%d (%s)\n", t->op_type,
+                               T_CMD_NAMES[t->op_type]));
+
+       if (t->op_type == TLIST) {
+               tp = find1case(t->left, w);
+               if (tp != 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->op_words; *wp;) {
+               cp = evalstr(*wp++, DOSUB);
+               if (cp && 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, const char *w)
+{
+       struct op **tp;
+
+       tp = find1case(t, w);
+       return tp != NULL ? *tp : NULL;
+}
+
+/*
+ * execute tree
+ */
+
+static int execute(struct op *t, int *pin, int *pout, int no_fork)
+{
+       struct op *t1;
+       volatile int i, rv, a;
+       const char *cp;
+       char **wp, **wp2;
+       struct var *vp;
+       struct op *outtree_save;
+       struct brkcon bc;
+
+#if __GNUC__
+       /* Avoid longjmp clobbering */
+       (void) &wp;
+#endif
+
+       if (t == NULL) {
+               DBGPRINTF4(("EXECUTE: enter, t==null, returning.\n"));
+               return 0;
+       }
+
+       DBGPRINTF(("EXECUTE: t=%p, t->op_type=%d (%s), t->op_words is %s\n", t,
+                          t->op_type, T_CMD_NAMES[t->op_type],
+                          ((t->op_words == NULL) ? "NULL" : t->op_words[0])));
+
+       rv = 0;
+       a = areanum++;
+       wp2 = t->op_words;
+       wp = (wp2 != NULL)
+               ? eval(wp2, t->op_type == TCOM ? DOALL : DOALL & ~DOKEY)
+               : NULL;
+
+       switch (t->op_type) {
+       case TDOT:
+               DBGPRINTF3(("EXECUTE: TDOT\n"));
+
+               outtree_save = outtree;
+
+               newfile(evalstr(t->op_words[0], DOALL));
+
+               t->left = dowholefile(TLIST /*, 0*/);
+               t->right = NULL;
+
+               outtree = outtree_save;
+
+               if (t->left)
+                       rv = execute(t->left, pin, pout, /* no_fork: */ 0);
+               if (t->right)
+                       rv = execute(t->right, pin, pout, /* no_fork: */ 0);
+               break;
+
+       case TPAREN:
+               rv = execute(t->left, pin, pout, /* no_fork: */ 0);
+               break;
+
+       case TCOM:
+               rv = forkexec(t, pin, pout, no_fork, wp);
+               break;
+
+       case TPIPE:
+               {
+                       int pv[2];
+
+                       rv = openpipe(pv);
+                       if (rv < 0)
+                               break;
+                       pv[0] = remap(pv[0]);
+                       pv[1] = remap(pv[1]);
+                       (void) execute(t->left, pin, pv, /* no_fork: */ 0);
+                       rv = execute(t->right, pv, pout, /* no_fork: */ 0);
+               }
+               break;
+
+       case TLIST:
+               (void) execute(t->left, pin, pout, /* no_fork: */ 0);
+               rv = execute(t->right, pin, pout, /* no_fork: */ 0);
+               break;
+
+       case TASYNC:
+               {
+                       smallint hinteractive = interactive;
+
+                       DBGPRINTF7(("EXECUTE: TASYNC clause, calling vfork()...\n"));
+
+                       i = vfork();
+                       if (i == 0) { /* child */
+                               signal(SIGINT, SIG_IGN);
+                               signal(SIGQUIT, SIG_IGN);
+                               if (interactive)
+                                       signal(SIGTERM, SIG_DFL);
+                               interactive = 0;
+                               if (pin == NULL) {
+                                       close(0);
+                                       xopen(bb_dev_null, O_RDONLY);
+                               }
+                               _exit(execute(t->left, pin, pout, /* no_fork: */ 1));
+                       }
+                       interactive = hinteractive;
+                       if (i != -1) {
+                               setval(lookup("!"), putn(i));
+                               closepipe(pin);
+                               if (interactive) {
+                                       prs(putn(i));
+                                       prs("\n");
+                               }
+                       } else
+                               rv = -1;
+                       setstatus(rv);
+               }
+               break;
+
+       case TOR:
+       case TAND:
+               rv = execute(t->left, pin, pout, /* no_fork: */ 0);
+               t1 = t->right;
+               if (t1 != NULL && (rv == 0) == (t->op_type == TAND))
+                       rv = execute(t1, pin, pout, /* no_fork: */ 0);
+               break;
+
+       case TFOR:
+               if (wp == NULL) {
+                       wp = dolv + 1;
+                       i = dolc;
+                       if (i < 0)
+                               i = 0;
+               } else {
+                       i = -1;
+                       while (*wp++ != NULL)
+                               continue;
+               }
+               vp = lookup(t->str);
+               while (setjmp(bc.brkpt))
+                       if (isbreak)
+                               goto broken;
+               /* Restore areanum value. It may be incremented by execute()
+                * below, and then "continue" may jump back to setjmp above */
+               areanum = a + 1;
+               freearea(areanum + 1);
+               brkset(&bc);
+               for (t1 = t->left; i-- && *wp != NULL;) {
+                       setval(vp, *wp++);
+                       rv = execute(t1, pin, pout, /* no_fork: */ 0);
+               }
+               brklist = brklist->nextlev;
+               break;
+
+       case TWHILE:
+       case TUNTIL:
+               while (setjmp(bc.brkpt))
+                       if (isbreak)
+                               goto broken;
+               /* Restore areanum value. It may be incremented by execute()
+                * below, and then "continue" may jump back to setjmp above */
+               areanum = a + 1;
+               freearea(areanum + 1);
+               brkset(&bc);
+               t1 = t->left;
+               while ((execute(t1, pin, pout, /* no_fork: */ 0) == 0) == (t->op_type == TWHILE))
+                       rv = execute(t->right, pin, pout, /* no_fork: */ 0);
+               brklist = brklist->nextlev;
+               break;
+
+       case TIF:
+       case TELIF:
+               if (t->right != NULL) {
+                       rv = !execute(t->left, pin, pout, /* no_fork: */ 0) ?
+                               execute(t->right->left, pin, pout, /* no_fork: */ 0) :
+                               execute(t->right->right, pin, pout, /* no_fork: */ 0);
+               }
+               break;
+
+       case TCASE:
+               cp = evalstr(t->str, DOSUB | DOTRIM);
+               if (cp == NULL)
+                       cp = "";
+
+               DBGPRINTF7(("EXECUTE: TCASE, t->str is %s, cp is %s\n",
+                                       ((t->str == NULL) ? "NULL" : t->str),
+                                       ((cp == NULL) ? "NULL" : cp)));
+
+               t1 = findcase(t->left, cp);
+               if (t1 != NULL) {
+                       DBGPRINTF7(("EXECUTE: TCASE, calling execute(t=%p, t1=%p)...\n", t, t1));
+                       rv = execute(t1, pin, pout, /* no_fork: */ 0);
+                       DBGPRINTF7(("EXECUTE: TCASE, back from execute(t=%p, t1=%p)...\n", t, t1));
+               }
+               break;
+
+       case TBRACE:
+/*
+               iopp = t->ioact;
+               if (i)
+                       while (*iopp)
+                               if (iosetup(*iopp++, pin!=NULL, pout!=NULL)) {
+                                       rv = -1;
+                                       break;
+                               }
+*/
+               if (rv >= 0) {
+                       t1 = t->left;
+                       if (t1) {
+                               rv = execute(t1, pin, pout, /* no_fork: */ 0);
+                       }
+               }
+               break;
+
+       };
+
+ broken:
+// Restoring op_words is most likely not needed now: see comment in forkexec()
+// (also take a look at exec builtin (doexec) - it touches t->op_words)
+       t->op_words = wp2;
+       isbreak = 0;
+       freehere(areanum);
+       freearea(areanum);
+       areanum = a;
+       if (interactive && intr) {
+               closeall();
+               fail();
+       }
+
+       i = trapset;
+       if (i != 0) {
+               trapset = 0;
+               runtrap(i);
+       }
+
+       DBGPRINTF(("EXECUTE: returning from t=%p, rv=%d\n", t, rv));
+       return rv;
+}
+
+static builtin_func_ptr inbuilt(const char *s)
+{
+       const struct builtincmd *bp;
+
+       for (bp = builtincmds; bp->name; bp++)
+               if (strcmp(bp->name, s) == 0)
+                       return bp->builtinfunc;
+       return NULL;
+}
+
+static int forkexec(struct op *t, int *pin, int *pout, int no_fork, char **wp)
+{
+       pid_t newpid;
+       int i;
+       builtin_func_ptr bltin = NULL;
+       const char *bltin_name = NULL;
+       const char *cp;
+       struct ioword **iopp;
+       int resetsig;
+       char **owp;
+       int forked;
+
+       int *hpin = pin;
+       int *hpout = pout;
+       char *hwp;
+       smallint hinteractive;
+       smallint hintr;
+       smallint hexecflg;
+       struct brkcon *hbrklist;
+
+#if __GNUC__
+       /* Avoid longjmp clobbering */
+       (void) &pin;
+       (void) &pout;
+       (void) &wp;
+       (void) &bltin;
+       (void) &cp;
+       (void) &resetsig;
+       (void) &owp;
+#endif
+
+       DBGPRINTF(("FORKEXEC: t=%p, pin %p, pout %p, no_fork %d\n", t, pin,
+                       pout, no_fork));
+       DBGPRINTF7(("FORKEXEC: t->op_words is %s\n",
+                       ((t->op_words == NULL) ? "NULL" : t->op_words[0])));
+       owp = wp;
+       resetsig = 0;
+       if (t->op_type == TCOM) {
+               while (*wp++ != NULL)
+                       continue;
+               cp = *wp;
+
+               /* strip all initial assignments */
+               /* FIXME: 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) {
+                       if (t->ioact == NULL) {
+                               while ((cp = *owp++) != NULL && assign(cp, COPYV))
+                                       continue;
+                               DBGPRINTF(("FORKEXEC: returning setstatus(0)\n"));
+                               return setstatus(0);
+                       }
+               } else { /* cp != NULL */
+                       bltin_name = cp;
+                       bltin = inbuilt(cp);
+               }
+       }
+
+       forked = 0;
+       // We were pointing t->op_words to temporary (expanded) arg list:
+       // t->op_words = wp;
+       // and restored it later (in execute()), but "break"
+       // longjmps away (at "Run builtin" below), leaving t->op_words clobbered!
+       // See http://bugs.busybox.net/view.php?id=846.
+       // Now we do not touch t->op_words, but separately pass wp as param list
+       // to builtins
+       DBGPRINTF(("FORKEXEC: bltin %p, no_fork %d, owp %p\n", bltin,
+                       no_fork, owp));
+       /* Don't fork if it is a lone builtin (not in pipe)
+        * OR we are told to _not_ fork */
+       if ((!bltin || pin || pout)   /* not lone bltin AND */
+        && !no_fork                  /* not told to avoid fork */
+       ) {
+               /* Save values in case child alters them after vfork */
+               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, can't vfork()!\n"));
+                       return -1;
+               }
+
+               if (newpid > 0) {  /* Parent */
+                       /* Restore values */
+                       pin = hpin;
+                       pout = hpout;
+                       *wp = hwp;
+                       interactive = hinteractive;
+                       intr = hintr;
+                       brklist = hbrklist;
+                       execflg = hexecflg;
+
+                       closepipe(pin);
+                       return (pout == NULL ? setstatus(waitfor(newpid, 0)) : 0);
+               }
+
+               /* Child */
+               DBGPRINTF(("FORKEXEC: child process, bltin=%p (%s)\n", bltin, bltin_name));
+               if (interactive) {
+                       signal(SIGINT, SIG_IGN);
+                       signal(SIGQUIT, SIG_IGN);
+                       resetsig = 1;
+               }
+               interactive = 0;
+               intr = 0;
+               forked = 1;
+               brklist = 0;
+               execflg = 0;
+       }
+
+       if (owp)
+               while ((cp = *owp++) != NULL && assign(cp, COPYV))
+                       if (!bltin)
+                               export(lookup(cp));
+
+       if (pin) { /* NB: close _first_, then move fds! */
+               close(pin[1]);
+               xmove_fd(pin[0], 0);
+       }
+       if (pout) {
+               close(pout[0]);
+               xmove_fd(pout[1], 1);
+       }
+
+       iopp = t->ioact;
+       if (iopp) {
+               if (bltin && bltin != doexec) {
+                       prs(bltin_name);
+                       err(": can't redirect shell command");
+                       if (forked)
+                               _exit(-1);
+                       return -1;
+               }
+               while (*iopp) {
+                       if (iosetup(*iopp++, pin != NULL, pout != NULL)) {
+                               /* system-detected error */
+                               if (forked)
+                                       _exit(-1);
+                               return -1;
+                       }
+               }
+       }
+
+       if (bltin) {
+               if (forked || pin || pout) {
+                       /* Builtin in pipe: disallowed */
+                       /* TODO: allow "exec"? */
+                       prs(bltin_name);
+                       err(": can't run builtin as part of pipe");
+                       if (forked)
+                               _exit(-1);
+                       return -1;
+               }
+               /* Run builtin */
+               i = setstatus(bltin(t, wp));
+               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->op_type == TPAREN)
+               _exit(execute(t->left, NOPIPE, NOPIPE, /* no_fork: */ 1));
+       if (wp[0] == NULL)
+               _exit(EXIT_SUCCESS);
+
+       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", getpid()));
+
+       leave();
+       /* NOTREACHED */
+       return 0;
+}
+
+/*
+ * 0< 1> are ignored as required
+ * within pipelines.
+ */
+static int iosetup(struct ioword *iop, int pipein, int pipeout)
+{
+       int u = -1;
+       char *cp = NULL;
+       const char *msg;
+
+       DBGPRINTF(("IOSETUP: iop %p, pipein %i, pipeout %i\n", iop,
+                          pipein, pipeout));
+
+       if (iop->io_fd == IODEFAULT)    /* take default */
+               iop->io_fd = iop->io_flag & (IOREAD | IOHERE) ? 0 : 1;
+
+       if (pipein && iop->io_fd == 0)
+               return 0;
+
+       if (pipeout && iop->io_fd == 1)
+               return 0;
+
+       msg = iop->io_flag & (IOREAD | IOHERE) ? "open" : "create";
+       if ((iop->io_flag & IOHERE) == 0) {
+               cp = iop->io_name; /* huh?? */
+               cp = evalstr(cp, DOSUB | DOTRIM);
+               if (cp == 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, O_RDONLY);
+               break;
+
+       case IOHERE:
+       case IOHERE | IOXHERE:
+               u = herein(iop->io_name, iop->io_flag & IOXHERE);
+               cp = (char*)"here file";
+               break;
+
+       case IOWRITE | IOCAT:
+               u = open(cp, O_WRONLY);
+               if (u >= 0) {
+                       lseek(u, (long) 0, SEEK_END);
+                       break;
+               }
+               /* fall through to creation if >>file doesn't exist */
+
+       case IOWRITE:
+               u = creat(cp, 0666);
+               break;
+
+       case IODUP:
+               u = dup2(*cp - '0', iop->io_fd);
+               break;
+
+       case IOCLOSE:
+               close(iop->io_fd);
+               return 0;
+       }
+
+       if (u < 0) {
+               prs(cp);
+               prs(": can't ");
+               warn(msg);
+               return 1;
+       }
+       xmove_fd(u, iop->io_fd);
+       return 0;
+}
+
+/*
+ * 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;
+       smallint oheedint = heedint;
+
+       heedint = 0;
+       rv = 0;
+       do {
+               pid = wait(&s);
+               if (pid == -1) {
+                       if (errno != EINTR || canintr)
+                               break;
+               } else {
+                       rv = WAITSIG(s);
+                       if (rv != 0) {
+                               if (rv < ARRAY_SIZE(signame)) {
+                                       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 >= ARRAY_SIZE(signame) || signame[rv])
+                                       prs("\n");
+                               rv |= 0x80;
+                       } 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 const char *rexecve(char *c, char **v, char **envp)
+{
+       const char *sp;
+       char *tp;
+       int asis = 0;
+       char *name = c;
+
+       if (ENABLE_FEATURE_SH_STANDALONE) {
+               if (find_applet_by_name(name) >= 0) {
+                       /* We have to exec here since we vforked.  Running
+                        * run_applet_and_exit() won't work and bad things
+                        * will happen. */
+                       execve(bb_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 = global_env.linep;
+               for (; *sp != '\0'; tp++) {
+                       *tp = *sp++;
+                       if (*tp == ':') {
+                               asis = (*sp == '\0');
+                               break;
+                       }
+               }
+               if (tp != global_env.linep)
+                       *tp++ = '/';
+               strcpy(tp, c);
+
+               DBGPRINTF3(("REXECVE: global_env.linep is %s\n", global_env.linep));
+
+               execve(global_env.linep, v, envp);
+
+               switch (errno) {
+               case ENOEXEC:
+                       /* File is executable but file format isnt recognized */
+                       /* Run it as a shell script */
+                       /* (execve above didnt do it itself, unlike execvp) */
+                       *v = global_env.linep;
+                       v--;
+                       tp = *v;
+                       *v = (char*)DEFAULT_SHELL;
+                       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";
+               }
+       }
+       if (errno == ENOENT) {
+               exstat = 127; /* standards require this */
+               return "not found";
+       }
+       exstat = 126; /* mimic bash */
+       return "can't 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;
+
+       errpt = ev;
+       if (newenv(setjmp(errpt)) == 0) {
+               wdlist = NULL;
+               iolist = NULL;
+               pushio(argp, f);
+               global_env.iobase = global_env.iop;
+               yynerrs = 0;
+               failpt = rt;
+               if (setjmp(failpt) == 0 && yyparse() == 0)
+                       rv = execute(outtree, NOPIPE, NOPIPE, /* no_fork: */ 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 UNUSED_PARAM, char **args UNUSED_PARAM)
+{
+       int col;
+       const struct builtincmd *x;
+
+       printf("\n"
+               "Built-in commands:\n"
+               "------------------\n");
+
+       col = 0;
+       x = builtincmds;
+       while (x->name) {
+               col += printf("%c%s", ((col == 0) ? '\t' : ' '), x->name);
+               if (col > 60) {
+                       bb_putchar('\n');
+                       col = 0;
+               }
+               x++;
+       }
+#if ENABLE_FEATURE_SH_STANDALONE
+       {
+               const char *applet = applet_names;
+
+               while (*applet) {
+                       col += printf("%c%s", ((col == 0) ? '\t' : ' '), applet);
+                       if (col > 60) {
+                               bb_putchar('\n');
+                               col = 0;
+                       }
+                       applet += strlen(applet) + 1;
+               }
+       }
+#endif
+       puts("\n");
+       return EXIT_SUCCESS;
+}
+
+static int dolabel(struct op *t UNUSED_PARAM, char **args UNUSED_PARAM)
+{
+       return 0;
+}
+
+static int dochdir(struct op *t UNUSED_PARAM, char **args)
+{
+       const char *cp, *er;
+
+       cp = args[1];
+       if (cp == NULL) {
+               cp = homedir->value;
+               if (cp != NULL)
+                       goto do_cd;
+               er = ": no home directory";
+       } else {
+ do_cd:
+               if (chdir(cp) >= 0)
+                       return 0;
+               er = ": bad directory";
+       }
+       prs(cp != NULL ? cp : "cd");
+       err(er);
+       return 1;
+}
+
+static int doshift(struct op *t UNUSED_PARAM, char **args)
+{
+       int n;
+
+       n = args[1] ? getn(args[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 UNUSED_PARAM, char **args)
+{
+       const char *cp;
+
+       if (interactive) {
+               signal(SIGINT, SIG_DFL);
+               signal(SIGQUIT, SIG_DFL);
+       }
+       cp = rexecve(args[0], args, makenv(0, NULL));
+       prs(args[0]);
+       prs(": ");
+       err(cp);
+       return 1;
+}
+
+static int doumask(struct op *t UNUSED_PARAM, char **args)
+{
+       int i;
+       char *cp;
+
+       cp = args[1];
+       if (cp == NULL) {
+               i = umask(0);
+               umask(i);
+               printf("%04o\n", i);
+       } else {
+               i = bb_strtou(cp, NULL, 8);
+               if (errno) {
+                       err("umask: bad octal number");
+                       return 1;
+               }
+               umask(i);
+       }
+       return 0;
+}
+
+static int doexec(struct op *t, char **args)
+{
+       jmp_buf ex;
+       xint *ofail;
+       char **sv_words;
+
+       t->ioact = NULL;
+       if (!args[1])
+               return 1;
+
+       execflg = 1;
+       ofail = failpt;
+       failpt = ex;
+
+       sv_words = t->op_words;
+       t->op_words = args + 1;
+// TODO: test what will happen with "exec break" -
+// will it leave t->op_words pointing to garbage?
+// (see http://bugs.busybox.net/view.php?id=846)
+       if (setjmp(failpt) == 0)
+               execute(t, NOPIPE, NOPIPE, /* no_fork: */ 1);
+       t->op_words = sv_words;
+
+       failpt = ofail;
+       execflg = 0;
+
+       return 1;
+}
+
+static int dodot(struct op *t UNUSED_PARAM, char **args)
+{
+       int i;
+       const char *sp;
+       char *tp;
+       char *cp;
+       int maltmp;
+
+       DBGPRINTF(("DODOT: enter, t=%p, tleft %p, tright %p, global_env.linep is %s\n",
+               t, t->left, t->right, ((global_env.linep == NULL) ? "NULL" : global_env.linep)));
+
+       cp = args[1];
+       if (cp == NULL) {
+               DBGPRINTF(("DODOT: bad args, ret 0\n"));
+               return 0;
+       }
+       DBGPRINTF(("DODOT: cp is %s\n", cp));
+
+       sp = any('/', cp) ? ":" : path->value;
+
+       DBGPRINTF(("DODOT: sp is %s,  global_env.linep is %s\n",
+                          ((sp == NULL) ? "NULL" : sp),
+                          ((global_env.linep == NULL) ? "NULL" : global_env.linep)));
+
+       while (*sp) {
+               tp = global_env.linep;
+               while (*sp && (*tp = *sp++) != ':')
+                       tp++;
+               if (tp != global_env.linep)
+                       *tp++ = '/';
+               strcpy(tp, cp);
+
+               /* Original code */
+               i = open(global_env.linep, O_RDONLY);
+               if (i >= 0) {
+                       exstat = 0;
+                       maltmp = remap(i);
+                       DBGPRINTF(("DODOT: remap=%d, exstat=%d, global_env.iofd %d, i %d, global_env.linep is %s\n",
+                               maltmp, exstat, global_env.iofd, i, global_env.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 UNUSED_PARAM, char **args)
+{
+       int i;
+       char *cp;
+
+       cp = args[1];
+       if (cp != NULL) {
+               i = getn(cp);
+               if (i == 0)
+                       return 0;
+       } else
+               i = -1;
+       setstatus(waitfor(i, 1));
+       return 0;
+}
+
+static int doread(struct op *t UNUSED_PARAM, char **args)
+{
+       char *cp, **wp;
+       int nb = 0;
+       int nl = 0;
+
+       if (args[1] == NULL) {
+               err("Usage: read name ...");
+               return 1;
+       }
+       for (wp = args + 1; *wp; wp++) {
+               for (cp = global_env.linep; !nl && cp < elinep - 1; cp++) {
+                       nb = nonblock_safe_read(STDIN_FILENO, cp, sizeof(*cp));
+                       if (nb != sizeof(*cp))
+                               break;
+                       nl = (*cp == '\n');
+                       if (nl || (wp[1] && any(*cp, ifs->value)))
+                               break;
+               }
+               *cp = '\0';
+               if (nb <= 0)
+                       break;
+               setval(lookup(*wp), global_env.linep);
+       }
+       return nb <= 0;
+}
+
+static int doeval(struct op *t UNUSED_PARAM, char **args)
+{
+       return RUN(awordlist, args + 1, wdchar);
+}
+
+static int dotrap(struct op *t UNUSED_PARAM, char **args)
+{
+       int n, i;
+       int resetsig;
+
+       if (args[1] == NULL) {
+               for (i = 0; i <= _NSIG; i++)
+                       if (trap[i]) {
+                               prn(i);
+                               prs(": ");
+                               prs(trap[i]);
+                               prs("\n");
+                       }
+               return 0;
+       }
+       resetsig = isdigit(args[1][0]);
+       for (i = resetsig ? 1 : 2; args[i] != NULL; ++i) {
+               n = getsig(args[i]);
+               freecell(trap[n]);
+               trap[n] = 0;
+               if (!resetsig) {
+                       if (args[1][0] != '\0') {
+                               trap[n] = strsave(args[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;
+
+       n = getn(s);
+       if (n < 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 UNUSED_PARAM, char **args)
+{
+       return brkcontin(args[1], 1);
+}
+
+static int docontinue(struct op *t UNUSED_PARAM, char **args)
+{
+       return brkcontin(args[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 {
+               bc = brklist;
+               if (bc == NULL)
+                       break;
+               brklist = bc->nextlev;
+       } while (--nl);
+       if (nl) {
+               err("bad break/continue level");
+               return 1;
+       }
+       isbreak = (val != 0);
+       longjmp(bc->brkpt, 1);
+       /* NOTREACHED */
+}
+
+static int doexit(struct op *t UNUSED_PARAM, char **args)
+{
+       char *cp;
+
+       execflg = 0;
+       cp = args[1];
+       if (cp != NULL)
+               setstatus(getn(cp));
+
+       DBGPRINTF(("DOEXIT: calling leave(), t=%p\n", t));
+
+       leave();
+       /* NOTREACHED */
+       return 0;
+}
+
+static int doexport(struct op *t UNUSED_PARAM, char **args)
+{
+       rdexp(args + 1, export, EXPORT);
+       return 0;
+}
+
+static int doreadonly(struct op *t UNUSED_PARAM, char **args)
+{
+       rdexp(args + 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++)
+                                       continue;
+                               *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 UNUSED_PARAM, char **args)
+{
+       struct var *vp;
+       char *cp;
+       int n;
+
+       cp = args[1];
+       if (cp == NULL) {
+               for (vp = vlist; vp; vp = vp->next)
+                       varput(vp->name, STDOUT_FILENO);
+               return 0;
+       }
+       if (*cp == '-') {
+               args++;
+               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 (args[1]) {
+               args[0] = dolv[0];
+               for (n = 1; args[n]; n++)
+                       setarea((char *) args[n], 0);
+               dolc = n - 1;
+               dolv = args;
+               setval(lookup("#"), putn(dolc));
+               setarea((char *) (dolv - 1), 0);
+       }
+       return 0;
+}
+
+static void varput(char *s, int out)
+{
+       if (isalnum(*s) || *s == '_') {
+               xwrite_str(out, s);
+               xwrite(out, "\n", 1);
+       }
+}
+
+
+/*
+ * Copyright (c) 1999 Herbert Xu <herbert@debian.org>
+ * This file contains code for the times builtin.
+ */
+static void times_fmt(char *buf, clock_t val, unsigned clk_tck)
+{
+       unsigned min, sec;
+       if (sizeof(val) > sizeof(int))
+               sec = ((unsigned long)val) / clk_tck;
+       else
+               sec = ((unsigned)val) / clk_tck;
+       min = sec / 60;
+#if ENABLE_DESKTOP
+       sprintf(buf, "%um%u.%03us", min, (sec - min * 60),
+       /* msec: */ ((unsigned)(val - (clock_t)sec * clk_tck)) * 1000 / clk_tck
+       );
+#else
+       sprintf(buf, "%um%us", min, (sec - min * 60));
+#endif
+}
+
+static int dotimes(struct op *t UNUSED_PARAM, char **args UNUSED_PARAM)
+{
+       struct tms buf;
+       unsigned clk_tck = sysconf(_SC_CLK_TCK);
+       /* How much do we need for "NmN.NNNs" ? */
+       enum { TIMEBUF_SIZE = sizeof(int)*3 + sizeof(int)*3 + 6 };
+       char u[TIMEBUF_SIZE], s[TIMEBUF_SIZE];
+       char cu[TIMEBUF_SIZE], cs[TIMEBUF_SIZE];
+
+       times(&buf);
+
+       times_fmt(u, buf.tms_utime, clk_tck);
+       times_fmt(s, buf.tms_stime, clk_tck);
+       times_fmt(cu, buf.tms_cutime, clk_tck);
+       times_fmt(cs, buf.tms_cstime, clk_tck);
+
+       printf("%s %s\n%s %s\n", u, s, cu, cs);
+       return 0;
+}
+
+
+/* -------- 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) &wp;
+       (void) &ap;
+#endif
+
+       DBGPRINTF4(("EVAL: enter, f=%d\n", f));
+
+       wp = NULL;
+       wb = NULL;
+       wf = NULL;
+       errpt = ev;
+       if (newenv(setjmp(errpt)) == 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 *) NULL, 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 int expand(const char *cp, struct wdblock **wbp, int f)
+{
+       jmp_buf ev;
+       char *xp;
+
+#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))
+       ) {
+               xp = strsave(cp, areanum);
+               if (f & DOTRIM)
+                       unquote(xp);
+               *wbp = addword(xp, *wbp);
+               return 1;
+       }
+       errpt = ev;
+       if (newenv(setjmp(errpt)) == 0) {
+               PUSHIO(aword, cp, strchar);
+               global_env.iobase = global_env.iop;
+               while ((xp = blank(f)) && gflg == 0) {
+                       global_env.linep = xp;
+                       xp = strsave(xp, areanum);
+                       if ((f & DOGLOB) == 0) {
+                               if (f & DOTRIM)
+                                       unquote(xp);
+                               *wbp = addword(xp, *wbp);
+                       } else
+                               *wbp = glob(xp, *wbp);
+               }
+               quitenv();
+       } else
+               gflg = 1;
+       return gflg == 0;
+}
+
+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
+               ) {
+// TODO: I suspect that
+// char *evalstr(char *cp, int f)  is actually
+// const char *evalstr(const char *cp, int f)!
+                       cp = (char*)"";
+               }
+               DELETE(wb);
+       } else
+               cp = NULL;
+       return cp;
+}
+
+
+/*
+ * 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 = global_env.linep;
+       scanequals = f & DOKEY;
+       foundequals = 0;
+
+ loop:
+       c = subgetc('"', foundequals);
+       switch (c) {
+       case 0:
+               if (sp == global_env.linep)
+                       return 0;
+               *global_env.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;
+                       *global_env.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;
+               }
+               *global_env.linep++ = c;
+       }
+       *global_env.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;
+                       global_env.iop->task = XGRAVE;
+                       goto again;
+               }
+               if (c == '$') {
+                       c = dollar(quoted);
+                       if (c == 0) {
+                               global_env.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 = global_env.linep;
+       if (c != '{') {
+               *global_env.linep++ = c;
+               if (isalpha(c) || c == '_') {
+                       while ((c = readc()) != 0 && (isalnum(c) || c == '_'))
+                               if (global_env.linep < elinep)
+                                       *global_env.linep++ = c;
+                       unget(c);
+               }
+               c = 0;
+       } else {
+               oiop = global_env.iop;
+               otask = global_env.iop->task;
+
+               global_env.iop->task = XOTHER;
+               while ((c = subgetc('"', 0)) != 0 && c != '}' && c != '\n')
+                       if (global_env.linep < elinep)
+                               *global_env.linep++ = c;
+               if (oiop == global_env.iop)
+                       global_env.iop->task = otask;
+               if (c != '}') {
+                       err("unclosed ${");
+                       gflg = 1;
+                       return c;
+               }
+       }
+       if (global_env.linep >= elinep) {
+               err("string in ${} too long");
+               gflg = 1;
+               global_env.linep -= 10;
+       }
+       *global_env.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 */
+                       global_env.linep = s;
+                       PUSHIO(awordlist, dolv + 1, dolchar);
+                       return 0;
+               } else {                                /* trap the nasty ${=} */
+                       s[0] = '1';
+                       s[1] = '\0';
+               }
+       }
+       vp = lookup(s);
+       dolp = vp->value;
+       if (dolp == null) {
+               switch (c) {
+               case '=':
+                       if (isdigit(*s)) {
+                               err("can't use ${...=...} with $n");
+                               gflg = 1;
+                               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 = 1;
+                       break;
+               }
+       } else if (c == '+')
+               dolp = strsave(cp, areanum);
+       if (FLAG['u'] && dolp == null) {
+               prs("unset variable: ");
+               err(s);
+               gflg = 1;
+       }
+       global_env.linep = s;
+       PUSHIO(aword, dolp, quoted ? qstrchar : strchar);
+       return 0;
+}
+
+/*
+ * Run the command in `...` and read its output.
+ */
+
+static int grave(int quoted)
+{
+       /* moved to G: static char child_cmd[LINELIM]; */
+
+       const char *cp;
+       int i;
+       int j;
+       int pf[2];
+       const 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 = global_env.iop->argp->aword; *cp != '`'; cp++) {
+               if (*cp == 0) {
+                       err("no closing `");
+                       return 0;
+               }
+       }
+
+       /* string copy with dollar expansion */
+       src = global_env.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;
+                       /* moved to G to reduce stack usage
+                       char var_name[LINELIM];
+                       char alt_value[LINELIM];
+                       */
+#define var_name (G.grave__var_name)
+#define alt_value (G.grave__alt_value)
+                       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++;
+                               }
+                       }
+#undef var_name
+#undef alt_value
+               } else {
+                       *dest++ = *src++;
+                       count++;
+                       ignore_once = 0;
+               }
+       }
+       *dest = '\0';
+
+       if (openpipe(pf) < 0)
+               return 0;
+
+       while ((i = vfork()) == -1 && errno == EAGAIN)
+               continue;
+
+       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); // safe_waitpid?
+               global_env.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);
+
+       /* Testcase where below checks are needed:
+        * close stdout & run this script:
+        *  files=`ls`
+        *  echo "$files" >zz
+        */
+       xmove_fd(pf[1], 1);
+       if (pf[0] != 1)
+               close(pf[0]);
+
+       argument_list[0] = (char *) DEFAULT_SHELL;
+       argument_list[1] = (char *) "-c";
+       argument_list[2] = child_cmd;
+       argument_list[3] = NULL;
+
+       cp = rexecve(argument_list[0], argument_list, makenv(1, wb));
+       prs(argument_list[0]);
+       prs(": ");
+       err(cp);
+       _exit(EXIT_FAILURE);
+}
+
+
+static char *unquote(char *as)
+{
+       char *s;
+
+       s = as;
+       if (s != 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 const char spcl[] ALIGN1= "[?*";
+
+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), NULL); 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);
+               }
+               if (cl->w_nword) {
+                       for (i = 0; i < cl->w_nword; i++)
+                               unquote(cl->w_words[i]);
+                       qsort_string_vector(cl->w_words, 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;
+       dp = cp = get_space((int) (pp - np) + 3);
+       while (np < pp)
+               *cp++ = *np++;
+       *cp++ = '.';
+       *cp = '\0';
+       gp = cp = get_space(strlen(pp) + 1);
+       while (*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 = get_space((int)(end1 - start1) + strlen(middle) + strlen(end) + 2);
+       xp = start1;
+       while (xp != end1)
+               *op++ = *xp++;
+       xp = middle;
+       while (*xp != '\0')
+               *op++ = *xp++;
+       strcpy(op, end);
+       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;
+}
+
+
+/* -------- word.c -------- */
+
+static struct wdblock *newword(int nw)
+{
+       struct wdblock *wb;
+
+       wb = get_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);
+       nw = wb->w_nword;
+       if (nw >= 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;
+       }
+       nb = sizeof(*wd) * wb->w_nword;
+       wd = get_space(nb);
+       memcpy(wd, wb->w_words, nb);
+       DELETE(wb);                     /* perhaps should done by caller */
+       return wd;
+}
+
+
+/* -------- io.c -------- */
+
+/*
+ * shell IO
+ */
+
+static int my_getc(int ec)
+{
+       int c;
+
+       if (global_env.linep > elinep) {
+               while ((c = readc()) != '\n' && c)
+                       continue;
+               err("input line too long");
+               gflg = 1;
+               return c;
+       }
+       c = readc();
+       if ((ec != '\'') && (ec != '`') && (global_env.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 (global_env.iop >= global_env.iobase)
+               global_env.iop->peekc = c;
+}
+
+static int eofc(void)
+{
+       return global_env.iop < global_env.iobase || (global_env.iop->peekc == 0 && global_env.iop->prev == 0);
+}
+
+static int readc(void)
+{
+       int c;
+
+       RCPRINTF(("READC: global_env.iop %p, global_env.iobase %p\n", global_env.iop, global_env.iobase));
+
+       for (; global_env.iop >= global_env.iobase; global_env.iop--) {
+               RCPRINTF(("READC: global_env.iop %p, peekc 0x%x\n", global_env.iop, global_env.iop->peekc));
+               c = global_env.iop->peekc;
+               if (c != '\0') {
+                       global_env.iop->peekc = 0;
+                       return c;
+               }
+               if (global_env.iop->prev != 0) {
+                       c = (*global_env.iop->iofn)(global_env.iop->argp, global_env.iop);
+                       if (c != '\0') {
+                               if (c == -1) {
+                                       global_env.iop++;
+                                       continue;
+                               }
+                               if (global_env.iop == iostack)
+                                       ioecho(c);
+                               global_env.iop->prev = c;
+                               return c;
+                       }
+                       if (global_env.iop->task == XIO && global_env.iop->prev != '\n') {
+                               global_env.iop->prev = 0;
+                               if (global_env.iop == iostack)
+                                       ioecho('\n');
+                               return '\n';
+                       }
+               }
+               if (global_env.iop->task == XIO) {
+                       if (multiline) {
+                               global_env.iop->prev = 0;
+                               return 0;
+                       }
+                       if (interactive && global_env.iop == iostack + 1) {
+#if ENABLE_FEATURE_EDITING
+                               current_prompt = prompt->value;
+#else
+                               prs(prompt->value);
+#endif
+                       }
+               }
+       }                                                       /* FOR */
+
+       if (global_env.iop >= iostack) {
+               RCPRINTF(("READC: return 0, global_env.iop %p\n", global_env.iop));
+               return 0;
+       }
+
+       DBGPRINTF(("READC: leave()...\n"));
+       leave();
+       /* NOTREACHED */
+       return 0;
+}
+
+static void ioecho(char c)
+{
+       if (FLAG['v'])
+               write(STDERR_FILENO, &c, sizeof c);
+}
+
+static void pushio(struct ioarg *argp, int (*fn) (struct ioarg *))
+{
+       DBGPRINTF(("PUSHIO: argp %p, argp->afid 0x%x, global_env.iop %p\n", argp,
+                          argp->afid, global_env.iop));
+
+       /* Set env ptr for io source to next array spot and check for array overflow */
+       if (++global_env.iop >= &iostack[NPUSH]) {
+               global_env.iop--;
+               err("Shell input nested too deeply");
+               gflg = 1;
+               return;
+       }
+
+       /* We did not overflow the NPUSH array spots so setup data structs */
+
+       global_env.iop->iofn = (int (*)(struct ioarg *, struct io *)) fn;       /* Store data source func ptr */
+
+       if (argp->afid != AFID_NOBUF)
+               global_env.iop->argp = argp;
+       else {
+
+               global_env.iop->argp = ioargstack + (global_env.iop - iostack); /* MAL - index into stack */
+               *global_env.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 (global_env.iop == &iostack[0])
+                       global_env.iop->argp->afbuf = &mainbuf;
+               else
+                       global_env.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(global_env.iop->argp->afile) == 0)
+                       && (global_env.iop == &iostack[0]
+                               || lseek(global_env.iop->argp->afile, 0L, SEEK_CUR) != -1)) {
+                       if (++bufid == AFID_NOBUF)      /* counter rollover check, AFID_NOBUF = 11111111  */
+                               bufid = AFID_ID;        /* AFID_ID = 0 */
+
+                       global_env.iop->argp->afid = bufid;     /* assign buffer id */
+               }
+
+               DBGPRINTF(("PUSHIO: iostack %p,  global_env.iop %p, afbuf %p\n",
+                                  iostack, global_env.iop, global_env.iop->argp->afbuf));
+               DBGPRINTF(("PUSHIO: mbuf %p, sbuf %p, bid %d, global_env.iop %p\n",
+                                  &mainbuf, &sharedbuf, bufid, global_env.iop));
+
+       }
+
+       global_env.iop->prev = ~'\n';
+       global_env.iop->peekc = 0;
+       global_env.iop->xchar = 0;
+       global_env.iop->nlcount = 0;
+
+       if (fn == filechar || fn == linechar)
+               global_env.iop->task = XIO;
+       else if (fn == (int (*)(struct ioarg *)) gravechar
+             || fn == (int (*)(struct ioarg *)) qgravechar)
+               global_env.iop->task = XGRAVE;
+       else
+               global_env.iop->task = XOTHER;
+}
+
+static struct io *setbase(struct io *ip)
+{
+       struct io *xp;
+
+       xp = global_env.iobase;
+       global_env.iobase = ip;
+       return xp;
+}
+
+/*
+ * Input generating functions
+ */
+
+/*
+ * Produce the characters of a string, then a newline, then NUL.
+ */
+static int nlchar(struct ioarg *ap)
+{
+       char c;
+
+       if (ap->aword == NULL)
+               return '\0';
+       c = *ap->aword++;
+       if (c == '\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;
+
+       wl = ap->awordlist;
+       if (wl == NULL)
+               return 0;
+       if (*wl != NULL) {
+               c = *(*wl)++;
+               if (c != 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;
+
+       wp = *ap->awordlist++;
+       if (wp != 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;
+       c = *ap->aword++;
+       if (c == '\0') {
+               ap->aword = NULL;
+               return ' ';
+       }
+       return c;
+}
+
+/*
+ * Produce the characters from a single word (string).
+ */
+static int strchar(struct ioarg *ap)
+{
+       if (ap->aword == NULL)
+               return 0;
+       return *ap->aword++;
+}
+
+/*
+ * Produce quoted characters from a single word (string).
+ */
+static int qstrchar(struct ioarg *ap)
+{
+       int c;
+
+       if (ap->aword == NULL)
+               return 0;
+       c = *ap->aword++;
+       if (c)
+               c |= QUOTE;
+       return c;
+}
+
+/*
+ * 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) {
+               i = (ap->afid != bp->id);
+               if (i || bp->bufp == bp->ebufp) {
+                       if (i)
+                               lseek(ap->afile, ap->afpos, SEEK_SET);
+
+                       i = nonblock_safe_read(ap->afile, bp->buf, sizeof(bp->buf));
+                       if (i <= 0) {
+                               closef(ap->afile);
+                               return 0;
+                       }
+
+                       bp->id = ap->afid;
+                       bp->bufp = bp->buf;
+                       bp->ebufp = bp->bufp + i;
+               }
+
+               ap->afpos++;
+               return *bp->bufp++ & 0177;
+       }
+#if ENABLE_FEATURE_EDITING
+       if (interactive && isatty(ap->afile)) {
+               /* moved to G: static char filechar_cmdbuf[BUFSIZ]; */
+               static int position = 0, size = 0;
+
+               while (size == 0 || position >= size) {
+                       size = read_line_input(current_prompt, filechar_cmdbuf, BUFSIZ, line_input_state);
+                       if (size < 0) /* Error/EOF */
+                               exit(EXIT_SUCCESS);
+                       position = 0;
+                       /* if Ctrl-C, size == 0 and loop will repeat */
+               }
+               c = filechar_cmdbuf[position];
+               position++;
+               return c;
+       }
+#endif
+       i = nonblock_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 (nonblock_safe_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;
+
+       c = qgravechar(ap, iop) & ~QUOTE;
+       if (c == '\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;
+
+       c = filechar(ap);
+       if (c == '\n') {
+               if (!multiline) {
+                       closef(ap->afile);
+                       ap->afile = -1;         /* illegal value */
+               }
+       }
+       return c;
+}
+
+/*
+ * Remap fd into shell's fd space
+ */
+static int remap(int fd)
+{
+       int i;
+       int map[NOFILE];
+       int newfd;
+
+       DBGPRINTF(("REMAP: fd=%d, global_env.iofd=%d\n", fd, global_env.iofd));
+
+       if (fd < global_env.iofd) {
+               for (i = 0; i < NOFILE; i++)
+                       map[i] = 0;
+
+               do {
+                       map[fd] = 1;
+                       newfd = dup(fd);
+                       fd = newfd;
+               } while (fd >= 0 && fd < global_env.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;
+
+       i = pipe(pv);
+       if (i < 0)
+               err("can't create pipe - try again");
+       return i;
+}
+
+static void closepipe(int *pv)
+{
+       if (pv != NULL) {
+               close(pv[0]);
+               close(pv[1]);
+       }
+}
+
+
+/* -------- here.c -------- */
+
+/*
+ * here documents
+ */
+
+static void markhere(char *s, struct ioword *iop)
+{
+       struct here *h, *lh;
+
+       DBGPRINTF7(("MARKHERE: enter, s=%p\n", s));
+
+       h = get_space(sizeof(struct here));
+       if (h == NULL)
+               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) ? '\0' : '\'');
+}
+
+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 /* NUL or ' */);
+
+       /* 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);
+       errpt = ev;
+       if (newenv(setjmp(errpt)) != 0)
+               unlink(tname);
+       else {
+               pushio(global_env.iop->argp, (int (*)(struct ioarg *)) global_env.iop->iofn);
+               global_env.iobase = global_env.iop;
+               for (;;) {
+                       if (interactive && global_env.iop <= iostack) {
+#if ENABLE_FEATURE_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, O_RDONLY);
+       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;
+               errpt = ev;
+               if (newenv(setjmp(errpt)) == 0) {
+                       PUSHIO(afile, hf, herechar);
+                       setbase(global_env.iop);
+                       while ((c = subgetc(0, 0)) != 0) {
+                               c &= ~QUOTE;
+                               write(tf, &c, sizeof c);
+                       }
+                       quitenv();
+               } else
+                       unlink(tname);
+               close(tf);
+               tf = open(tname, O_RDONLY);
+               unlink(tname);
+               return tf;
+       }
+       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;
+               }
+       }
+}
+
+
+/* -------- sh.c -------- */
+/*
+ * shell
+ */
+
+int msh_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int msh_main(int argc, char **argv)
+{
+       int f;
+       char *s;
+       int cflag;
+       char *name, **ap;
+       int (*iof) (struct ioarg *);
+
+       INIT_G();
+
+       sharedbuf.id = AFID_NOBUF;
+       mainbuf.id = AFID_NOBUF;
+       elinep = line + sizeof(line) - 5;
+
+#if ENABLE_FEATURE_EDITING
+       line_input_state = new_line_input_t(FOR_SHELL);
+#endif
+
+       DBGPRINTF(("MSH_MAIN: argc %d, environ %p\n", argc, environ));
+
+       initarea();
+       ap = environ;
+       if (ap != 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) {
+               /* Can be merged with same string elsewhere in bbox */
+               if (geteuid() == 0)
+                       setval(path, bb_default_root_path);
+               else
+                       setval(path, bb_default_path);
+       }
+       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");
+#if ENABLE_FEATURE_EDITING_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");
+#if ENABLE_FEATURE_EDITING_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 = 1;
+                               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 = mshdbg_var->value[0] - '0';
+                       if (mshdbg < 0)
+                               mshdbg = 0;
+#endif
+                       DBGPRINTF(("MSH_MAIN: calling newfile()\n"));
+
+                       name = *++argv;
+                       if (newfile(name))
+                               exit(EXIT_FAILURE);  /* Exit on error */
+               }
+       }
+
+       setdash();
+
+       /* This won't be true if PUSHIO has been called, say from newfile() above */
+       if (global_env.iop < iostack) {
+               PUSHIO(afile, 0, iof);
+               if (isatty(0) && isatty(1) && !cflag) {
+                       interactive = 1;
+#if !ENABLE_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 = 1;
+               f = open(".profile", O_RDONLY);
+               if (f >= 0)
+                       next(remap(f));
+               f = open("/etc/profile", O_RDONLY);
+               if (f >= 0)
+                       next(remap(f));
+       }
+       if (interactive)
+               signal(SIGTERM, sig);
+
+       if (signal(SIGINT, SIG_IGN) != SIG_IGN)
+               signal(SIGINT, onintr);
+
+/* Handle "msh SCRIPT VAR=val params..." */
+/* Disabled: bash does not do it! */
+#if 0
+       argv++;
+       /* skip leading args of the form VAR=val */
+       while (*argv && assign(*argv, !COPYV)) {
+               argc--;
+               argv++;
+       }
+       argv--;
+#endif
+       dolv = argv;
+       dolc = argc;
+       dolv[0] = name;
+
+       setval(lookup("#"), putn((--dolc < 0) ? (dolc = 0) : dolc));
+
+       DBGPRINTF(("MSH_MAIN: begin FOR loop, interactive %d, global_env.iop %p, iostack %p\n", interactive, global_env.iop, iostack));
+
+       for (;;) {
+               if (interactive && global_env.iop <= iostack) {
+#if ENABLE_FEATURE_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"));
+}
+
+
+/*
+ * 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/shell/msh_function.patch b/shell/msh_function.patch
new file mode 100644 (file)
index 0000000..270b9ee
--- /dev/null
@@ -0,0 +1,350 @@
+This is a "function" patch for msh which is in use by some busybox
+users. Unfortunately it is far too buggy to be applied, but maybe
+it's a useful starting point for future work.
+
+Function-related code is delimited by comments of the form
+       //funccode:start
+       ...
+       //funccode:end
+for ease of grepping
+
+An example of buggy behavior:
+
+#f() {
+#    echo foo
+#    echo test`echo bar >&2`
+#    echo END f
+#}
+
+function g {
+#    echo 2 foo
+#    echo 2 test`echo 2 bar >&2`
+#    f
+    echo END g
+#    echo "1:'$1' 2:'$2'"
+}
+
+# Even this first block fails - it does not even call functions!
+# (replacing "echo END g" above with "echo END" makes it run ok)
+echo DRY RUN
+    echo 2 foo
+    echo 2 test`echo 2 bar >&2`
+    echo END g
+    echo "1:'$1' 2:'$2'"
+    echo foo
+    echo test`echo bar >&2`
+    echo END f
+echo END DRY RUN
+
+exit
+
+# This would fail too
+g "$1-one" "two$2"
+echo DONE
+
+
+
+diff -d -urpN busybox.7/shell/msh.c busybox.8/shell/msh.c
+--- busybox.7/shell/msh.c      2008-06-09 09:34:45.000000000 +0200
++++ busybox.8/shell/msh.c      2008-06-09 09:38:17.000000000 +0200
+@@ -89,6 +89,14 @@ static char *itoa(int n)
+ //#define MSHDEBUG 4
++/* Used only in "function" support code */
++#ifdef KSDBG //funccode:start
++      #define KSDBG_PRINT_FUNCNAME fprintf(stderr, "in %s\n", __FUNCTION__)
++#else
++      #define KSDBG_PRINT_FUNCNAME ((void)0)
++#endif
++//funccode:end
++
+ #ifdef MSHDEBUG
+ static int mshdbg = MSHDEBUG;
+@@ -220,6 +228,9 @@ struct op {
+ #define TASYNC  16      /* c & */
+ /* Added to support "." file expansion */
+ #define TDOT    17
++#define TFUNC   18 //funccode:start
++#define TRETURN 19
++ //funccode:end
+ /* Strings for names to make debug easier */
+ #ifdef MSHDEBUG
+@@ -319,6 +330,27 @@ struct region {
+       int area;
+ };
++static int func_finished; //funccode:start
++struct func {
++      char* name;
++      int begin_addr; /* pos in buffer of function */
++      int end_addr;
++};
++#define MAX_FUNCS 100
++
++static struct func funcs[MAX_FUNCS];
++
++/* the max DEPTH of function call */
++#define MAX_DEPTH 100
++static struct _frame_s {
++      int argc;
++      char **argv;
++      int saved_return_addr;
++} frame[MAX_DEPTH];
++
++static void register_func(int begin, int end);
++static struct func* find_func(char* name);
++static void exec_func(struct func* f); //funccode:end
+ /* -------- grammar stuff -------- */
+ typedef union {
+@@ -347,6 +379,8 @@ typedef union {
+ #define IN      272
+ /* Added for "." file expansion */
+ #define DOT     273
++#define FUNC    274 //funccode:start
++#define RETURN  275 //funccode:end
+ #define       YYERRCODE 300
+@@ -1722,6 +1756,40 @@ static struct op *simple(void)
+                       (void) synio(0);
+                       break;
++              case FUNC: { //funccode:start
++                      int stop_flag;
++                      int number_brace;
++                      int func_begin;
++                      int func_end;
++                      int c;
++                      while ((c = my_getc(0)) == ' ' || c == '\t'|| c == '\n') /* skip whitespace */
++                              continue;
++                      stop_flag = 1;
++                      number_brace = 0;
++                      func_begin = global_env.iobase->argp->afpos;
++                      while (stop_flag) {
++                              if (c == '{')
++                                      number_brace++;
++                              if (c == '}')
++                                      number_brace--;
++                              if (!number_brace) /* if we reach the brace of most outsite */
++                                      stop_flag = 0;
++                              c = my_getc(0);
++                      }
++                      unget(c);
++                      unget(c);
++                      func_end = global_env.iobase->argp->afpos;
++                      register_func(func_begin, func_end);
++                      peeksym = 0;
++                      t = NULL;
++                      return t;
++              }
++              case RETURN:
++                      func_finished = 1;
++                      peeksym = 0;
++                      t = NULL;
++                      return t; //funccode:end
++
+               case WORD:
+                       if (t == NULL) {
+                               t = newtp();
+@@ -2265,6 +2333,13 @@ static int yylex(int cf)
+       case ')':
+               startl = 1;
+               return c;
++      case '{': //funccode:start
++              c = collect(c, '}');
++              if (c != '\0')
++                      return c;
++              break;
++      case '}':
++              return RETURN; //funccode:end
+       }
+       unget(c);
+@@ -2293,9 +2368,172 @@ static int yylex(int cf)
+       }
+       yylval.cp = strsave(line, areanum);
++      /* To identify a subroutine */ //funccode:start
++      c = my_getc(0);
++      if (c && any(c, "(")) {
++              c = my_getc(0);
++              if (c && any(c, ")"))
++                      return FUNC;
++              zzerr();
++      } else
++              unget(c);
++      /* read the first char */
++      /* To identify a function */
++      if (strcmp(yylval.cp, "function") == 0) {
++              int ret = yylex(0);
++              /* read the function name after "function" */
++              if (ret == WORD)
++                      return (FUNC);
++              zzerr();
++      }
++      {
++              struct func* f = find_func(yylval.cp);
++              if (f != NULL) {
++                      exec_func(f);
++                      return RETURN;
++              }
++      }
++      if (yylval.cp != NULL && strcmp(yylval.cp, "return") == 0) {
++              return RETURN;
++      } //funccode:end
+       return WORD;
+ }
++static void register_func(int begin, int end) //funccode:start
++{
++      struct func *p;
++      int i;
++        for (i = 0; i < MAX_FUNCS; i++) {
++              if (funcs[i].name == NULL) {
++                      p = &funcs[i];
++                      break;
++              }
++      }
++      if (i == MAX_FUNCS) {
++              fprintf(stderr, "Too much functions beyond limit\n");
++              leave();
++      }
++      p->name = xstrdup(yylval.cp);
++      //fprintf(stderr, "register function,%d,%d,%s\n", begin, end, p->name);
++      KSDBG_PRINT_FUNCNAME;
++      /* io stream */
++      p->begin_addr = begin;
++      p->end_addr = end;
++}
++
++static struct func* find_func(char* name)
++{
++      int i;
++      for (i = 0; i < MAX_FUNCS; i++) {
++              if (funcs[i].name == NULL)
++                      continue;
++              if (!strcmp(funcs[i].name, name))
++                      return &funcs[i];
++      }
++      KSDBG_PRINT_FUNCNAME;
++      //fprintf(stderr, "not found the function %s\n", name);
++      return NULL;
++      //zzerr();
++}
++
++/* Begin to execute the function */
++static int cur_frame = 0;
++
++static void exec_func(struct func* f)
++{
++      int c;
++      int temp_argc;
++      char** temp_argv;
++      struct iobuf *bp;
++
++      /* create a new frame, save the argument and return address to this frame */
++      frame[cur_frame].argc = dolc;
++      frame[cur_frame].argv = dolv;
++
++      cur_frame++;
++      /* do some argument parse and set arguments */
++      temp_argv = xmalloc(sizeof(char *));
++      temp_argv[0] = xstrdup(f->name);
++      temp_argc = 0;
++      global_env.iop->argp->afpos--;
++      global_env.iop->argp->afbuf->bufp--;
++//    unget(c);
++      while (((c = yylex(0)) != '\n') && (yylval.cp != NULL)) {
++              temp_argc++;
++              temp_argv = xrealloc(temp_argv, sizeof(char *) * (temp_argc+1));
++              /* parse $ var if passed argument is a variable */
++              if (yylval.cp[0] == '$') {
++                      struct var *arg = lookup(&yylval.cp[1]);
++                      temp_argv[temp_argc] = xstrdup(arg->value);
++                      //fprintf(stderr, "arg->value=%s\n", arg->value);
++              } else {
++                      temp_argv[temp_argc] = xstrdup(yylval.cp);
++                      //fprintf(stderr, "ARG:%s\n", yylval.cp);
++              }
++      }
++      /*
++      global_env.iop->argp->afpos--;
++      global_env.iop->argp->afbuf->bufp--;
++      */
++      dolc = temp_argc;
++      dolv = temp_argv;
++      //unget(c);
++      //while ((c = my_getc(0)) == ' ' || c == '\t')  /* Skip whitespace */
++      //      continue;
++      //unget(c);
++      frame[cur_frame].saved_return_addr = global_env.iop->argp->afpos;
++
++      /* get function begin address and execute this function */
++
++      bp = global_env.iop->argp->afbuf;
++      bp->bufp = &(bp->buf[f->begin_addr]);
++      global_env.iop->argp->afpos = f->begin_addr;
++
++      /* func_finished=0 means we are in a function and func_finished=1 means we are executing a function */
++      func_finished = 0;
++
++      //fprintf(stderr, "exec function %s\n", f->name);
++      KSDBG_PRINT_FUNCNAME;
++      for (;;) {
++              //fprintf(stderr, "afpos=%d,%s\n", global_env.iop->argp->afpos, yylval.cp);
++              if (global_env.iop->argp->afpos == f->end_addr)
++                      break;
++              onecommand();
++              /* we return from a function, when func_finished = 1 */
++              if (func_finished)
++                      break;
++      }
++
++      {
++              //fprintf(stderr, "%s is finished @%d!\n", f->name, global_env.iop->argp->afpos);
++              int ret = frame[cur_frame].saved_return_addr;
++              /* workaround code for \n */
++              if (dolc)
++                      ret--;
++              /* get return address from current frame and jump to */
++              global_env.iop->argp->afpos = ret;
++              global_env.iop->argp->afbuf->bufp = &(global_env.iop->argp->afbuf->buf[ret]);
++      }
++      /*
++      fprintf(stderr, "******** after execution ********************\n");
++      fprintf(stderr, " %s \n############# %d\n", global_env.iop->argp->afbuf->bufp, ret);
++      fprintf(stderr, "*******************************\n");
++      */
++      /* we return to previous frame */
++      cur_frame--;
++      /* free some space occupied by argument */
++      while (dolc--)
++              free(dolv[dolc]);
++      free(dolv);
++
++      /* recover argument for last function */
++      dolv = frame[cur_frame].argv;
++      dolc = frame[cur_frame].argc;
++      /* If we are not in the outest frame, we should set
++       * func_finished to 0 that means we still in some function */
++      if (cur_frame != 0)
++              func_finished = 0;
++} //funccode:end
+ static int collect(int c, int c1)
+ {
+@@ -2601,6 +2839,10 @@ static int execute(struct op *t, int *pi
+                               execute(t->right->right, pin, pout, /* no_fork: */ 0);
+               }
+               break;
++      case TFUNC: //funccode:start
++              break;
++      case TRETURN:
++              break; //funccode:end
+       case TCASE:
+               cp = evalstr(t->str, DOSUB | DOTRIM);
diff --git a/shell/msh_test/msh-bugs/noeol3.right b/shell/msh_test/msh-bugs/noeol3.right
new file mode 100644 (file)
index 0000000..56f8515
--- /dev/null
@@ -0,0 +1 @@
+hush: syntax error: unterminated "
diff --git a/shell/msh_test/msh-bugs/noeol3.tests b/shell/msh_test/msh-bugs/noeol3.tests
new file mode 100755 (executable)
index 0000000..ec958ed
--- /dev/null
@@ -0,0 +1,2 @@
+# last line has no EOL!
+echo "unterminated
\ No newline at end of file
diff --git a/shell/msh_test/msh-bugs/process_subst.right b/shell/msh_test/msh-bugs/process_subst.right
new file mode 100644 (file)
index 0000000..397bc80
--- /dev/null
@@ -0,0 +1,3 @@
+TESTzzBEST
+TEST$(echo zz)BEST
+TEST'BEST
diff --git a/shell/msh_test/msh-bugs/process_subst.tests b/shell/msh_test/msh-bugs/process_subst.tests
new file mode 100755 (executable)
index 0000000..21996bc
--- /dev/null
@@ -0,0 +1,3 @@
+echo "TEST`echo zz;echo;echo`BEST"
+echo "TEST`echo '$(echo zz)'`BEST"
+echo "TEST`echo "'"`BEST"
diff --git a/shell/msh_test/msh-bugs/read.right b/shell/msh_test/msh-bugs/read.right
new file mode 100644 (file)
index 0000000..0e50e2a
--- /dev/null
@@ -0,0 +1,4 @@
+read
+cat
+echo "REPLY=$REPLY"
+REPLY=exec <read.tests
diff --git a/shell/msh_test/msh-bugs/read.tests b/shell/msh_test/msh-bugs/read.tests
new file mode 100755 (executable)
index 0000000..ff1acbd
--- /dev/null
@@ -0,0 +1,4 @@
+exec <read.tests
+read
+cat
+echo "REPLY=$REPLY"
diff --git a/shell/msh_test/msh-bugs/shift.right b/shell/msh_test/msh-bugs/shift.right
new file mode 100644 (file)
index 0000000..d281e35
--- /dev/null
@@ -0,0 +1,6 @@
+./shift.tests abc d e
+./shift.tests d e 123
+./shift.tests d e 123
+./shift.tests
+./shift.tests
+./shift.tests
diff --git a/shell/msh_test/msh-bugs/shift.tests b/shell/msh_test/msh-bugs/shift.tests
new file mode 100755 (executable)
index 0000000..53ef249
--- /dev/null
@@ -0,0 +1,14 @@
+if test $# = 0; then
+    exec "$THIS_SH" $0 abc "d e" 123
+fi
+echo $0 $1 $2
+shift
+echo $0 $1 $2
+shift 999
+echo $0 $1 $2
+shift 2
+echo $0 $1 $2
+shift 2
+echo $0 $1 $2
+shift
+echo $0 $1 $2
diff --git a/shell/msh_test/msh-bugs/starquoted.right b/shell/msh_test/msh-bugs/starquoted.right
new file mode 100644 (file)
index 0000000..b56323f
--- /dev/null
@@ -0,0 +1,8 @@
+.1 abc d e f.
+.1.
+.abc.
+.d e f.
+.-1 abc d e f-.
+.-1.
+.abc.
+.d e f-.
diff --git a/shell/msh_test/msh-bugs/starquoted.tests b/shell/msh_test/msh-bugs/starquoted.tests
new file mode 100755 (executable)
index 0000000..2fe49b1
--- /dev/null
@@ -0,0 +1,8 @@
+if test $# = 0; then
+    exec "$THIS_SH" "$0" 1 abc 'd e f'
+fi
+
+for a in "$*"; do echo ".$a."; done
+for a in "$@"; do echo ".$a."; done
+for a in "-$*-"; do echo ".$a."; done
+for a in "-$@-"; do echo ".$a."; done
diff --git a/shell/msh_test/msh-bugs/syntax_err.right b/shell/msh_test/msh-bugs/syntax_err.right
new file mode 100644 (file)
index 0000000..08a270c
--- /dev/null
@@ -0,0 +1,2 @@
+shown
+hush: syntax error: unterminated '
diff --git a/shell/msh_test/msh-bugs/syntax_err.tests b/shell/msh_test/msh-bugs/syntax_err.tests
new file mode 100755 (executable)
index 0000000..d10ed42
--- /dev/null
@@ -0,0 +1,3 @@
+echo shown
+echo test `echo 'aa`
+echo not shown
diff --git a/shell/msh_test/msh-bugs/var_expand_in_assign.right b/shell/msh_test/msh-bugs/var_expand_in_assign.right
new file mode 100644 (file)
index 0000000..352210d
--- /dev/null
@@ -0,0 +1,5 @@
+. .
+.abc d e.
+.abc d e.
+.abc d e.
+.abc d e.
diff --git a/shell/msh_test/msh-bugs/var_expand_in_assign.tests b/shell/msh_test/msh-bugs/var_expand_in_assign.tests
new file mode 100755 (executable)
index 0000000..18cdc74
--- /dev/null
@@ -0,0 +1,15 @@
+if test $# = 0; then
+    exec "$THIS_SH" "$0" abc "d e"
+fi
+
+space=' '
+echo .$space.
+
+a=$*
+echo .$a.
+a=$@
+echo .$a.
+a="$*"
+echo .$a.
+a="$@"
+echo .$a.
diff --git a/shell/msh_test/msh-bugs/var_expand_in_redir.right b/shell/msh_test/msh-bugs/var_expand_in_redir.right
new file mode 100644 (file)
index 0000000..423299c
--- /dev/null
@@ -0,0 +1,3 @@
+TEST1
+TEST2
+TEST3
diff --git a/shell/msh_test/msh-bugs/var_expand_in_redir.tests b/shell/msh_test/msh-bugs/var_expand_in_redir.tests
new file mode 100755 (executable)
index 0000000..bda6bdd
--- /dev/null
@@ -0,0 +1,13 @@
+if test $# = 0; then
+    exec "$THIS_SH" "$0" abc "d e"
+fi
+
+echo TEST1 >"$1.out"
+echo TEST2 >"$2.out"
+# bash says: "$@.out": ambiguous redirect
+# ash handles it as if it is '$*' - we do the same
+echo TEST3 >"$@.out"
+
+cat abc.out "d e.out" "abc d e.out"
+
+rm abc.out "d e.out" "abc d e.out"
diff --git a/shell/msh_test/msh-execution/exitcode_EACCES.right b/shell/msh_test/msh-execution/exitcode_EACCES.right
new file mode 100644 (file)
index 0000000..6e5480b
--- /dev/null
@@ -0,0 +1,2 @@
+./: can't execute
+126
diff --git a/shell/msh_test/msh-execution/exitcode_EACCES.tests b/shell/msh_test/msh-execution/exitcode_EACCES.tests
new file mode 100755 (executable)
index 0000000..26b5c61
--- /dev/null
@@ -0,0 +1,2 @@
+./
+echo $?
diff --git a/shell/msh_test/msh-execution/exitcode_ENOENT.right b/shell/msh_test/msh-execution/exitcode_ENOENT.right
new file mode 100644 (file)
index 0000000..dd49d2c
--- /dev/null
@@ -0,0 +1,2 @@
+./does_not_exist_for_sure: not found
+127
diff --git a/shell/msh_test/msh-execution/exitcode_ENOENT.tests b/shell/msh_test/msh-execution/exitcode_ENOENT.tests
new file mode 100755 (executable)
index 0000000..7f1b88a
--- /dev/null
@@ -0,0 +1,2 @@
+./does_not_exist_for_sure
+echo $?
diff --git a/shell/msh_test/msh-execution/many_continues.right b/shell/msh_test/msh-execution/many_continues.right
new file mode 100644 (file)
index 0000000..d86bac9
--- /dev/null
@@ -0,0 +1 @@
+OK
diff --git a/shell/msh_test/msh-execution/many_continues.tests b/shell/msh_test/msh-execution/many_continues.tests
new file mode 100755 (executable)
index 0000000..86c729a
--- /dev/null
@@ -0,0 +1,15 @@
+if test $# = 0; then
+    # Child will kill us in 1 second
+    "$THIS_SH" "$0" $$ &
+
+    # Loop many, many times
+    trap 'echo OK; exit 0' 15
+    while true; do
+       continue
+    done
+    echo BAD
+    exit 1
+fi
+
+sleep 1
+kill $1
diff --git a/shell/msh_test/msh-execution/nested_break.right b/shell/msh_test/msh-execution/nested_break.right
new file mode 100644 (file)
index 0000000..4e8b6b0
--- /dev/null
@@ -0,0 +1,8 @@
+A
+B
+iteration
+C
+A
+B
+iteration
+D
diff --git a/shell/msh_test/msh-execution/nested_break.tests b/shell/msh_test/msh-execution/nested_break.tests
new file mode 100755 (executable)
index 0000000..f2e6f81
--- /dev/null
@@ -0,0 +1,17 @@
+# Testcase for http://bugs.busybox.net/view.php?id=846
+
+n=0
+while :
+do
+        echo A
+        while :
+        do
+               echo B
+                break
+        done
+        echo iteration
+        [ $n = 1 ] && break
+       echo C
+        n=`expr $n + 1`
+done
+echo D
diff --git a/shell/msh_test/msh-misc/tick.right b/shell/msh_test/msh-misc/tick.right
new file mode 100644 (file)
index 0000000..6ed281c
--- /dev/null
@@ -0,0 +1,2 @@
+1
+1
diff --git a/shell/msh_test/msh-misc/tick.tests b/shell/msh_test/msh-misc/tick.tests
new file mode 100755 (executable)
index 0000000..1f749a9
--- /dev/null
@@ -0,0 +1,4 @@
+true
+false; echo `echo $?`
+true
+{ false; echo `echo $?`; }
diff --git a/shell/msh_test/msh-parsing/argv0.right b/shell/msh_test/msh-parsing/argv0.right
new file mode 100644 (file)
index 0000000..d86bac9
--- /dev/null
@@ -0,0 +1 @@
+OK
diff --git a/shell/msh_test/msh-parsing/argv0.tests b/shell/msh_test/msh-parsing/argv0.tests
new file mode 100755 (executable)
index 0000000..f5c40f6
--- /dev/null
@@ -0,0 +1,4 @@
+if test $# = 0; then
+    exec "$THIS_SH" "$0" arg
+fi
+echo OK
diff --git a/shell/msh_test/msh-parsing/noeol.right b/shell/msh_test/msh-parsing/noeol.right
new file mode 100644 (file)
index 0000000..e427984
--- /dev/null
@@ -0,0 +1 @@
+HELLO
diff --git a/shell/msh_test/msh-parsing/noeol.tests b/shell/msh_test/msh-parsing/noeol.tests
new file mode 100755 (executable)
index 0000000..a93113a
--- /dev/null
@@ -0,0 +1,2 @@
+# next line has no EOL!
+echo HELLO
\ No newline at end of file
diff --git a/shell/msh_test/msh-parsing/noeol2.right b/shell/msh_test/msh-parsing/noeol2.right
new file mode 100644 (file)
index 0000000..d00491f
--- /dev/null
@@ -0,0 +1 @@
+1
diff --git a/shell/msh_test/msh-parsing/noeol2.tests b/shell/msh_test/msh-parsing/noeol2.tests
new file mode 100755 (executable)
index 0000000..1220f05
--- /dev/null
@@ -0,0 +1,7 @@
+# last line has no EOL!
+if true
+then
+  echo 1
+else
+  echo 2
+fi
\ No newline at end of file
diff --git a/shell/msh_test/msh-parsing/quote1.right b/shell/msh_test/msh-parsing/quote1.right
new file mode 100644 (file)
index 0000000..cb38205
--- /dev/null
@@ -0,0 +1 @@
+'1'
diff --git a/shell/msh_test/msh-parsing/quote1.tests b/shell/msh_test/msh-parsing/quote1.tests
new file mode 100755 (executable)
index 0000000..f558954
--- /dev/null
@@ -0,0 +1,2 @@
+a=1
+echo "'$a'"
diff --git a/shell/msh_test/msh-parsing/quote2.right b/shell/msh_test/msh-parsing/quote2.right
new file mode 100644 (file)
index 0000000..3bc9edc
--- /dev/null
@@ -0,0 +1 @@
+>1
diff --git a/shell/msh_test/msh-parsing/quote2.tests b/shell/msh_test/msh-parsing/quote2.tests
new file mode 100755 (executable)
index 0000000..bd966f3
--- /dev/null
@@ -0,0 +1,2 @@
+a=1
+echo ">$a"
diff --git a/shell/msh_test/msh-parsing/quote3.right b/shell/msh_test/msh-parsing/quote3.right
new file mode 100644 (file)
index 0000000..069a46e
--- /dev/null
@@ -0,0 +1,3 @@
+Testing: in $empty""
+..
+Finished
diff --git a/shell/msh_test/msh-parsing/quote3.tests b/shell/msh_test/msh-parsing/quote3.tests
new file mode 100755 (executable)
index 0000000..075e785
--- /dev/null
@@ -0,0 +1,8 @@
+if test $# = 0; then
+    exec "$THIS_SH" quote3.tests abc "d e"
+fi
+
+echo 'Testing: in $empty""'
+empty=''
+for a in $empty""; do echo ".$a."; done
+echo Finished
diff --git a/shell/msh_test/msh-parsing/quote4.right b/shell/msh_test/msh-parsing/quote4.right
new file mode 100644 (file)
index 0000000..b2901ea
--- /dev/null
@@ -0,0 +1 @@
+a b
diff --git a/shell/msh_test/msh-parsing/quote4.tests b/shell/msh_test/msh-parsing/quote4.tests
new file mode 100755 (executable)
index 0000000..f1dabfa
--- /dev/null
@@ -0,0 +1,2 @@
+a_b='a b'
+echo "$a_b"
diff --git a/shell/msh_test/msh-vars/star.right b/shell/msh_test/msh-vars/star.right
new file mode 100644 (file)
index 0000000..0ecc55b
--- /dev/null
@@ -0,0 +1,6 @@
+.1.
+.abc.
+.d.
+.e.
+.f.
+.1 abc d e f.
diff --git a/shell/msh_test/msh-vars/star.tests b/shell/msh_test/msh-vars/star.tests
new file mode 100755 (executable)
index 0000000..5554c40
--- /dev/null
@@ -0,0 +1,8 @@
+if test $# = 0; then
+    exec "$THIS_SH" star.tests 1 abc 'd e f'
+fi
+# 'd e f' should be split into 3 separate args:
+for a in $*; do echo ".$a."; done
+
+# must produce .1 abc d e f.
+for a in "$*"; do echo ".$a."; done
diff --git a/shell/msh_test/msh-vars/var.right b/shell/msh_test/msh-vars/var.right
new file mode 100644 (file)
index 0000000..14b2314
--- /dev/null
@@ -0,0 +1,4 @@
+http://busybox.net
+http://busybox.net_abc
+1
+1
diff --git a/shell/msh_test/msh-vars/var.tests b/shell/msh_test/msh-vars/var.tests
new file mode 100755 (executable)
index 0000000..0a63696
--- /dev/null
@@ -0,0 +1,9 @@
+URL=http://busybox.net
+
+echo $URL
+echo ${URL}_abc
+
+true
+false; echo $?
+true
+{ false; echo $?; }
diff --git a/shell/msh_test/msh-vars/var_subst_in_for.right b/shell/msh_test/msh-vars/var_subst_in_for.right
new file mode 100644 (file)
index 0000000..c8aca1c
--- /dev/null
@@ -0,0 +1,40 @@
+Testing: in x y z
+.x.
+.y.
+.z.
+Testing: in u $empty v
+.u.
+.v.
+Testing: in u " $empty" v
+.u.
+. .
+.v.
+Testing: in u $empty $empty$a v
+.u.
+.a.
+.v.
+Testing: in $a_b
+.a.
+.b.
+Testing: in $*
+.abc.
+.d.
+.e.
+Testing: in $@
+.abc.
+.d.
+.e.
+Testing: in -$*-
+.-abc.
+.d.
+.e-.
+Testing: in -$@-
+.-abc.
+.d.
+.e-.
+Testing: in $a_b -$a_b-
+.a.
+.b.
+.-a.
+.b-.
+Finished
diff --git a/shell/msh_test/msh-vars/var_subst_in_for.tests b/shell/msh_test/msh-vars/var_subst_in_for.tests
new file mode 100755 (executable)
index 0000000..4d1c112
--- /dev/null
@@ -0,0 +1,40 @@
+if test $# = 0; then
+    exec "$THIS_SH" var_subst_in_for.tests abc "d e"
+fi
+
+echo 'Testing: in x y z'
+for a in x y z; do echo ".$a."; done
+
+echo 'Testing: in u $empty v'
+empty=''
+for a in u $empty v; do echo ".$a."; done
+
+echo 'Testing: in u " $empty" v'
+empty=''
+for a in u " $empty" v; do echo ".$a."; done
+
+echo 'Testing: in u $empty $empty$a v'
+a='a'
+for a in u $empty $empty$a v; do echo ".$a."; done
+
+echo 'Testing: in $a_b'
+a_b='a b'
+for a in $a_b; do echo ".$a."; done
+
+echo 'Testing: in $*'
+for a in $*; do echo ".$a."; done
+
+echo 'Testing: in $@'
+for a in $@; do echo ".$a."; done
+
+echo 'Testing: in -$*-'
+for a in -$*-; do echo ".$a."; done
+
+echo 'Testing: in -$@-'
+for a in -$@-; do echo ".$a."; done
+
+echo 'Testing: in $a_b -$a_b-'
+a_b='a b'
+for a in $a_b -$a_b-; do echo ".$a."; done
+
+echo Finished
diff --git a/shell/msh_test/run-all b/shell/msh_test/run-all
new file mode 100755 (executable)
index 0000000..29f62a5
--- /dev/null
@@ -0,0 +1,64 @@
+#!/bin/sh
+
+test -x msh || {
+    echo "No ./msh - creating a link to ../../busybox"
+    ln -s ../../busybox msh
+}
+
+PATH="$PWD:$PATH" # for msh
+export PATH
+
+THIS_SH="$PWD/msh"
+export THIS_SH
+
+do_test()
+{
+    test -d "$1" || return 0
+#   echo Running tests in directory "$1"
+    (
+    cd "$1" || { echo "cannot cd $1!"; exit 1; }
+    for x in run-*; do
+       test -f "$x" || continue
+       case "$x" in
+           "$0"|run-minimal|run-gprof) ;;
+           *.orig|*~) ;;
+           #*) echo $x ; sh $x ;;
+           *)
+           sh "$x" >"../$1-$x.fail" 2>&1 && \
+           { echo "$1/$x: ok"; rm "../$1-$x.fail"; } || echo "$1/$x: fail";
+           ;;
+       esac
+    done
+    # Many bash run-XXX scripts just do this,
+    # no point in duplication it all over the place
+    for x in *.tests; do
+       test -x "$x" || continue
+       name="${x%%.tests}"
+       test -f "$name.right" || continue
+#      echo Running test: "$name.right"
+       {
+           "$THIS_SH" "./$x" >"$name.xx" 2>&1
+           diff -u "$name.xx" "$name.right" >"../$1-$x.fail" && rm -f "$name.xx" "../$1-$x.fail"
+       } && echo "$1/$x: ok" || echo "$1/$x: fail"
+    done
+    )
+}
+
+# Main part of this script
+# Usage: run-all [directories]
+
+if [ $# -lt 1 ]; then
+    # All sub directories
+    modules=`ls -d msh-*`
+
+    for module in $modules; do
+       do_test $module
+    done
+else
+    while [ $# -ge 1 ]; do
+       if [ -d $1 ]; then
+           do_test $1
+       fi
+       shift
+    done
+fi
diff --git a/shell/susv3_doc.tar.bz2 b/shell/susv3_doc.tar.bz2
new file mode 100644 (file)
index 0000000..443a283
Binary files /dev/null and b/shell/susv3_doc.tar.bz2 differ
diff --git a/sysklogd/Config.in b/sysklogd/Config.in
new file mode 100644 (file)
index 0000000..0664be0
--- /dev/null
@@ -0,0 +1,118 @@
+#
+# 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_SYSLOGD_DUP
+       bool "Support -D (drop dups) option"
+       default n
+       depends on SYSLOGD
+       help
+         Option -D instructs syslogd to drop consecutive messages
+         which are totally the same.
+
+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
+       range 4 2147483647
+       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 "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
+       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 (file)
index 0000000..d802198
--- /dev/null
@@ -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)           += syslogd_and_logger.o
+lib-$(CONFIG_LOGREAD)          += logread.o
+lib-$(CONFIG_SYSLOGD)          += syslogd_and_logger.o
diff --git a/sysklogd/klogd.c b/sysklogd/klogd.c
new file mode 100644 (file)
index 0000000..c54e80a
--- /dev/null
@@ -0,0 +1,135 @@
+/* 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 "libbb.h"
+#include <syslog.h>
+#include <sys/klog.h>
+
+static void klogd_signal(int sig)
+{
+       /* FYI: cmd 7 is equivalent to setting console_loglevel to 7
+        * via klogctl(8, NULL, 7). */
+       klogctl(7, NULL, 0); /* "7 -- Enable printk's to console" */
+       klogctl(0, NULL, 0); /* "0 -- Close the log. Currently a NOP" */
+       syslog(LOG_NOTICE, "klogd: exiting");
+       kill_myself_with_sig(sig);
+}
+
+#define log_buffer bb_common_bufsiz1
+enum {
+       KLOGD_LOGBUF_SIZE = sizeof(log_buffer),
+       OPT_LEVEL      = (1 << 0),
+       OPT_FOREGROUND = (1 << 1),
+};
+
+int klogd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int klogd_main(int argc UNUSED_PARAM, char **argv)
+{
+       int i = 0;
+       char *opt_c;
+       int opt;
+       int used = 0;
+
+       opt = getopt32(argv, "c:n", &opt_c);
+       if (opt & OPT_LEVEL) {
+               /* Valid levels are between 1 and 8 */
+               i = xatou_range(opt_c, 1, 8);
+       }
+       if (!(opt & OPT_FOREGROUND)) {
+               bb_daemonize_or_rexec(DAEMON_CHDIR_ROOT, argv);
+       }
+
+       openlog("kernel", 0, LOG_KERN);
+
+       bb_signals(BB_FATAL_SIGS, klogd_signal);
+       signal(SIGHUP, SIG_IGN);
+
+       /* "Open the log. Currently a NOP" */
+       klogctl(1, NULL, 0);
+
+       /* "printk() prints a message on the console only if it has a loglevel
+        * less than console_loglevel". Here we set console_loglevel = i. */
+       if (i)
+               klogctl(8, NULL, i);
+
+       syslog(LOG_NOTICE, "klogd started: %s", bb_banner);
+
+       while (1) {
+               int n;
+               int priority;
+               char *start;
+
+               /* "2 -- Read from the log." */
+               start = log_buffer + used;
+               n = klogctl(2, start, KLOGD_LOGBUF_SIZE-1 - used);
+               if (n < 0) {
+                       if (errno == EINTR)
+                               continue;
+                       syslog(LOG_ERR, "klogd: error %d in klogctl(2): %m",
+                                       errno);
+                       break;
+               }
+               start[n] = '\0';
+
+               /* klogctl buffer parsing modelled after code in dmesg.c */
+               /* Process each newline-terminated line in the buffer */
+               start = log_buffer;
+               while (1) {
+                       char *newline = strchrnul(start, '\n');
+
+                       if (*newline == '\0') {
+                               /* This line is incomplete... */
+                               if (start != log_buffer) {
+                                       /* move it to the front of the buffer */
+                                       overlapping_strcpy(log_buffer, start);
+                                       used = newline - start;
+                                       /* don't log it yet */
+                                       break;
+                               }
+                               /* ...but if buffer is full, log it anyway */
+                               used = 0;
+                               newline = NULL;
+                       } else {
+                               *newline++ = '\0';
+                       }
+
+                       /* Extract the priority */
+                       priority = LOG_INFO;
+                       if (*start == '<') {
+                               start++;
+                               if (*start) {
+                                       /* kernel never generates multi-digit prios */
+                                       priority = (*start - '0');
+                                       start++;
+                               }
+                               if (*start == '>')
+                                       start++;
+                       }
+                       /* Log (only non-empty lines) */
+                       if (*start)
+                               syslog(priority, "%s", start);
+
+                       if (!newline)
+                               break;
+                       start = newline;
+               }
+       }
+
+       return EXIT_FAILURE;
+}
diff --git a/sysklogd/logger.c b/sysklogd/logger.c
new file mode 100644 (file)
index 0000000..759981c
--- /dev/null
@@ -0,0 +1,156 @@
+/* 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.
+ */
+
+/*
+ * Done in syslogd_and_logger.c:
+#include "libbb.h"
+#define SYSLOG_NAMES
+#define SYSLOG_NAMES_CONST
+#include <syslog.h>
+*/
+
+/* 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, const CODE *codetab)
+{
+       const 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 %s name: %s", "facility", save);
+               *s++ = '.';
+       } else {
+               s = save;
+       }
+       lev = decode(s, prioritynames);
+       if (lev < 0)
+               bb_error_msg_and_die("unknown %s name: %s", "priority", save);
+       return ((lev & LOG_PRIMASK) | (fac & LOG_FACMASK));
+}
+
+#define strbuf bb_common_bufsiz1
+
+int logger_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int logger_main(int argc, char **argv)
+{
+       char *str_p, *str_t;
+       int opt;
+       int i = 0;
+
+       /* Fill out the name string early (may be overwritten later) */
+       str_t = uid2uname_utoa(geteuid());
+
+       /* Parse any options */
+       opt = getopt32(argv, "p:st:", &str_p, &str_t);
+
+       if (opt & 0x2) /* -s */
+               i |= LOG_PERROR;
+       //if (opt & 0x4) /* -t */
+       openlog(str_t, i, 0);
+       i = LOG_USER | LOG_NOTICE;
+       if (opt & 0x1) /* -p */
+               i = pencode(str_p);
+
+       argc -= optind;
+       argv += optind;
+       if (!argc) {
+               while (fgets(strbuf, COMMON_BUFSIZE, stdin)) {
+                       if (strbuf[0]
+                        && NOT_LONE_CHAR(strbuf, '\n')
+                       ) {
+                               /* Neither "" nor "\n" */
+                               syslog(i, "%s", strbuf);
+                       }
+               }
+       } else {
+               char *message = NULL;
+               int len = 0;
+               int pos = 0;
+               do {
+                       len += strlen(*argv) + 1;
+                       message = xrealloc(message, len + 1);
+                       sprintf(message + pos, " %s", *argv),
+                       pos = len;
+               } while (*++argv);
+               syslog(i, "%s", message + 1); /* skip leading " " */
+       }
+
+       closelog();
+       return EXIT_SUCCESS;
+}
+
+/* Clean up. Needed because we are included from syslogd_and_logger.c */
+#undef strbuf
+
+/*-
+ * 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 (file)
index 0000000..603a377
--- /dev/null
@@ -0,0 +1,185 @@
+/* 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 "libbb.h"
+#include <sys/ipc.h>
+#include <sys/sem.h>
+#include <sys/shm.h>
+
+#define DEBUG 0
+
+/* our shared key (syslogd.c and logread.c must be in sync) */
+enum { KEY_ID = 0x414e4547 }; /* "GENA" */
+
+struct shbuf_ds {
+       int32_t size;           // size of data - 1
+       int32_t tail;           // end of message list
+       char data[1];           // messages
+};
+
+static const struct sembuf init_sem[3] = {
+       {0, -1, IPC_NOWAIT | SEM_UNDO},
+       {1, 0}, {0, +1, SEM_UNDO}
+};
+
+struct globals {
+       struct sembuf SMrup[1]; // {0, -1, IPC_NOWAIT | SEM_UNDO},
+       struct sembuf SMrdn[2]; // {1, 0}, {0, +1, SEM_UNDO}
+       struct shbuf_ds *shbuf;
+};
+#define G (*(struct globals*)&bb_common_bufsiz1)
+#define SMrup (G.SMrup)
+#define SMrdn (G.SMrdn)
+#define shbuf (G.shbuf)
+#define INIT_G() do { \
+       memcpy(SMrup, init_sem, sizeof(init_sem)); \
+} while (0)
+
+static void error_exit(const char *str) NORETURN;
+static void error_exit(const char *str)
+{
+       //release all acquired resources
+       shmdt(shbuf);
+       bb_perror_msg_and_die(str);
+}
+
+/*
+ * sem_up - up()'s a semaphore.
+ */
+static void sem_up(int semid)
+{
+       if (semop(semid, SMrup, 1) == -1)
+               error_exit("semop[SMrup]");
+}
+
+static void interrupted(int sig UNUSED_PARAM)
+{
+       signal(SIGINT, SIG_IGN);
+       shmdt(shbuf);
+       exit(EXIT_SUCCESS);
+}
+
+int logread_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int logread_main(int argc UNUSED_PARAM, char **argv)
+{
+       unsigned cur;
+       int log_semid; /* ipc semaphore id */
+       int log_shmid; /* ipc shared memory id */
+       smallint follow = getopt32(argv, "f");
+
+       INIT_G();
+
+       log_shmid = shmget(KEY_ID, 0, 0);
+       if (log_shmid == -1)
+               bb_perror_msg_and_die("can't find syslogd buffer");
+
+       /* Attach shared memory to our char* */
+       shbuf = shmat(log_shmid, NULL, SHM_RDONLY);
+       if (shbuf == NULL)
+               bb_perror_msg_and_die("can't access syslogd buffer");
+
+       log_semid = semget(KEY_ID, 0, 0);
+       if (log_semid == -1)
+               error_exit("can't get access to semaphores for syslogd buffer");
+
+       signal(SIGINT, interrupted);
+
+       /* Suppose atomic memory read */
+       /* Max possible value for tail is shbuf->size - 1 */
+       cur = shbuf->tail;
+
+       /* Loop for logread -f, one pass if there was no -f */
+       do {
+               unsigned shbuf_size;
+               unsigned shbuf_tail;
+               const char *shbuf_data;
+#if ENABLE_FEATURE_LOGREAD_REDUCED_LOCKING
+               int i;
+               int len_first_part;
+               int len_total = len_total; /* for gcc */
+               char *copy = copy; /* for gcc */
+#endif
+               if (semop(log_semid, SMrdn, 2) == -1)
+                       error_exit("semop[SMrdn]");
+
+               /* Copy the info, helps gcc to realize that it doesn't change */
+               shbuf_size = shbuf->size;
+               shbuf_tail = shbuf->tail;
+               shbuf_data = shbuf->data; /* pointer! */
+
+               if (DEBUG)
+                       printf("cur:%d tail:%i size:%i\n",
+                                       cur, shbuf_tail, shbuf_size);
+
+               if (!follow) {
+                       /* advance to oldest complete message */
+                       /* find NUL */
+                       cur += strlen(shbuf_data + cur);
+                       if (cur >= shbuf_size) { /* last byte in buffer? */
+                               cur = strnlen(shbuf_data, shbuf_tail);
+                               if (cur == shbuf_tail)
+                                       goto unlock; /* no complete messages */
+                       }
+                       /* advance to first byte of the message */
+                       cur++;
+                       if (cur >= shbuf_size) /* last byte in buffer? */
+                               cur = 0;
+               } else { /* logread -f */
+                       if (cur == shbuf_tail) {
+                               sem_up(log_semid);
+                               fflush(stdout);
+                               sleep(1); /* TODO: replace me with a sleep_on */
+                               continue;
+                       }
+               }
+
+               /* Read from cur to tail */
+#if ENABLE_FEATURE_LOGREAD_REDUCED_LOCKING
+               len_first_part = len_total = shbuf_tail - cur;
+               if (len_total < 0) {
+                       /* message wraps: */
+                       /* [SECOND PART.........FIRST PART] */
+                       /*  ^data      ^tail    ^cur      ^size */
+                       len_total += shbuf_size;
+               }
+               copy = xmalloc(len_total + 1);
+               if (len_first_part < 0) {
+                       /* message wraps (see above) */
+                       len_first_part = shbuf_size - cur;
+                       memcpy(copy + len_first_part, shbuf_data, shbuf_tail);
+               }
+               memcpy(copy, shbuf_data + cur, len_first_part);
+               copy[len_total] = '\0';
+               cur = shbuf_tail;
+#else
+               while (cur != shbuf_tail) {
+                       fputs(shbuf_data + cur, stdout);
+                       cur += strlen(shbuf_data + cur) + 1;
+                       if (cur >= shbuf_size)
+                               cur = 0;
+               }
+#endif
+ unlock:
+               /* release the lock on the log chain */
+               sem_up(log_semid);
+
+#if ENABLE_FEATURE_LOGREAD_REDUCED_LOCKING
+               for (i = 0; i < len_total; i += strlen(copy + i) + 1) {
+                       fputs(copy + i, stdout);
+               }
+               free(copy);
+#endif
+       } while (follow);
+
+       shmdt(shbuf);
+
+       fflush_stdout_and_exit(EXIT_SUCCESS);
+}
diff --git a/sysklogd/syslogd.c b/sysklogd/syslogd.c
new file mode 100644 (file)
index 0000000..8939103
--- /dev/null
@@ -0,0 +1,715 @@
+/* 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.
+ */
+
+/*
+ * Done in syslogd_and_logger.c:
+#include "libbb.h"
+#define SYSLOG_NAMES
+#define SYSLOG_NAMES_CONST
+#include <syslog.h>
+*/
+
+#include <paths.h>
+#include <sys/un.h>
+#include <sys/uio.h>
+
+#if ENABLE_FEATURE_REMOTE_LOG
+#include <netinet/in.h>
+#endif
+
+#if ENABLE_FEATURE_IPC_SYSLOG
+#include <sys/ipc.h>
+#include <sys/sem.h>
+#include <sys/shm.h>
+#endif
+
+
+#define DEBUG 0
+
+/* MARK code is not very useful, is bloat, and broken:
+ * can deadlock if alarmed to make MARK while writing to IPC buffer
+ * (semaphores are down but do_mark routine tries to down them again) */
+#undef SYSLOGD_MARK
+
+/* Write locking does not seem to be useful either */
+#undef SYSLOGD_WRLOCK
+
+enum {
+       MAX_READ = 256,
+       DNS_WAIT_SEC = 2 * 60,
+};
+
+/* Semaphore operation structures */
+struct shbuf_ds {
+       int32_t size;   /* size of data - 1 */
+       int32_t tail;   /* end of message list */
+       char data[1];   /* data/messages */
+};
+
+/* Allows us to have smaller initializer. Ugly. */
+#define GLOBALS \
+       const char *logFilePath;                \
+       int logFD;                              \
+       /* interval between marks in seconds */ \
+       /*int markInterval;*/                   \
+       /* level of messages to be logged */    \
+       int logLevel;                           \
+USE_FEATURE_ROTATE_LOGFILE( \
+       /* max size of file before rotation */  \
+       unsigned logFileSize;                   \
+       /* number of rotated message files */   \
+       unsigned logFileRotate;                 \
+       unsigned curFileSize;                   \
+       smallint isRegular;                     \
+) \
+USE_FEATURE_REMOTE_LOG( \
+       /* udp socket for remote logging */     \
+       int remoteFD;                           \
+       len_and_sockaddr* remoteAddr;           \
+) \
+USE_FEATURE_IPC_SYSLOG( \
+       int shmid; /* ipc shared memory id */   \
+       int s_semid; /* ipc semaphore id */     \
+       int shm_size;                           \
+       struct sembuf SMwup[1];                 \
+       struct sembuf SMwdn[3];                 \
+)
+
+struct init_globals {
+       GLOBALS
+};
+
+struct globals {
+       GLOBALS
+
+#if ENABLE_FEATURE_REMOTE_LOG
+       unsigned last_dns_resolve;
+       char *remoteAddrStr;
+#endif
+
+#if ENABLE_FEATURE_IPC_SYSLOG
+       struct shbuf_ds *shbuf;
+#endif
+       time_t last_log_time;
+       /* localhost's name. We print only first 64 chars */
+       char *hostname;
+
+       /* We recv into recvbuf... */
+       char recvbuf[MAX_READ * (1 + ENABLE_FEATURE_SYSLOGD_DUP)];
+       /* ...then copy to parsebuf, escaping control chars */
+       /* (can grow x2 max) */
+       char parsebuf[MAX_READ*2];
+       /* ...then sprintf into printbuf, adding timestamp (15 chars),
+        * host (64), fac.prio (20) to the message */
+       /* (growth by: 15 + 64 + 20 + delims = ~110) */
+       char printbuf[MAX_READ*2 + 128];
+};
+
+static const struct init_globals init_data = {
+       .logFilePath = "/var/log/messages",
+       .logFD = -1,
+#ifdef SYSLOGD_MARK
+       .markInterval = 20 * 60,
+#endif
+       .logLevel = 8,
+#if ENABLE_FEATURE_ROTATE_LOGFILE
+       .logFileSize = 200 * 1024,
+       .logFileRotate = 1,
+#endif
+#if ENABLE_FEATURE_REMOTE_LOG
+       .remoteFD = -1,
+#endif
+#if ENABLE_FEATURE_IPC_SYSLOG
+       .shmid = -1,
+       .s_semid = -1,
+       .shm_size = ((CONFIG_FEATURE_IPC_SYSLOG_BUFFER_SIZE)*1024), // default shm size
+       .SMwup = { {1, -1, IPC_NOWAIT} },
+       .SMwdn = { {0, 0}, {1, 0}, {1, +1} },
+#endif
+};
+
+#define G (*ptr_to_globals)
+#define INIT_G() do { \
+       SET_PTR_TO_GLOBALS(memcpy(xzalloc(sizeof(G)), &init_data, sizeof(init_data))); \
+} while (0)
+
+
+/* Options */
+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_remotelog  ,) // -R
+       USE_FEATURE_REMOTE_LOG(    OPTBIT_locallog   ,) // -L
+       USE_FEATURE_IPC_SYSLOG(    OPTBIT_circularlog,) // -C
+       USE_FEATURE_SYSLOGD_DUP(   OPTBIT_dup        ,) // -D
+
+       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_remotelog  )) + 0,
+       OPT_locallog    = USE_FEATURE_REMOTE_LOG(    (1 << OPTBIT_locallog   )) + 0,
+       OPT_circularlog = USE_FEATURE_IPC_SYSLOG(    (1 << OPTBIT_circularlog)) + 0,
+       OPT_dup         = USE_FEATURE_SYSLOGD_DUP(   (1 << OPTBIT_dup        )) + 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::") \
+       USE_FEATURE_SYSLOGD_DUP(   "D"  )
+#define OPTION_DECL *opt_m, *opt_l \
+       USE_FEATURE_ROTATE_LOGFILE(,*opt_s) \
+       USE_FEATURE_ROTATE_LOGFILE(,*opt_b) \
+       USE_FEATURE_IPC_SYSLOG(    ,*opt_C = NULL)
+#define OPTION_PARAM &opt_m, &G.logFilePath, &opt_l \
+       USE_FEATURE_ROTATE_LOGFILE(,&opt_s) \
+       USE_FEATURE_ROTATE_LOGFILE(,&opt_b) \
+       USE_FEATURE_REMOTE_LOG(    ,&G.remoteAddrStr) \
+       USE_FEATURE_IPC_SYSLOG(    ,&opt_C)
+
+
+/* circular buffer variables/structures */
+#if ENABLE_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
+
+/* our shared key (syslogd.c and logread.c must be in sync) */
+enum { KEY_ID = 0x414e4547 }; /* "GENA" */
+
+static void ipcsyslog_cleanup(void)
+{
+       if (G.shmid != -1) {
+               shmdt(G.shbuf);
+       }
+       if (G.shmid != -1) {
+               shmctl(G.shmid, IPC_RMID, NULL);
+       }
+       if (G.s_semid != -1) {
+               semctl(G.s_semid, 0, IPC_RMID, 0);
+       }
+}
+
+static void ipcsyslog_init(void)
+{
+       if (DEBUG)
+               printf("shmget(%x, %d,...)\n", (int)KEY_ID, G.shm_size);
+
+       G.shmid = shmget(KEY_ID, G.shm_size, IPC_CREAT | 0644);
+       if (G.shmid == -1) {
+               bb_perror_msg_and_die("shmget");
+       }
+
+       G.shbuf = shmat(G.shmid, NULL, 0);
+       if (G.shbuf == (void*) -1L) { /* shmat has bizarre error return */
+               bb_perror_msg_and_die("shmat");
+       }
+
+       memset(G.shbuf, 0, G.shm_size);
+       G.shbuf->size = G.shm_size - offsetof(struct shbuf_ds, data) - 1;
+       /*G.shbuf->tail = 0;*/
+
+       // we'll trust the OS to set initial semval to 0 (let's hope)
+       G.s_semid = semget(KEY_ID, 2, IPC_CREAT | IPC_EXCL | 1023);
+       if (G.s_semid == -1) {
+               if (errno == EEXIST) {
+                       G.s_semid = semget(KEY_ID, 2, 0);
+                       if (G.s_semid != -1)
+                               return;
+               }
+               bb_perror_msg_and_die("semget");
+       }
+}
+
+/* Write message to shared mem buffer */
+static void log_to_shmem(const char *msg, int len)
+{
+       int old_tail, new_tail;
+
+       if (semop(G.s_semid, G.SMwdn, 3) == -1) {
+               bb_perror_msg_and_die("SMwdn");
+       }
+
+       /* Circular Buffer Algorithm:
+        * --------------------------
+        * tail == position where to store next syslog message.
+        * tail's max value is (shbuf->size - 1)
+        * Last byte of buffer is never used and remains NUL.
+        */
+       len++; /* length with NUL included */
+ again:
+       old_tail = G.shbuf->tail;
+       new_tail = old_tail + len;
+       if (new_tail < G.shbuf->size) {
+               /* store message, set new tail */
+               memcpy(G.shbuf->data + old_tail, msg, len);
+               G.shbuf->tail = new_tail;
+       } else {
+               /* k == available buffer space ahead of old tail */
+               int k = G.shbuf->size - old_tail;
+               /* copy what fits to the end of buffer, and repeat */
+               memcpy(G.shbuf->data + old_tail, msg, k);
+               msg += k;
+               len -= k;
+               G.shbuf->tail = 0;
+               goto again;
+       }
+       if (semop(G.s_semid, G.SMwup, 1) == -1) {
+               bb_perror_msg_and_die("SMwup");
+       }
+       if (DEBUG)
+               printf("tail:%d\n", G.shbuf->tail);
+}
+#else
+void ipcsyslog_cleanup(void);
+void ipcsyslog_init(void);
+void log_to_shmem(const char *msg);
+#endif /* FEATURE_IPC_SYSLOG */
+
+
+/* Print a message to the log file. */
+static void log_locally(time_t now, char *msg)
+{
+#ifdef SYSLOGD_WRLOCK
+       struct flock fl;
+#endif
+       int len = strlen(msg);
+
+#if ENABLE_FEATURE_IPC_SYSLOG
+       if ((option_mask32 & OPT_circularlog) && G.shbuf) {
+               log_to_shmem(msg, len);
+               return;
+       }
+#endif
+       if (G.logFD >= 0) {
+               /* Reopen log file every second. This allows admin
+                * to delete the file and not worry about restarting us.
+                * This costs almost nothing since it happens
+                * _at most_ once a second.
+                */
+               if (!now)
+                       now = time(NULL);
+               if (G.last_log_time != now) {
+                       G.last_log_time = now;
+                       close(G.logFD);
+                       goto reopen;
+               }
+       } else {
+ reopen:
+               G.logFD = open(G.logFilePath, O_WRONLY | O_CREAT
+                                       | O_NOCTTY | O_APPEND | O_NONBLOCK,
+                                       0666);
+               if (G.logFD < 0) {
+                       /* cannot open logfile? - print to /dev/console then */
+                       int fd = device_open(DEV_CONSOLE, O_WRONLY | O_NOCTTY | O_NONBLOCK);
+                       if (fd < 0)
+                               fd = 2; /* then stderr, dammit */
+                       full_write(fd, msg, len);
+                       if (fd != 2)
+                               close(fd);
+                       return;
+               }
+#if ENABLE_FEATURE_ROTATE_LOGFILE
+               {
+                       struct stat statf;
+                       G.isRegular = (fstat(G.logFD, &statf) == 0 && S_ISREG(statf.st_mode));
+                       /* bug (mostly harmless): can wrap around if file > 4gb */
+                       G.curFileSize = statf.st_size;
+               }
+#endif
+       }
+
+#ifdef SYSLOGD_WRLOCK
+       fl.l_whence = SEEK_SET;
+       fl.l_start = 0;
+       fl.l_len = 1;
+       fl.l_type = F_WRLCK;
+       fcntl(G.logFD, F_SETLKW, &fl);
+#endif
+
+#if ENABLE_FEATURE_ROTATE_LOGFILE
+       if (G.logFileSize && G.isRegular && G.curFileSize > G.logFileSize) {
+               if (G.logFileRotate) { /* always 0..99 */
+                       int i = strlen(G.logFilePath) + 3 + 1;
+                       char oldFile[i];
+                       char newFile[i];
+                       i = G.logFileRotate - 1;
+                       /* rename: f.8 -> f.9; f.7 -> f.8; ... */
+                       while (1) {
+                               sprintf(newFile, "%s.%d", G.logFilePath, i);
+                               if (i == 0) break;
+                               sprintf(oldFile, "%s.%d", G.logFilePath, --i);
+                               /* ignore errors - file might be missing */
+                               rename(oldFile, newFile);
+                       }
+                       /* newFile == "f.0" now */
+                       rename(G.logFilePath, newFile);
+#ifdef SYSLOGD_WRLOCK
+                       fl.l_type = F_UNLCK;
+                       fcntl(G.logFD, F_SETLKW, &fl);
+#endif
+                       close(G.logFD);
+                       goto reopen;
+               }
+               ftruncate(G.logFD, 0);
+       }
+       G.curFileSize +=
+#endif
+                       full_write(G.logFD, msg, len);
+#ifdef SYSLOGD_WRLOCK
+       fl.l_type = F_UNLCK;
+       fcntl(G.logFD, F_SETLKW, &fl);
+#endif
+}
+
+static void parse_fac_prio_20(int pri, char *res20)
+{
+       const CODE *c_pri, *c_fac;
+
+       if (pri != 0) {
+               c_fac = facilitynames;
+               while (c_fac->c_name) {
+                       if (c_fac->c_val != (LOG_FAC(pri) << 3)) {
+                               c_fac++;
+                               continue;
+                       }
+                       /* facility is found, look for prio */
+                       c_pri = prioritynames;
+                       while (c_pri->c_name) {
+                               if (c_pri->c_val != LOG_PRI(pri)) {
+                                       c_pri++;
+                                       continue;
+                               }
+                               snprintf(res20, 20, "%s.%s",
+                                               c_fac->c_name, c_pri->c_name);
+                               return;
+                       }
+                       /* prio not found, bail out */
+                       break;
+               }
+               snprintf(res20, 20, "<%d>", pri);
+       }
+}
+
+/* len parameter is used only for "is there a timestamp?" check.
+ * NB: some callers cheat and supply len==0 when they know
+ * that there is no timestamp, short-circuiting the test. */
+static void timestamp_and_log(int pri, char *msg, int len)
+{
+       char *timestamp;
+       time_t now;
+
+       if (len < 16 || msg[3] != ' ' || msg[6] != ' '
+        || msg[9] != ':' || msg[12] != ':' || msg[15] != ' '
+       ) {
+               time(&now);
+               timestamp = ctime(&now) + 4; /* skip day of week */
+       } else {
+               now = 0;
+               timestamp = msg;
+               msg += 16;
+       }
+       timestamp[15] = '\0';
+
+       if (option_mask32 & OPT_small)
+               sprintf(G.printbuf, "%s %s\n", timestamp, msg);
+       else {
+               char res[20];
+               parse_fac_prio_20(pri, res);
+               sprintf(G.printbuf, "%s %.64s %s %s\n", timestamp, G.hostname, res, msg);
+       }
+
+       /* Log message locally (to file or shared mem) */
+       log_locally(now, G.printbuf);
+}
+
+static void timestamp_and_log_internal(const char *msg)
+{
+       /* -L, or no -R */
+       if (ENABLE_FEATURE_REMOTE_LOG && !(option_mask32 & OPT_locallog))
+               return;
+       timestamp_and_log(LOG_SYSLOG | LOG_INFO, (char*)msg, 0);
+}
+
+/* tmpbuf[len] is a NUL byte (set by caller), but there can be other,
+ * embedded NULs. Split messages on each of these NULs, parse prio,
+ * escape control chars and log each locally. */
+static void split_escape_and_log(char *tmpbuf, int len)
+{
+       char *p = tmpbuf;
+
+       tmpbuf += len;
+       while (p < tmpbuf) {
+               char c;
+               char *q = G.parsebuf;
+               int pri = (LOG_USER | LOG_NOTICE);
+
+               if (*p == '<') {
+                       /* Parse the magic priority number */
+                       pri = bb_strtou(p + 1, &p, 10);
+                       if (*p == '>')
+                               p++;
+                       if (pri & ~(LOG_FACMASK | LOG_PRIMASK))
+                               pri = (LOG_USER | LOG_NOTICE);
+               }
+
+               while ((c = *p++)) {
+                       if (c == '\n')
+                               c = ' ';
+                       if (!(c & ~0x1f) && c != '\t') {
+                               *q++ = '^';
+                               c += '@'; /* ^@, ^A, ^B... */
+                       }
+                       *q++ = c;
+               }
+               *q = '\0';
+
+               /* Now log it */
+               if (LOG_PRI(pri) < G.logLevel)
+                       timestamp_and_log(pri, G.parsebuf, q - G.parsebuf);
+       }
+}
+
+#ifdef SYSLOGD_MARK
+static void do_mark(int sig)
+{
+       if (G.markInterval) {
+               timestamp_and_log_internal("-- MARK --");
+               alarm(G.markInterval);
+       }
+}
+#endif
+
+/* Don't inline: prevent struct sockaddr_un to take up space on stack
+ * permanently */
+static NOINLINE int create_socket(void)
+{
+       struct sockaddr_un sunx;
+       int sock_fd;
+       char *dev_log_name;
+
+       memset(&sunx, 0, sizeof(sunx));
+       sunx.sun_family = AF_UNIX;
+
+       /* Unlink old /dev/log or object it points to. */
+       /* (if it exists, bind will fail) */
+       strcpy(sunx.sun_path, "/dev/log");
+       dev_log_name = xmalloc_follow_symlinks("/dev/log");
+       if (dev_log_name) {
+               safe_strncpy(sunx.sun_path, dev_log_name, sizeof(sunx.sun_path));
+               free(dev_log_name);
+       }
+       unlink(sunx.sun_path);
+
+       sock_fd = xsocket(AF_UNIX, SOCK_DGRAM, 0);
+       xbind(sock_fd, (struct sockaddr *) &sunx, sizeof(sunx));
+       chmod("/dev/log", 0666);
+
+       return sock_fd;
+}
+
+#if ENABLE_FEATURE_REMOTE_LOG
+static int try_to_resolve_remote(void)
+{
+       if (!G.remoteAddr) {
+               unsigned now = monotonic_sec();
+
+               /* Don't resolve name too often - DNS timeouts can be big */
+               if ((now - G.last_dns_resolve) < DNS_WAIT_SEC)
+                       return -1;
+               G.last_dns_resolve = now;
+               G.remoteAddr = host2sockaddr(G.remoteAddrStr, 514);
+               if (!G.remoteAddr)
+                       return -1;
+       }
+       return socket(G.remoteAddr->u.sa.sa_family, SOCK_DGRAM, 0);
+}
+#endif
+
+static void do_syslogd(void) NORETURN;
+static void do_syslogd(void)
+{
+       int sock_fd;
+#if ENABLE_FEATURE_SYSLOGD_DUP
+       int last_sz = -1;
+       char *last_buf;
+       char *recvbuf = G.recvbuf;
+#else
+#define recvbuf (G.recvbuf)
+#endif
+
+       /* Set up signal handlers (so that they interrupt read()) */
+       signal_no_SA_RESTART_empty_mask(SIGTERM, record_signo);
+       signal_no_SA_RESTART_empty_mask(SIGINT, record_signo);
+       //signal_no_SA_RESTART_empty_mask(SIGQUIT, record_signo);
+       signal(SIGHUP, SIG_IGN);
+#ifdef SYSLOGD_MARK
+       signal(SIGALRM, do_mark);
+       alarm(G.markInterval);
+#endif
+       sock_fd = create_socket();
+
+       if (ENABLE_FEATURE_IPC_SYSLOG && (option_mask32 & OPT_circularlog)) {
+               ipcsyslog_init();
+       }
+
+       timestamp_and_log_internal("syslogd started: BusyBox v" BB_VER);
+
+       while (!bb_got_signal) {
+               ssize_t sz;
+
+#if ENABLE_FEATURE_SYSLOGD_DUP
+               last_buf = recvbuf;
+               if (recvbuf == G.recvbuf)
+                       recvbuf = G.recvbuf + MAX_READ;
+               else
+                       recvbuf = G.recvbuf;
+#endif
+ read_again:
+               sz = read(sock_fd, recvbuf, MAX_READ - 1);
+               if (sz < 0) {
+                       if (!bb_got_signal)
+                               bb_perror_msg("read from /dev/log");
+                       break;
+               }
+
+               /* Drop trailing '\n' and NULs (typically there is one NUL) */
+               while (1) {
+                       if (sz == 0)
+                               goto read_again;
+                       /* man 3 syslog says: "A trailing newline is added when needed".
+                        * However, neither glibc nor uclibc do this:
+                        * syslog(prio, "test")   sends "test\0" to /dev/log,
+                        * syslog(prio, "test\n") sends "test\n\0".
+                        * IOW: newline is passed verbatim!
+                        * I take it to mean that it's syslogd's job
+                        * to make those look identical in the log files. */
+                       if (recvbuf[sz-1] != '\0' && recvbuf[sz-1] != '\n')
+                               break;
+                       sz--;
+               }
+#if ENABLE_FEATURE_SYSLOGD_DUP
+               if ((option_mask32 & OPT_dup) && (sz == last_sz))
+                       if (memcmp(last_buf, recvbuf, sz) == 0)
+                               continue;
+               last_sz = sz;
+#endif
+#if ENABLE_FEATURE_REMOTE_LOG
+               /* We are not modifying log messages in any way before send */
+               /* Remote site cannot trust _us_ anyway and need to do validation again */
+               if (G.remoteAddrStr) {
+                       if (-1 == G.remoteFD) {
+                               G.remoteFD = try_to_resolve_remote();
+                               if (-1 == G.remoteFD)
+                                       goto no_luck;
+                       }
+                       /* Stock syslogd sends it '\n'-terminated
+                        * over network, mimic that */
+                       recvbuf[sz] = '\n';
+                       /* send message to remote logger, ignore possible error */
+                       /* TODO: on some errors, close and set G.remoteFD to -1
+                        * so that DNS resolution and connect is retried? */
+                       sendto(G.remoteFD, recvbuf, sz+1, MSG_DONTWAIT,
+                                   &G.remoteAddr->u.sa, G.remoteAddr->len);
+ no_luck: ;
+               }
+#endif
+               if (!ENABLE_FEATURE_REMOTE_LOG || (option_mask32 & OPT_locallog)) {
+                       recvbuf[sz] = '\0'; /* ensure it *is* NUL terminated */
+                       split_escape_and_log(recvbuf, sz);
+               }
+       } /* while (!bb_got_signal) */
+
+       timestamp_and_log_internal("syslogd exiting");
+       puts("syslogd exiting");
+       if (ENABLE_FEATURE_IPC_SYSLOG)
+               ipcsyslog_cleanup();
+       kill_myself_with_sig(bb_got_signal);
+#undef recvbuf
+}
+
+int syslogd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int syslogd_main(int argc UNUSED_PARAM, char **argv)
+{
+       char OPTION_DECL;
+       int opts;
+
+       INIT_G();
+#if ENABLE_FEATURE_REMOTE_LOG
+       G.last_dns_resolve = monotonic_sec() - DNS_WAIT_SEC - 1;
+#endif
+
+       /* do normal option parsing */
+       opt_complementary = "=0"; /* no non-option params */
+       opts = getopt32(argv, OPTION_STR, OPTION_PARAM);
+#ifdef SYSLOGD_MARK
+       if (opts & OPT_mark) // -m
+               G.markInterval = xatou_range(opt_m, 0, INT_MAX/60) * 60;
+#endif
+       //if (opts & OPT_nofork) // -n
+       //if (opts & OPT_outfile) // -O
+       if (opts & OPT_loglevel) // -l
+               G.logLevel = xatou_range(opt_l, 1, 8);
+       //if (opts & OPT_small) // -S
+#if ENABLE_FEATURE_ROTATE_LOGFILE
+       if (opts & OPT_filesize) // -s
+               G.logFileSize = xatou_range(opt_s, 0, INT_MAX/1024) * 1024;
+       if (opts & OPT_rotatecnt) // -b
+               G.logFileRotate = xatou_range(opt_b, 0, 99);
+#endif
+#if ENABLE_FEATURE_IPC_SYSLOG
+       if (opt_C) // -Cn
+               G.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 && !(opts & OPT_remotelog)) // -R
+               option_mask32 |= OPT_locallog;
+
+       /* Store away localhost's name before the fork */
+       G.hostname = safe_gethostname();
+       *strchrnul(G.hostname, '.') = '\0';
+
+       if (!(opts & OPT_nofork)) {
+               bb_daemonize_or_rexec(DAEMON_CHDIR_ROOT, argv);
+       }
+       umask(0);
+       write_pidfile("/var/run/syslogd.pid");
+       do_syslogd();
+       /* return EXIT_SUCCESS; */
+}
+
+/* Clean up. Needed because we are included from syslogd_and_logger.c */
+#undef DEBUG
+#undef SYSLOGD_MARK
+#undef SYSLOGD_WRLOCK
+#undef G
+#undef GLOBALS
+#undef INIT_G
+#undef OPTION_STR
+#undef OPTION_DECL
+#undef OPTION_PARAM
diff --git a/sysklogd/syslogd_and_logger.c b/sysklogd/syslogd_and_logger.c
new file mode 100644 (file)
index 0000000..51573bd
--- /dev/null
@@ -0,0 +1,51 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * prioritynames[] and facilitynames[]
+ *
+ * Copyright (C) 2008 by Denys Vlasenko <vda.linux@gmail.com>
+ *
+ * Licensed under GPLv2, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.h"
+#define SYSLOG_NAMES
+#define SYSLOG_NAMES_CONST
+#include <syslog.h>
+
+#if 0
+/* For the record: with SYSLOG_NAMES <syslog.h> defines
+ * (not declares) the following:
+ */
+typedef struct _code {
+       /*const*/ char *c_name;
+       int c_val;
+} CODE;
+/*const*/ CODE prioritynames[] = {
+    { "alert", LOG_ALERT },
+...
+    { NULL, -1 }
+};
+/* same for facilitynames[] */
+
+/* This MUST occur only once per entire executable,
+ * therefore we can't just do it in syslogd.c and logger.c -
+ * there will be two copies of it.
+ *
+ * We cannot even do it in separate file and then just reference
+ * prioritynames[] from syslogd.c and logger.c - bare <syslog.h>
+ * will not emit extern decls for prioritynames[]! Attempts to
+ * emit "matching" struct _code declaration defeat the whole purpose
+ * of <syslog.h>.
+ *
+ * For now, syslogd.c and logger.c are simply compiled into
+ * one object file.
+ */
+#endif
+
+#if ENABLE_SYSLOGD
+#include "syslogd.c"
+#endif
+
+#if ENABLE_LOGGER
+#include "logger.c"
+#endif
diff --git a/testsuite/README b/testsuite/README
new file mode 100644 (file)
index 0000000..b4719e6
--- /dev/null
@@ -0,0 +1,33 @@
+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:
+
+./runtest <applet1> <applet2>...
+
+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 (file)
index 0000000..b8957f4
--- /dev/null
@@ -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 (executable)
index 0000000..45f4011
--- /dev/null
@@ -0,0 +1,92 @@
+#!/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
+
+
+#
+# make sure all usage strings are properly escaped.  oftentimes people miss
+# an escape sequence so we end up with:
+# #define foo_usage \
+#       " this line is ok" \
+#       " as is this line"
+#       " but this one is broken as the \ is missing from above"
+#
+${CROSS_COMPILE}cpp -dD -P $srcdir/../include/usage.h \
+       | sed -e '/^#define/d' -e '/^$/d' > src.usage.escaped
+testing "Usage strings escaped" "cat src.usage.escaped" "" "" ""
+rm -f src.usage.escaped
+
+
+#
+# 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|sigblock|siggetmask|sigsetmask)\>[[: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/awk.tests b/testsuite/awk.tests
new file mode 100755 (executable)
index 0000000..0db99ab
--- /dev/null
@@ -0,0 +1,50 @@
+#!/bin/sh
+
+# Copyright 2007 by Denys Vlasenko <vda.linux@googlemail.com>
+# Licensed under GPL v2, see file LICENSE for details.
+
+. testing.sh
+
+# testing "description" "command" "result" "infile" "stdin"
+
+testing "awk -F case 0" "awk -F '[#]' '{ print NF }'" ""    "" ""
+testing "awk -F case 1" "awk -F '[#]' '{ print NF }'" "0\n" "" "\n"
+testing "awk -F case 2" "awk -F '[#]' '{ print NF }'" "2\n" "" "#\n"
+testing "awk -F case 3" "awk -F '[#]' '{ print NF }'" "3\n" "" "#abc#\n"
+testing "awk -F case 4" "awk -F '[#]' '{ print NF }'" "3\n" "" "#abc#zz\n"
+testing "awk -F case 5" "awk -F '[#]' '{ print NF }'" "4\n" "" "#abc##zz\n"
+testing "awk -F case 6" "awk -F '[#]' '{ print NF }'" "4\n" "" "z#abc##zz\n"
+testing "awk -F case 7" "awk -F '[#]' '{ print NF }'" "5\n" "" "z##abc##zz\n"
+
+# 4294967295 = 0xffffffff
+testing "awk bitwise op"  "awk '{ print or(4294967295,1) }'" "4.29497e+09\n" "" "\n"
+testing "awk hex const 1" "awk '{ print or(0xffffffff,1) }'" "4.29497e+09\n" "" "\n"
+testing "awk hex const 2" "awk '{ print or(0x80000000,1) }'" "2.14748e+09\n" "" "\n"
+testing "awk oct const"   "awk '{ print or(01234,1) }'"      "669\n"         "" "\n"
+
+# long field seps requiring regex
+testing "awk long field sep" "awk -F-- '{ print NF, length(\$NF), \$NF }'" \
+       "2 0 \n3 0 \n4 0 \n5 0 \n" \
+       "" \
+       "a--\na--b--\na--b--c--\na--b--c--d--"
+
+# '@(samp|code|file)\{' is an invalid extended regex (unmatched '{'),
+# but gawk 3.1.5 does not bail out on it.
+testing "awk gsub falls back to non-extended-regex" \
+       "awk 'gsub(\"@(samp|code|file)\{\",\"\");'; echo \$?" "0\n" "" "Hi\n"
+
+tar xjf awk_t1.tar.bz2
+testing "awk 'gcc build bug'" \
+       "awk -f awk_t1_opt-functions.awk -f awk_t1_opth-gen.awk <awk_t1_input | md5sum" \
+       "f842e256461a5ab1ec60b58d16f1114f  -\n" \
+       "" ""
+rm -rf awk_t1_*
+
+Q='":"'
+
+testing "awk NF in BEGIN" \
+       "awk 'BEGIN { print ${Q} NF ${Q} \$0 ${Q} \$1 ${Q} \$2 ${Q} }'" \
+       ":0::::\n" \
+       "" ""
+
+exit $FAILCOUNT
diff --git a/testsuite/awk_t1.tar.bz2 b/testsuite/awk_t1.tar.bz2
new file mode 100644 (file)
index 0000000..0fb8a07
Binary files /dev/null and b/testsuite/awk_t1.tar.bz2 differ
diff --git a/testsuite/basename/basename-does-not-remove-identical-extension b/testsuite/basename/basename-does-not-remove-identical-extension
new file mode 100644 (file)
index 0000000..4448fde
--- /dev/null
@@ -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 (file)
index 0000000..38907d4
--- /dev/null
@@ -0,0 +1,2 @@
+test x$(basename $(pwd)) = x$(busybox basename $(pwd))
+
diff --git a/testsuite/bunzip2.tests b/testsuite/bunzip2.tests
new file mode 100755 (executable)
index 0000000..3105ba4
--- /dev/null
@@ -0,0 +1,524 @@
+#!/bin/sh
+# Used by both gunzip and bunzip2 tests
+
+if test "${0##*/}" = "gunzip.tests"; then
+    unpack=gunzip
+    ext=gz
+elif test "${0##*/}" = "bunzip2.tests"; then
+    unpack=bunzip2
+    ext=bz2
+else
+    echo "WTF? argv0='$0'"
+    exit 1
+fi
+
+bb="busybox "
+
+unset LC_ALL
+unset LC_MESSAGES
+unset LANG
+unset LANGUAGE
+
+hello_gz() {
+# Gzipped "HELLO\n"
+#_________________________ vvv vvv vvv vvv - mtime
+$ECHO -ne "\x1f\x8b\x08\x00\x85\x1d\xef\x45\x02\x03\xf3\x70\xf5\xf1\xf1\xe7"
+$ECHO -ne "\x02\x00\x6e\xd7\xac\xfd\x06\x00\x00\x00"
+}
+
+hello_bz2() {
+# Bzipped "HELLO\n"
+$ECHO -ne "\x42\x5a\x68\x39\x31\x41\x59\x26\x53\x59\x5b\xb8\xe8\xa3\x00\x00"
+$ECHO -ne "\x01\x44\x00\x00\x10\x02\x44\xa0\x00\x30\xcd\x00\xc3\x46\x29\x97"
+$ECHO -ne "\x17\x72\x45\x38\x50\x90\x5b\xb8\xe8\xa3"
+}
+
+# We had bunzip2 error on this .bz2 file (fixed in rev 22521)
+test1_bz2()
+{
+$ECHO -ne "\x42\x5a\x68\x39\x31\x41\x59\x26\x53\x59\xbf\x4b\x95\xe7\x00\x15"
+$ECHO -ne "\xa1\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
+$ECHO -ne "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xef\xff\xff\xfb\xff\xff\xff"
+$ECHO -ne "\xff\xff\xff\xe0\x16\xe6\x37\xb7\x77\xb0\xfb\x22\xb5\x81\x40\xf5"
+$ECHO -ne "\xa7\x69\xa4\x47\xab\x61\x4d\x6a\x3d\xef\x75\x7b\x22\xaf\x71\x9b"
+$ECHO -ne "\xb3\x5a\x93\xbb\x78\x00\x79\xbd\xc0\x07\xae\x1b\xdb\xc5\xc6\x6b"
+$ECHO -ne "\x7a\x7b\xd3\xd7\x38\xbb\x70\x5e\xf0\xd1\x08\x9a\x64\x26\x53\x68"
+$ECHO -ne "\x03\x53\x4d\x32\x30\xd2\x61\x31\x13\x68\xd1\xa6\x4c\x41\x89\x84"
+$ECHO -ne "\x31\x4f\x40\xd2\x79\x14\xf0\xa3\xda\x1a\x00\x65\x4f\x53\xd9\x28"
+$ECHO -ne "\xd3\xf4\x4c\x13\x26\x4c\x4c\x64\x34\x9a\x6a\x7a\x99\x3c\x12\x78"
+$ECHO -ne "\xd2\x68\xd4\xda\x6a\x79\x32\x64\xd1\xa8\x1b\x13\x01\x4f\x29\xea"
+$ECHO -ne "\x6c\xa3\xd2\x07\x94\x06\xa6\x44\x01\x4f\x11\xa3\x4d\x13\x4d\x31"
+$ECHO -ne "\x32\x4c\x98\x0c\x9a\xa6\xda\x29\x3d\xa4\xf1\x24\xfd\x1a\xa3\x7a"
+$ECHO -ne "\x93\xf4\xa7\xb5\x3d\x51\xe2\x47\x94\xf2\x8f\x29\xb2\x9b\x29\xe9"
+$ECHO -ne "\x34\x79\x4f\x46\x9e\x84\x6a\x69\xea\x69\xa7\xa9\xb5\x03\x27\xa8"
+$ECHO -ne "\xf1\x40\x32\x7a\x13\x10\x00\x3d\x41\x90\x00\xd0\x1e\x84\x0d\x1b"
+$ECHO -ne "\x53\x41\xa3\x21\x93\xd0\x83\x53\x10\x99\x34\x24\xf5\x32\x99\x34"
+$ECHO -ne "\xd2\x7a\x86\xca\x6c\x28\xda\x6d\x29\xa6\x4d\x31\x0c\xd4\x7a\x69"
+$ECHO -ne "\x1e\x93\x23\xca\x1e\x93\x4d\x03\x26\x9a\x68\x01\xa0\xc9\xa0\x1a"
+$ECHO -ne "\x00\x34\x00\x00\x69\xa0\xf4\x80\x0d\x00\x00\x34\x06\x86\x80\x34"
+$ECHO -ne "\x00\x00\x00\x34\x00\x48\x88\x84\x53\x68\x4f\x45\x3d\x51\xfa\x4d"
+$ECHO -ne "\x4c\xda\x9a\x8d\xb5\x4c\xd4\xf2\x35\x1b\x51\xb4\xd3\x14\xf5\x0f"
+$ECHO -ne "\x50\xf5\x0f\x24\xd3\x32\x23\xd4\xcd\x21\xa6\xd4\xd0\xd0\x69\xa0"
+$ECHO -ne "\xf4\x8d\x3d\x43\xd3\x51\xea\x6c\x90\xd0\x68\xf4\x40\x00\x07\xa8"
+$ECHO -ne "\x19\x3d\x47\xa8\x1e\xa0\xd0\x34\x00\x0d\x1a\x06\x80\x01\xe9\x00"
+$ECHO -ne "\x64\x20\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+$ECHO -ne "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+$ECHO -ne "\x00\x00\x48\x92\x1a\x46\x9a\x02\x9e\x24\xc5\x4f\xc9\x91\x4f\x29"
+$ECHO -ne "\xec\xa9\xea\x34\xd3\xd2\x68\x68\x00\xf4\xd4\xf5\x19\x00\x34\x00"
+$ECHO -ne "\x06\x4d\x00\xd0\x1a\x1a\x03\x20\x01\xa0\x00\xf5\x00\x0d\x06\x9b"
+$ECHO -ne "\x50\x36\xa3\x35\x00\x03\x40\x03\x40\x68\xf5\x34\x00\x00\x00\x71"
+$ECHO -ne "\xe4\x3c\x0c\x0f\x0f\x3d\x23\x9a\x7f\x31\x80\xae\x1a\xe1\x09\x03"
+$ECHO -ne "\x2c\x61\x63\x18\xfc\x1a\x28\x0e\x9a\x85\xc3\x86\x96\x11\x94\x88"
+$ECHO -ne "\x0f\x92\x11\x8a\x1a\xb4\x61\x8c\x38\x30\xf3\xa9\xfb\xbe\xcd\x8d"
+$ECHO -ne "\xc4\xb7\x7a\x52\xad\x92\x9f\xde\xe6\x75\x74\xb7\xbb\x0b\xf5\x4c"
+$ECHO -ne "\x97\xd9\x49\xc8\x63\x9b\xa6\xba\x95\xe8\x46\x70\x11\x71\x55\x67"
+$ECHO -ne "\x17\xe3\xab\x91\x7d\xbe\x0d\x56\x78\x61\x98\x41\x28\x2c\xb5\xa3"
+$ECHO -ne "\xd1\xb8\x76\x80\xc8\xad\x9b\x79\x6f\x8a\x7a\x84\x44\x35\x24\x64"
+$ECHO -ne "\xa0\xde\x18\x80\x11\x14\xcd\xb0\xc6\xe4\x31\x49\x71\x53\xaf\x5d"
+$ECHO -ne "\x20\xc9\x59\xdc\xa2\x53\x06\xf0\x4f\xc8\x09\x21\x92\x6b\xf2\x37"
+$ECHO -ne "\x32\xcb\x75\x91\x75\x41\xc0\xe8\x47\x02\xb3\x8e\x0b\x17\x47\x42"
+$ECHO -ne "\x79\x21\x3c\xec\x6c\xb0\x84\x24\x96\x45\x4a\x8e\xbb\xbe\xc2\xe0"
+$ECHO -ne "\x16\x85\x76\x43\x26\xd5\xd0\x58\xdd\xf7\x83\x65\x44\x8f\xbe\x6c"
+$ECHO -ne "\x72\xe1\x5b\x1a\x0e\x3a\xbb\x51\xcf\xbd\x9b\x3a\xd0\xd5\x39\x5b"
+$ECHO -ne "\x23\x7d\xc9\x0b\x08\x0a\x23\x7b\x3a\x00\x67\xa1\x76\xa5\x19\xab"
+$ECHO -ne "\x48\xbd\x54\xaa\x8f\xaf\xb6\xe8\xd5\x91\x0f\x3e\x4b\x3a\x8d\xc9"
+$ECHO -ne "\x48\x02\xc2\x6b\xfc\xef\x0a\x9b\xf1\x67\xd0\x45\x48\x45\x05\xc0"
+$ECHO -ne "\x07\xc4\x47\x96\x6e\x79\xac\x31\x49\xcf\x1f\xa8\x3b\xdc\x1d\x44"
+$ECHO -ne "\x83\x84\x04\x49\x9f\x25\x01\x4b\x41\x80\x14\x1b\x9d\xaf\xfc\xb5"
+$ECHO -ne "\x46\x22\xca\x96\x4e\xd5\xe3\x08\x7d\xab\x17\x0b\x65\xcd\xa7\xd4"
+$ECHO -ne "\x5e\xd1\x8a\x27\xc8\x60\xe9\x17\xa3\xc9\xb4\x26\xf8\x58\xb2\x4a"
+$ECHO -ne "\x15\x52\x8e\x15\x20\x72\xb2\x0e\x4f\x64\xcb\x2b\xa3\xd9\xf0\xa6"
+$ECHO -ne "\x6f\x0b\x50\xed\x4e\xa4\x28\xe8\xe0\x05\x2d\x15\x26\x2c\x3d\x9f"
+$ECHO -ne "\x87\xc4\xd1\xd3\x69\xe2\x1c\xea\x41\x99\xbd\x43\x59\x9f\x06\x57"
+$ECHO -ne "\x06\xb4\x72\xe7\x45\x2c\xd2\xcf\x5f\x66\xa5\xeb\x58\x6a\xc0\x37"
+$ECHO -ne "\x82\x81\xf6\xdc\x1c\x35\x5b\xc6\xf1\x92\x4e\xe0\xe2\xd2\x12\x82"
+$ECHO -ne "\x92\x97\x14\xe5\xac\x49\x9f\xfd\x16\x46\xc6\xc2\xf1\x48\x86\x05"
+$ECHO -ne "\xe6\x74\xac\x9a\x52\x49\x64\x45\x25\x43\x08\x06\x03\x63\x49\x91"
+$ECHO -ne "\x9a\x92\x96\x5b\xe3\x2f\x11\x51\xcd\xe3\xa3\x9f\x3e\x8f\x9f\xab"
+$ECHO -ne "\x92\xbc\x6d\x36\xa3\xd1\x71\xa4\x4a\xb6\xe1\x49\xb8\xdc\x2c\x90"
+$ECHO -ne "\xb2\xd6\xfb\xa6\xc6\xd9\xa4\x5b\x9b\xe5\xd6\x59\xb3\x76\x37\xeb"
+$ECHO -ne "\xa6\x3e\xf0\x4c\x98\x1d\x73\x04\x01\x06\x8c\x10\x00\x3b\x2b\x04"
+$ECHO -ne "\x55\xf4\xfd\xec\x9d\xad\xc7\x2b\xbd\xe3\xda\x4b\xee\x28\x5d\x7a"
+$ECHO -ne "\xbe\xa6\xb9\xe0\x81\x15\xa6\x09\x26\x52\x60\xcc\x03\x30\x66\x60"
+$ECHO -ne "\xb9\xd0\x79\xfd\xb6\xb3\x85\xac\xd1\xc4\x4c\xbf\x80\x20\x44\x45"
+$ECHO -ne "\x7f\x72\x27\xff\x14\xc2\xc0\x81\x02\xab\x32\x20\x43\x46\x06\x7f"
+$ECHO -ne "\xb7\xc2\xb9\xf6\x39\x7b\x0b\x0c\xcb\xe7\x6e\x03\xe3\x20\x46\x82"
+$ECHO -ne "\x4a\x01\x23\xbb\xb0\x0c\xb5\x6f\xf7\xfb\xfc\xf5\xf2\x3c\x8e\x7e"
+$ECHO -ne "\xcb\x77\x6f\x7e\xc3\x71\x7c\x44\x3f\xbc\x3c\x54\xb8\x40\x27\x78"
+$ECHO -ne "\x63\x4d\x83\x22\x6a\x0a\x00\x0e\x8d\xa5\xfa\x5e\xe5\x89\x55\xa4"
+$ECHO -ne "\x18\x60\xc2\xa6\xd6\x17\x98\x23\xf0\x07\x44\x45\x18\xa4\x68\xd9"
+$ECHO -ne "\xcc\x0d\xe3\x81\x06\x09\x0c\x17\xaf\x52\xad\x85\x83\x5d\x09\x30"
+$ECHO -ne "\x0d\xa9\xb3\xe6\x45\x85\x9e\x26\x47\xab\x7d\x14\xe1\x8a\xb9\xfe"
+$ECHO -ne "\x0a\x0f\x8d\x68\x73\x73\x5c\xa3\x0b\xb5\x29\x0c\xd8\xde\xc2\x30"
+$ECHO -ne "\xbe\x61\xce\xbd\x4d\x3a\x03\xef\xe9\x45\xef\xeb\x07\xe8\x6b\x7d"
+$ECHO -ne "\xd2\xf4\x92\x8f\x91\xa1\x6e\x85\x2b\x73\xaf\x01\x8a\xd2\x0f\x52"
+$ECHO -ne "\xed\x65\x9f\xe6\x15\x47\xb2\x71\xd3\xbc\xee\xde\xff\x10\xfa\x4d"
+$ECHO -ne "\x7f\x9d\x5a\x4b\x13\x4a\x92\xd6\x85\xb2\xef\xe1\xbb\x92\x80\x4a"
+$ECHO -ne "\x45\x70\xa0\x4e\xe6\xf3\x39\x9a\xf6\x55\xee\x80\xc5\xa0\xff\x9d"
+$ECHO -ne "\xb6\x66\xe6\xcc\x81\xb2\xdc\xd6\x39\xb7\x06\x2c\xd6\x3b\x27\x0e"
+$ECHO -ne "\x5d\x01\x92\x5c\xf3\xbe\x3d\x46\x8a\x46\xa4\xd4\x03\x21\x86\x8e"
+$ECHO -ne "\x68\x05\x3b\xf0\x66\x69\x4c\x61\xf0\x39\x1c\x9d\xe2\x74\x3b\x5f"
+$ECHO -ne "\xd7\x87\xdc\xd3\xeb\x59\x50\xb6\x6d\x75\xc9\x5b\xdc\x4d\xb7\x29"
+$ECHO -ne "\x0c\x64\x9c\x5c\x22\xd1\x44\xd7\x01\x68\x0a\x26\x25\x7d\x6a\x76"
+$ECHO -ne "\x1c\x1b\xbf\x7a\xa5\xeb\x42\x8f\x2f\x93\xa3\xc1\xca\xe3\x9f\x46"
+$ECHO -ne "\xfd\x77\x07\x27\x2d\xaf\xbb\x1a\x13\x5b\x86\x94\x00\x90\x86\xc1"
+$ECHO -ne "\x24\x8d\x86\x22\x56\xbe\x06\xe1\xa1\x44\x4c\x36\xe2\x22\x08\x21"
+$ECHO -ne "\xb2\x20\x6d\xb6\xdb\x6c\x6e\x26\x26\x06\xc0\x26\x09\x94\x09\x75"
+$ECHO -ne "\x2c\x10\x4b\x44\xb0\x1b\x44\x26\x11\x58\x10\xdf\x2c\xc1\x55\x8a"
+$ECHO -ne "\xad\xb2\xa3\x08\x67\x34\xe5\x83\x95\x0a\x08\x82\xc1\x8a\x06\xdd"
+$ECHO -ne "\xb1\x32\x14\xa5\x27\x78\xca\xb6\xd1\x57\x5a\xc9\x2a\x06\x05\x29"
+$ECHO -ne "\x0c\x88\x28\xd2\x86\xa5\xa9\x69\x51\x81\x46\xa1\xa4\x81\xb1\x8d"
+$ECHO -ne "\xb1\xb0\x01\x83\x49\x4b\x15\x1a\x6e\x13\x24\x68\x54\x60\x4b\x4e"
+$ECHO -ne "\x21\x39\x82\x1c\x8d\x02\x6d\x23\xc3\x30\x25\x83\x69\x05\x11\x05"
+$ECHO -ne "\x4d\x24\x04\x4e\x0c\x53\x81\x25\xce\x34\x10\xd0\x04\xd4\x98\xa1"
+$ECHO -ne "\x21\x0b\x7e\xc4\x09\x11\x30\x82\x8f\x68\xc4\x13\x48\x0a\x30\x17"
+$ECHO -ne "\x4f\xaf\x80\x52\xd0\x36\x22\xd6\x43\x48\x15\xf6\xa1\x82\x84\xdc"
+$ECHO -ne "\x44\x34\x07\x52\xc4\x2c\x56\xb7\xaf\xa8\x3b\xb1\x08\x4b\x6b\x6c"
+$ECHO -ne "\x24\x05\xce\x1a\x10\xe2\x02\x31\x22\x25\xb8\x23\x65\xd0\x52\x9b"
+$ECHO -ne "\x4a\xcb\x64\xae\xbd\xe8\xda\x30\xb4\x25\x79\xa4\xbc\xe6\xe0\xf3"
+$ECHO -ne "\xde\x82\x23\x84\xce\xe5\xb9\xc9\xe9\xeb\x69\x78\x2f\xbc\x76\x6d"
+$ECHO -ne "\x58\x86\xc4\xa5\x82\xfa\xad\x61\x75\x62\x0c\xb6\x9b\x00\xdf\x30"
+$ECHO -ne "\x4a\xd6\x83\xaa\x60\x8a\x33\x7c\xd2\x12\xf5\x6c\x48\x52\xc5\x85"
+$ECHO -ne "\xe2\x6f\x37\x73\xc7\xbc\xad\xea\x27\x27\xa8\xef\xf7\xef\x59\x17"
+$ECHO -ne "\x65\xb6\xe1\xd8\xdd\xb5\x93\x42\xd0\x29\x5a\x18\x76\x08\xdb\xe5"
+$ECHO -ne "\x38\xf9\xa8\xe4\xa1\xa2\xd4\x40\xa0\xfd\x45\x18\x4b\x3c\xa6\x85"
+$ECHO -ne "\x02\x94\x8c\x88\xa9\x71\x87\x40\x96\x4d\x23\x26\xf4\x17\x44\xb8"
+$ECHO -ne "\x78\x1e\x71\xe3\x3b\xee\xc6\x4b\x87\x88\xfd\x2b\xb5\x8b\x1b\x53"
+$ECHO -ne "\x0b\xab\xd6\x47\x23\xa7\xcf\x78\x3a\x25\xd7\x3c\x77\xb3\x8e\x00"
+$ECHO -ne "\x37\x83\x11\xbb\x68\xf5\xed\x0a\x1a\x4d\xa3\x90\x68\xea\xed\x49"
+$ECHO -ne "\x8d\xb6\x80\x69\x83\x67\xcf\x65\x5a\xec\xda\x12\xe6\x5a\x47\x5a"
+$ECHO -ne "\x3c\x63\x50\x41\x73\x40\x83\xc7\x69\xbc\x46\xa7\xb1\xed\x79\x3c"
+$ECHO -ne "\xfe\xdf\x27\x4b\xbe\xeb\x68\xec\x83\x00\x6b\x7b\x08\x4a\x6e\x0c"
+$ECHO -ne "\x2d\x16\xba\x1a\x96\xa1\x03\xc3\x63\x9e\x7a\xce\x8b\xe2\xae\x51"
+$ECHO -ne "\xfb\x7d\xed\x5d\xfb\xbc\xed\x04\x6f\x1f\x21\xfc\x69\x3c\xb1\xaa"
+$ECHO -ne "\xdf\xbf\xa0\xab\xc3\xcc\x6a\xbf\xe7\x96\xbe\x36\xb3\x23\x32\x1c"
+$ECHO -ne "\xb5\x18\x44\x54\x51\x81\xb4\x63\xc7\x99\x84\x06\xcb\xaf\x5b\x05"
+$ECHO -ne "\x4f\x82\xf5\x93\xb4\xc3\xcf\xdb\x65\xb8\x8d\xae\xa1\xc2\xf0\xdf"
+$ECHO -ne "\xa7\xe5\xf3\x37\xd2\x57\x73\x0d\x89\xb8\x21\x10\x9a\x43\xe9\xe0"
+$ECHO -ne "\x09\x1a\x40\x49\xa0\xcc\x03\x30\x46\x66\x66\x0c\x12\x48\x89\xff"
+$ECHO -ne "\x57\xe8\xd2\x7c\x3e\x8d\x9e\x46\x7f\x97\xfc\x3b\x12\x95\xd2\xdf"
+$ECHO -ne "\x2f\xb1\xc8\x7d\x61\xdb\xb2\x8a\xdd\xbf\xf3\x7e\x08\xcc\xad\x16"
+$ECHO -ne "\xbe\x45\x13\xf2\x7f\x14\x5a\x79\x2e\xb5\xbb\x78\x0c\x22\xc6\x10"
+$ECHO -ne "\x31\xce\x9c\x6b\x1d\x48\x11\x16\x4c\xdf\x98\x12\xf3\x41\x05\x81"
+$ECHO -ne "\xd3\x24\x94\x92\x37\x51\x5d\xdc\x51\x08\xd3\x73\xba\x89\x42\x3f"
+$ECHO -ne "\xcb\x5c\x4c\xb2\x16\xcb\x04\xcd\x86\xb2\x05\x8a\xc3\x56\xc8\x83"
+$ECHO -ne "\x0b\x2e\x90\x31\x86\x5c\x68\xb9\x8d\xbc\xbf\xf2\xe2\xd2\xb0\x0b"
+$ECHO -ne "\x76\x2b\x3d\x79\xba\x3f\x9b\xe3\x8e\xc4\xf5\xed\xe0\xf7\xdd\xdb"
+$ECHO -ne "\x97\x5f\x9a\xb3\xfc\x50\xbf\x89\xf4\x7a\x38\xa3\x44\x0c\x50\x5d"
+$ECHO -ne "\x7c\xbb\x65\x47\xf1\x33\xd6\x67\xa4\xe0\xf0\x68\x58\xe9\x6c\x40"
+$ECHO -ne "\x02\x6b\x01\x20\x40\x84\x89\x80\x08\xcc\x52\xa0\x20\x81\x98\x16"
+$ECHO -ne "\xa1\x90\xf8\xcd\xbe\x1e\xc7\x6b\x1d\xb5\x81\x6b\x04\xdb\x4c\x43"
+$ECHO -ne "\x1a\xbc\xd4\x0d\xb6\x0d\xb3\x82\xc8\xc7\xf0\x13\xa8\xc5\x20\xd5"
+$ECHO -ne "\xbd\xb4\xc0\x5a\xdd\xe8\xd1\x31\x4f\xad\x88\x63\x30\x44\x0d\xd5"
+$ECHO -ne "\xc6\x56\x96\x28\xe2\xe8\xa8\xa9\x10\xdb\x1a\xa3\x21\xa6\xc5\xe6"
+$ECHO -ne "\xf5\xb2\xa4\x6d\x8d\xb4\x31\xb5\xc3\xec\x3e\x8f\xd0\xeb\x35\xce"
+$ECHO -ne "\xdb\x02\x9c\x4e\x96\xcd\x40\x14\xcd\x97\xb9\x0a\xe3\x09\xf5\x49"
+$ECHO -ne "\xfe\x1e\xc7\xc5\x57\xb9\x96\xe9\xf5\x8a\x56\xdf\x63\xda\x8a\xea"
+$ECHO -ne "\x41\x97\x74\x7b\xa6\x57\x99\x8d\xb0\x78\x60\xe4\x04\xd7\xe4\xbf"
+$ECHO -ne "\x89\x71\xa5\xc8\x93\x42\x02\x53\x7a\x6a\x9d\x99\xc9\xd3\x2b\x87"
+$ECHO -ne "\x75\xb2\x8f\x19\x86\x28\x2b\xc3\x2b\x67\x95\x72\xfb\x13\x39\xb5"
+$ECHO -ne "\xca\x8c\x43\x88\x1c\xdc\x47\xb6\xdb\x05\xaf\x8e\x31\x54\xb8\xbd"
+$ECHO -ne "\x98\x8b\x1d\x1f\x17\x87\x9d\x6d\x05\xca\xa8\x90\x49\x10\xbb\x67"
+$ECHO -ne "\x2f\x92\x61\x43\xfe\xe2\xd6\x18\x6d\x2a\xc0\x14\x96\x9a\x2a\x65"
+$ECHO -ne "\x48\x04\xc7\x2d\x76\xa6\x1f\xc5\x79\x36\x28\x69\x6f\x09\xb6\x90"
+$ECHO -ne "\xc3\x55\x6d\x98\xf0\xbd\xce\xb1\x37\xf4\xc4\x90\x1c\xdf\x5a\x27"
+$ECHO -ne "\xbc\x24\x38\x52\x75\xc0\xee\xc9\x05\x5a\xd7\x2b\x61\xfd\xba\xfb"
+$ECHO -ne "\xea\x9f\x65\x39\x9f\xe7\xc9\xc3\x0e\xa9\x3a\x20\x50\x87\xb6\x08"
+$ECHO -ne "\xc7\x80\x92\xe2\x60\x21\xd2\x2d\x51\x12\xf8\x46\x60\xbd\xf4\x65"
+$ECHO -ne "\xd5\x7b\x1a\xa7\x79\xb7\x73\x79\xe9\x0d\x60\x34\xc3\xb0\x58\xc8"
+$ECHO -ne "\xcc\x42\x7b\xb0\x56\x8c\xde\x66\x72\x23\xc2\x59\xe6\x9f\x83\x6a"
+$ECHO -ne "\xef\x4a\x9e\x1e\xf3\xd5\xde\x52\x32\x14\x8a\x2d\x0b\xf0\x1e\x5b"
+$ECHO -ne "\x7c\x4a\x34\x4d\x72\x4f\x1d\x8f\x97\xe8\xc9\xcd\xe2\xb9\x03\x36"
+$ECHO -ne "\x9f\x89\x97\xc3\x19\x8d\x8d\x84\x41\x0c\x03\x34\x18\x41\x20\x10"
+$ECHO -ne "\x26\x4c\x10\x18\x50\x5e\xd7\x93\x1f\x31\xf7\x54\xb3\x43\x4d\xd7"
+$ECHO -ne "\x48\x69\xcf\x7d\x29\x2f\x7f\x8f\x11\xf2\x4c\x3f\xcd\xe7\xa2\xe1"
+$ECHO -ne "\x09\x9a\x1a\x6c\xc6\xf3\xcf\x33\xe5\xb5\x8f\x6e\x41\xf1\x80\x07"
+$ECHO -ne "\x4d\x7f\xbe\x1b\x37\xdd\xe3\x64\xb8\xa2\x59\x90\x2c\xa2\xbe\xf4"
+$ECHO -ne "\x82\x2a\x80\x46\x4d\x1a\x8c\x88\x5a\x18\x30\x64\x0a\x5a\x57\x37"
+$ECHO -ne "\x63\xe9\x6d\x8a\x6d\x5f\x88\x5e\x6d\x41\x33\x60\xfd\x45\x90\x7e"
+$ECHO -ne "\x15\xaa\x95\x6f\xbd\xfc\xe9\x0b\x34\xe4\x3b\xa8\x41\x78\x1c\x55"
+$ECHO -ne "\x62\x5d\xb2\x19\xdd\xeb\x69\xeb\xef\xe1\xbf\x7b\xeb\x62\x23\x8a"
+$ECHO -ne "\x61\x14\x9f\x22\x53\x08\x6a\x31\xba\x30\x24\x1e\x54\x83\xae\xbd"
+$ECHO -ne "\x87\xa1\x71\xf0\x3c\x7d\x94\xa1\x2c\xea\xff\x84\x76\x77\xd2\xc9"
+$ECHO -ne "\x9f\x2f\x9c\xc7\x83\x3f\x89\x5d\x1b\x5c\xc3\x0f\xfa\xd2\x93\x32"
+$ECHO -ne "\xfc\xed\xa6\x26\x86\x98\x1b\x05\x10\x20\x27\x4c\x95\x3f\x6d\x94"
+$ECHO -ne "\x82\x5a\xa8\x68\x72\xae\xd7\xae\xdb\xaf\x26\xb6\x5a\x89\x30\xe7"
+$ECHO -ne "\xd0\x5a\x7c\xc6\x66\xfa\xc3\x85\x7d\x26\xee\xb3\x34\xc2\xac\x70"
+$ECHO -ne "\x88\x03\x15\xc6\xee\x55\x45\xde\x1c\x10\x01\xe6\x3b\x36\x26\x11"
+$ECHO -ne "\xbe\xec\x54\xea\xd8\x20\x1d\x35\x00\xd1\x8c\x00\xe8\xf5\x21\x97"
+$ECHO -ne "\x26\x06\x69\x87\x55\xa3\xc8\xf6\x58\xcc\x60\x12\x30\x0b\x8a\x50"
+$ECHO -ne "\x01\x57\x30\xdc\x9a\x01\xd4\xa4\xcd\xd6\x69\x23\x0f\xc3\xb8\x85"
+$ECHO -ne "\x12\xbb\x8e\xdf\xc5\xf1\xf3\x7c\xc9\x7a\x24\x25\x07\x9c\x86\x97"
+$ECHO -ne "\x68\xb5\xad\x0b\xba\x2e\xe8\x6f\x7f\xa1\xed\x4f\x0c\x34\x7b\xc8"
+$ECHO -ne "\x84\x10\x08\x2a\xcc\x19\x59\xbd\xbc\xe4\x3d\xa8\xd9\x35\xaf\x8b"
+$ECHO -ne "\xa7\x0a\xad\x42\xe8\x02\x90\xe6\x8e\x76\x5d\x0f\x3b\x87\xb8\xe4"
+$ECHO -ne "\x65\x4e\x5f\x0d\xe8\x26\xaf\x2a\x94\x9a\x2e\x21\x9a\x19\xb9\xa0"
+$ECHO -ne "\x8d\x26\x78\xa1\x4b\x6e\xf6\xd7\x29\x66\xdb\x49\x09\xa0\xca\x4d"
+$ECHO -ne "\x32\xb0\x31\xf5\x73\xe1\x67\xce\xe0\x5a\x79\x84\xa4\x22\xd4\xc9"
+$ECHO -ne "\x43\x59\x08\xa8\xd5\x5e\x8c\x72\x61\x70\x9a\xa6\x42\xc0\x42\x22"
+$ECHO -ne "\x2d\xd0\xbe\xb1\x49\x6e\x36\xbb\x8d\x8f\x03\x9b\xb4\xdb\x5a\x77"
+$ECHO -ne "\x3e\x29\x91\xc6\x73\x88\xef\x8c\xf7\xde\xe2\x2b\xc2\xce\xcd\x8c"
+$ECHO -ne "\x92\x60\x96\x29\x89\x99\x62\x99\x81\x36\x9b\x50\xc8\x70\xd6\x8d"
+$ECHO -ne "\xaf\x6b\x30\xba\xc7\x7a\xca\x4c\x56\x66\x66\x2d\xc7\xa5\xf7\x63"
+$ECHO -ne "\xa4\x55\x8d\xd4\x92\xdb\x2b\x6b\xb1\xa1\x96\x99\xd9\x25\xdb\x14"
+$ECHO -ne "\x1c\x49\x04\x67\x25\x45\x0a\x50\x1d\x20\xd8\x8d\xcf\xe7\x03\x20"
+$ECHO -ne "\xf0\xd7\xc0\xcc\x84\x20\x68\x4a\x63\x41\xa4\x6c\x32\x08\xa2\x37"
+$ECHO -ne "\x03\x6b\x42\x12\xbe\xa9\x4e\x9b\x97\x16\x92\x48\x56\x32\xae\x2c"
+$ECHO -ne "\x10\xc6\x31\x14\x8c\xcc\xd6\x23\x09\xf4\x64\x15\x9e\xf1\x35\x75"
+$ECHO -ne "\x98\x3a\x0c\x12\x29\xaa\xb7\x2b\x82\xe0\xf9\xcd\x05\xed\xb8\xbe"
+$ECHO -ne "\xb6\xf5\x6f\xcc\x41\xbc\x3a\x81\x39\x3b\x03\xe8\xb2\xab\xb6\xaa"
+$ECHO -ne "\xed\xa8\x58\xdf\xca\x06\xba\x64\x7b\xc4\xef\xec\x23\x38\x77\xec"
+$ECHO -ne "\xcf\xd7\xd2\xeb\x75\x3d\x26\xe2\xfa\x66\x49\x0b\x4a\xdc\xe3\x48"
+$ECHO -ne "\x64\x33\xc4\xb3\x93\xda\xdd\x3c\x08\x83\x7d\x91\x78\xe5\x61\x57"
+$ECHO -ne "\x67\x37\x73\xe1\x05\xbb\x96\x3e\x26\xc7\x6c\x44\xb5\xfb\x80\xb2"
+$ECHO -ne "\xd9\xa0\x99\x6b\xbf\x74\x62\xb7\xf7\x14\xec\x07\x12\xfc\xe6\x1b"
+$ECHO -ne "\xf1\x1d\x24\xfe\xd0\xb9\x61\x76\x56\xa0\xa5\x8c\x63\xce\x96\x5d"
+$ECHO -ne "\x65\x4f\xae\xcc\x7d\x86\x2d\xd7\x74\x96\x67\xb7\x5c\x94\xa6\x30"
+$ECHO -ne "\xbd\xb3\x84\x56\x93\x1e\x44\xc5\x43\x5f\x65\x9d\x1a\x92\xb1\x9a"
+$ECHO -ne "\x4c\x46\x1f\xd2\x64\x54\xb6\x4e\x7e\xb2\x71\x75\xf6\xce\xac\xdc"
+$ECHO -ne "\x5a\xa1\xd4\xf1\xf5\x71\x6a\x93\x50\xd2\x8b\xb2\xb1\x7f\xaf\x20"
+$ECHO -ne "\xd2\xc9\xce\xeb\xfb\x1d\x4a\xff\x26\x89\xa2\x60\xed\x8a\xeb\xa7"
+$ECHO -ne "\x6e\x92\xea\xb7\xef\x7a\xcc\xd9\x4b\xbb\x3e\xad\xc6\x7a\xfa\xbb"
+$ECHO -ne "\xe0\x25\x0c\x0f\xe2\x14\xf9\x2e\x0b\x5f\xd4\xbd\x8f\x5a\xae\xb6"
+$ECHO -ne "\xca\xc1\x5a\x89\x4c\x74\x36\xd3\x32\xab\x87\xa7\x7d\x57\x7f\x45"
+$ECHO -ne "\x1a\x1d\x45\xcc\xc8\xf1\x36\x8c\x4d\x6e\xc9\x01\xb8\x7a\x99\xdc"
+$ECHO -ne "\x4d\x9a\xa1\xc3\x7a\x81\xac\xa9\x40\x20\xc1\x18\xc7\x1e\x0d\xeb"
+$ECHO -ne "\xf7\x53\x9b\xcb\xe2\x64\x4e\x17\x1c\x6a\xd7\x74\x6b\xe4\x4b\xe7"
+$ECHO -ne "\x5f\x06\x31\xac\xe7\x5c\x64\x93\x05\x69\x13\x1a\x34\x52\x3e\x1a"
+$ECHO -ne "\xc8\xf6\xed\xde\x5e\x79\xf4\xe2\x04\xc3\xb6\xb3\x49\xdc\x7a\xe3"
+$ECHO -ne "\x52\x12\x1b\x32\xd9\xe2\x5c\x95\x5f\x69\x01\xde\x77\x16\x34\xf7"
+$ECHO -ne "\xda\x43\x2c\x56\x77\x21\xcc\x86\xc4\x4a\x14\xb8\x29\x28\x0a\xf1"
+$ECHO -ne "\x79\x8a\x9e\x94\x86\x6c\x6a\x6c\x0f\x15\xe6\xb4\x57\x92\xfc\x1f"
+$ECHO -ne "\x6d\x98\xbf\x2f\x0b\x97\xb3\x4b\xec\xe6\xd3\xf7\x94\xe4\x2c\xf3"
+$ECHO -ne "\x20\xfa\x42\x69\xd9\xb6\xb2\x96\x31\x09\x6b\xcb\xd2\x92\x14\x40"
+$ECHO -ne "\x69\x75\x9a\x83\x49\x44\xed\xe0\xdd\xa3\x3d\x09\xe0\xe4\xcf\x4f"
+$ECHO -ne "\x3b\x12\x84\xb6\x47\x2b\x1b\x7e\xc2\xac\x8d\xf6\x1c\x74\x26\xb0"
+$ECHO -ne "\x2a\x27\xf6\x03\xe3\xf9\xe3\xbb\x4a\x1a\x6f\xa2\xf4\x10\xb0\xe5"
+$ECHO -ne "\x70\x9a\x9b\xdf\xac\x42\x6a\xdc\x80\xc3\x80\x0c\x84\x26\xf0\x23"
+$ECHO -ne "\xd6\x5b\xcb\x8b\x3b\xe1\x65\xfe\xba\xad\x85\xe9\x1d\x88\xa4\xf8"
+$ECHO -ne "\x05\xb8\x58\x0b\xb1\x13\xa8\xd8\xea\xfd\x07\x68\xee\x6b\x5c\x88"
+$ECHO -ne "\x17\x49\x89\x0e\xa5\x7a\xe6\xa0\x9c\x3a\x06\x2d\x71\x84\x2c\xd2"
+$ECHO -ne "\x1b\x07\xfd\x43\x9b\x48\x9b\xae\x60\x54\x5d\xd3\x2b\xf1\xc0\x0d"
+$ECHO -ne "\x49\x01\x64\x34\x36\x77\x2f\x0e\xe7\x72\x35\x48\x2f\x05\xaa\xd5"
+$ECHO -ne "\xb4\x98\x77\xa3\x19\xa2\xf4\xb8\x11\xa7\xa6\x24\x91\xac\x1e\x09"
+$ECHO -ne "\x38\x04\xc6\xff\x0b\x7d\x36\xc2\xcb\xb8\x9c\x7e\x7b\x49\x8c\x4e"
+$ECHO -ne "\xbb\x37\x19\x18\x83\xc5\x23\x03\x6c\xcb\x51\x24\xe5\x42\x85\xc7"
+$ECHO -ne "\x73\x13\x2c\xc8\x22\x28\x50\x83\xbc\x3a\x8e\x60\xac\xb1\xda\x18"
+$ECHO -ne "\x24\x6d\x64\xd0\xa9\x65\xcd\xd6\x5a\xa7\xaa\xc6\xed\x32\x76\x7b"
+$ECHO -ne "\x07\x90\xb4\x7b\x5d\x16\x88\x9b\xd7\x5e\x0a\xb7\xbf\xbf\xc4\x5d"
+$ECHO -ne "\x1c\xbd\x39\xf3\x17\xae\x50\xaa\xc7\xa4\xe9\xad\xa5\xac\x04\xd9"
+$ECHO -ne "\xa4\x27\x5f\x79\x75\x29\x10\x69\x75\xe9\x06\x53\x7c\x66\x8b\x83"
+$ECHO -ne "\xf7\x7c\xfd\xcd\x16\xc3\x8c\x8e\x51\x6f\xcb\x68\x0a\x9c\x39\x39"
+$ECHO -ne "\xb9\x0b\x6a\x16\xc5\x4a\x22\xc0\x31\x09\x22\x28\xa0\x65\x69\x05"
+$ECHO -ne "\x30\x90\xc1\x18\x22\x05\x9e\xad\xa9\xc3\x54\x3e\x27\xa9\xc4\x41"
+$ECHO -ne "\x2c\x39\x03\xd2\x8e\x3f\x91\x9a\x4c\xc8\x68\x14\xe4\x1c\xa6\x5f"
+$ECHO -ne "\x0b\x57\x27\x09\x8a\x7d\xff\x47\x63\xa7\x5a\x29\x82\xa0\x3a\x28"
+$ECHO -ne "\x30\x9a\x2b\xf3\x69\x63\x18\xcd\xe2\x32\x66\x3c\xd7\x79\xdd\x12"
+$ECHO -ne "\x86\x34\xc6\x9e\x75\x05\x87\x39\x23\x72\x97\x71\x27\x64\xcd\xd9"
+$ECHO -ne "\xa6\x2e\x61\xd2\x37\xe4\xae\xc6\xc9\x81\xc0\x2e\x9f\xc6\xf9\x7f"
+$ECHO -ne "\xd9\x5e\xd0\xa9\x09\x97\x35\xa2\xe3\x4f\xe9\x19\x7c\xa5\xc7\x4d"
+$ECHO -ne "\x2d\x92\xec\xd6\xef\xda\x55\xf3\xa2\x95\x17\x1b\xce\xbe\x6b\x74"
+$ECHO -ne "\x70\xee\xdb\xa8\x42\x26\xb1\xcc\xc1\x31\x0a\x67\x92\x13\x9d\x9c"
+$ECHO -ne "\x12\x18\xa4\x08\x4d\x4d\xfc\x7c\xeb\x59\x6b\x22\x03\xaa\x97\xc3"
+$ECHO -ne "\x27\xa5\x21\x35\x68\xd2\x57\x54\xca\x58\x38\x82\xc5\x05\xa0\x71"
+$ECHO -ne "\x01\x1b\xce\x57\x1e\x20\xbf\x89\x96\x2a\x31\x8e\x6e\xaf\x7f\x35"
+$ECHO -ne "\x08\x10\xd9\x0e\x8a\x78\xb0\x48\x98\xa4\x64\x14\xa2\xcf\x23\x2d"
+$ECHO -ne "\x0a\x7b\x84\xe5\xfd\x29\x49\x15\x3d\x75\x39\xfd\xaa\xd6\xa4\xb9"
+$ECHO -ne "\x05\x12\x57\x31\x04\xdc\x26\x34\x16\x3f\xa7\x03\x32\x1d\x4b\x1d"
+$ECHO -ne "\x78\xdc\x9b\x79\x96\x9a\x87\x6e\xb4\x80\xaa\x01\x19\x33\x92\xb0"
+$ECHO -ne "\x16\xc9\x94\x9c\xe7\xa5\x63\xe6\x18\x13\xb2\x34\xbd\x98\x41\xd6"
+$ECHO -ne "\xa4\xc8\xb9\x6e\x06\x9c\x72\xf8\x49\xab\xd5\x47\x9e\xa1\xe6\xde"
+$ECHO -ne "\x62\xd0\xec\xaf\xbf\x1b\x8a\xaf\x63\xa0\x29\xbe\x3d\x87\xa0\x22"
+$ECHO -ne "\xce\x46\x4e\x18\x30\x7b\x3c\x3d\x86\xe1\x9e\xb6\x59\xef\x1c\x43"
+$ECHO -ne "\x65\xd0\x3d\x53\xd0\x41\x20\x40\xb7\x2b\xb1\xdd\x52\x2c\xdd\x68"
+$ECHO -ne "\x44\xc1\xbe\x40\x72\x61\xd7\x25\x5d\xf5\x69\xce\x3a\x3b\x2e\x9b"
+$ECHO -ne "\x13\x19\x79\x1a\xf0\xee\xb0\xe7\x17\x44\x45\xe8\x2d\x59\x50\xbc"
+$ECHO -ne "\x40\x67\x66\x12\x20\xcc\x43\x8a\x9c\x1d\xde\xac\x2d\x00\x76\xb2"
+$ECHO -ne "\x98\x8a\xa9\xde\x1c\xb6\x8b\x32\x19\x67\x1c\x67\x95\x41\x40\x60"
+$ECHO -ne "\xf3\x13\x44\xb8\xc5\x18\xa7\xca\xdd\x8c\x5a\x8f\x72\x69\xf1\x31"
+$ECHO -ne "\xa9\xd2\xeb\xac\x3e\x2f\xdc\xc7\xe0\x00\x78\x5d\x72\xff\x01\x95"
+$ECHO -ne "\x86\x4a\x90\x2b\xf8\x10\xc5\xc2\xd1\x9d\x7a\xc3\x65\xb1\xfd\x2d"
+$ECHO -ne "\x09\x0b\xcd\xdf\x03\x80\x3e\x44\x81\x65\x49\x4f\x50\x7e\x1f\x75"
+$ECHO -ne "\x97\xc6\x05\xda\x5a\xe9\xf6\xee\xe5\x66\xcc\x5e\x17\xe2\x8c\xb2"
+$ECHO -ne "\x06\x5b\xdd\x41\x0d\x26\xcc\x87\x0d\x37\x2e\x2d\x35\xe0\x5d\x93"
+$ECHO -ne "\xc5\xdf\x2d\xb4\xa2\xb1\x1b\x0e\x9b\xe6\x76\xb4\x28\x69\x5c\xe9"
+$ECHO -ne "\x4e\x27\x6f\x52\xcb\x4d\xb3\xc8\xaa\xea\xd3\x1a\x57\x00\xdf\x20"
+$ECHO -ne "\x2d\x42\xea\x6a\x18\x0a\xac\xae\x9a\x32\x08\x23\x99\xb7\xd8\xe5"
+$ECHO -ne "\x75\x3a\x65\x8b\x2f\xaa\x4f\x7b\x68\xd5\x66\x76\xf4\xec\x3d\xdb"
+$ECHO -ne "\xe9\x37\xdb\x69\x40\x6d\x35\x4f\x77\xfa\x8f\x07\x60\xac\x8e\x3b"
+$ECHO -ne "\x89\x46\x3c\x16\xd4\x4b\x6e\x71\x4f\x00\x10\x22\x14\x12\xca\x72"
+$ECHO -ne "\xe0\x6c\x54\x2f\x0e\x32\x8c\xba\x53\xad\x51\x48\xaf\xee\xb2\xca"
+$ECHO -ne "\x93\x4a\x46\x24\x1f\x09\x83\x69\x1c\x3f\x72\x50\x70\xff\x10\x74"
+$ECHO -ne "\x21\xef\x4a\x08\x38\x25\x4c\x54\xb6\x34\x83\x64\x99\x22\x0f\x02"
+$ECHO -ne "\x49\x58\x50\x74\xa3\xbe\xc2\x17\x05\xa7\x60\x55\xc4\x21\x52\x0c"
+$ECHO -ne "\x57\xee\x0f\x64\x6a\xa9\x73\x25\xa1\x2a\x94\x1d\x00\xca\x65\xc4"
+$ECHO -ne "\x39\xfc\x53\xa8\xe7\x4c\x07\x44\x5f\x29\x19\x98\x08\x16\x53\x1a"
+$ECHO -ne "\xba\xee\x8e\x2e\x16\x97\x66\x5b\x7c\xb5\x63\x2d\x31\x18\xdb\x64"
+$ECHO -ne "\xc5\x69\x15\xa9\xe8\x23\x5f\x92\xdb\x75\x60\x90\x6a\xbf\xb5\xba"
+$ECHO -ne "\xe5\xa5\x70\xce\x26\xd0\xc1\x63\xcb\x0e\x21\x67\x1e\x8e\x20\x32"
+$ECHO -ne "\xa1\x2d\x51\xfc\x32\xa0\xc9\xd0\x32\x91\x9a\xda\x45\x73\x2e\x97"
+$ECHO -ne "\x09\x17\x0c\xea\xe4\x89\x94\xe8\xad\x64\xd6\x78\x02\x07\x79\x06"
+$ECHO -ne "\xa4\x01\xce\xd0\xcc\x33\x20\x8d\xc9\x2d\x67\xdf\x85\x06\xb5\x21"
+$ECHO -ne "\x74\x61\x49\x99\x98\xec\x28\x06\xc4\xbd\x25\xb5\x62\x2d\xb0\xba"
+$ECHO -ne "\x5f\x4c\xc4\x33\x85\x42\x58\x11\xd4\xff\x27\x21\x3c\x57\x9e\xd9"
+$ECHO -ne "\xc4\xb1\x6d\x8d\x4a\x8c\x8a\x80\x6c\x1e\x16\x5f\xc1\xc4\x68\x4a"
+$ECHO -ne "\xca\x20\xb1\x40\x10\x1b\x1b\x6c\xf7\x82\xf8\xd4\x35\x29\x10\x76"
+$ECHO -ne "\x7d\x3a\x4d\x4d\x49\x9b\x62\x65\x66\xd4\xda\x81\x24\xca\x4a\x48"
+$ECHO -ne "\x48\x2f\x83\x48\xd1\x09\xdf\x2f\x17\x8b\xc5\x37\x89\x94\x15\xb1"
+$ECHO -ne "\x36\x58\xcd\x80\xb4\x19\xc5\xc6\xda\xda\x16\x95\x82\x14\xc5\x19"
+$ECHO -ne "\x61\x6e\xb5\xcc\x27\xb5\xf3\xdb\xef\x6e\x44\x37\xbf\xdc\x11\xf9"
+$ECHO -ne "\xa0\xf2\x78\x30\x85\xc0\xc0\x07\x67\x02\x66\x56\x7c\x76\xee\x7a"
+$ECHO -ne "\x97\x6e\x02\x5e\x08\xc0\x35\x02\x4a\x87\x39\x4c\xd6\xc4\xe0\x99"
+$ECHO -ne "\xcd\xd9\xda\x2c\x49\x18\x5c\x22\xb6\x51\x4b\xa0\x58\x8b\x7a\x55"
+$ECHO -ne "\x61\xdc\xa5\x21\x83\x1d\x47\x9c\x0b\xf4\x74\xba\x08\x85\xe4\xc8"
+$ECHO -ne "\x80\xbe\x80\x46\xfc\x46\x85\x60\x64\xa6\xc4\xc1\xae\x69\x67\x0b"
+$ECHO -ne "\x8e\xac\xa2\xc0\xf4\x6b\x6f\x7a\x9e\x00\xdd\x4d\x59\x57\x4a\x78"
+$ECHO -ne "\x08\x64\x08\x84\x80\x50\x34\xb1\x3b\xc7\x71\x3f\x3e\x1c\x1d\x4e"
+$ECHO -ne "\x4e\xa9\xb0\x32\x02\x10\x8e\x88\x71\xed\x87\x2c\x32\x4d\x57\x05"
+$ECHO -ne "\xf1\xba\xa0\xf9\x61\x30\x4b\x18\x65\x6e\x38\xf9\x41\xdd\xf1\x48"
+$ECHO -ne "\x63\x38\x50\x10\xc1\xac\x1b\xf2\x5b\xaa\x15\xf4\x89\x0e\xe9\x77"
+$ECHO -ne "\x80\x56\x50\x18\x81\x71\xd8\xdb\x0d\x6a\xce\xd2\xb6\x76\xbd\x35"
+$ECHO -ne "\xf0\x96\xe1\x06\x8b\x09\xab\x83\x21\x10\x10\x30\x68\x30\xad\xe0"
+$ECHO -ne "\xc2\x62\xa2\x99\x0b\x92\x17\x19\xab\xe3\x7a\xd1\x90\xae\x5c\x2b"
+$ECHO -ne "\x6e\xbe\x31\xec\x72\x78\x03\x7a\x85\x70\xe0\x67\x36\xe0\xdb\x63"
+$ECHO -ne "\x6e\xed\x26\x94\xcc\x9b\x4e\xa8\x23\x57\x56\xe1\x49\x61\x31\x5e"
+$ECHO -ne "\xc8\x2b\x81\x05\x23\x18\xdb\x68\x34\x0b\x6c\xf1\xfc\xc7\xdd\xdf"
+$ECHO -ne "\x1a\x39\xf8\xf6\x72\xb9\x4d\xc9\x80\xbf\x23\x93\x24\x76\xdd\x6d"
+$ECHO -ne "\x0a\x8f\x18\xe1\x81\x8f\x48\x7b\x48\x2e\xd0\xb5\xd0\xcb\xa1\x46"
+$ECHO -ne "\xae\x1c\x26\x02\xd2\xe0\xf4\x56\x8c\x8a\x01\x97\x4e\x5f\xd1\xde"
+$ECHO -ne "\x9a\x10\x31\x0d\x4c\xbc\x40\x06\xc5\x04\x92\x91\x88\x81\x58\x5d"
+$ECHO -ne "\x55\x13\xab\x4f\xaa\xbd\xee\xa0\x6a\x80\xb2\x83\xd0\x46\x31\xa0"
+$ECHO -ne "\xbc\x2c\xf9\x0d\xad\xe2\x62\xb0\xac\xa4\x91\x84\xb8\x31\x99\xb9"
+$ECHO -ne "\x45\xb3\x47\x1e\xc2\x96\xc9\x9d\xcc\xd3\xcc\x71\xc4\xf3\x9a\x92"
+$ECHO -ne "\x2b\xac\xc3\x8c\xe1\xdc\x40\x66\x64\xe8\x24\x35\x50\x26\x68\x0b"
+$ECHO -ne "\x79\x96\x81\xb6\x36\xc7\xa4\x82\x0d\x32\x65\xc3\x4c\x61\x49\x32"
+$ECHO -ne "\x09\x14\x22\xac\x37\x69\x34\xb4\x6c\xdd\xbc\x95\x54\x6b\x59\x53"
+$ECHO -ne "\xc6\x50\x32\x09\x99\x14\x8c\x18\x74\xcc\x05\x86\x7a\x06\x48\x50"
+$ECHO -ne "\x6e\xe0\xaa\x41\xbb\xb0\xbc\x19\xaa\x2c\x12\x9c\xcd\xa5\x1c\x6d"
+$ECHO -ne "\x19\x0a\x62\x02\xfe\xd3\x4a\xcc\x7c\x6a\xa5\x72\x06\x35\xfb\x8d"
+$ECHO -ne "\xf9\xab\x1e\x0b\x29\x73\x70\xb5\xe8\xf6\x54\xb6\x4c\xc8\xea\x30"
+$ECHO -ne "\x8c\xaf\xd0\xd3\xb0\x20\x59\x80\x61\x40\xc8\x19\x99\x6d\x97\xb3"
+$ECHO -ne "\xca\x66\x1e\x16\x3d\xa7\x74\xa6\x58\xf0\xd4\x00\x67\xdc\xbb\x8a"
+$ECHO -ne "\x4a\x7b\x75\xa4\x6e\x89\xc4\x44\x44\x3d\x72\xb4\x52\x8a\xc0\xc2"
+$ECHO -ne "\x11\x40\x22\x9a\x14\x09\x66\xc2\x03\xcc\x04\x86\x02\x03\xa6\x8a"
+$ECHO -ne "\xab\x60\xe0\xe8\xdc\x2b\x5d\x0d\x73\xb5\x8f\x74\xc6\xce\xdb\xb5"
+$ECHO -ne "\xa8\xe7\x95\x3f\x8b\xaf\xb9\x87\xbc\x63\xab\x84\xea\x93\x1e\x9d"
+$ECHO -ne "\xb4\xe0\x83\xc8\x4a\xc9\xc7\xb9\xc7\xf2\xc6\x25\x10\x58\xc0\x21"
+$ECHO -ne "\x64\xa1\x08\xd3\x10\x2f\x94\x40\x5a\x56\x17\xa1\x0f\xa6\xfb\xda"
+$ECHO -ne "\xd3\x12\x42\x31\x71\x09\xa5\x2e\x8b\xd1\x69\x5c\x99\x5b\x09\x52"
+$ECHO -ne "\xc6\x9b\x5a\x18\x0c\x06\x47\x42\x8a\xc3\xad\xef\x9a\xe9\x9d\xf6"
+$ECHO -ne "\x2b\x81\x72\x48\x05\x20\x16\x10\xa3\xc3\xc5\xd2\x71\x0e\xca\x04"
+$ECHO -ne "\x17\xef\xdf\x39\x64\x26\x4c\x9f\x22\xb4\x13\x1c\x3d\xe7\x55\x40"
+$ECHO -ne "\x2e\xd1\x91\x28\xc8\x1c\x68\x69\x65\x97\x13\x75\xfe\x5b\x5c\xb1"
+$ECHO -ne "\x9b\x5a\xf7\xd2\x02\xb2\x0b\x41\x36\x67\xe7\xa9\x10\x80\xd0\x5c"
+$ECHO -ne "\x64\x08\x67\xda\x56\x36\x53\x4a\xa8\xca\x16\x88\xc5\x79\xdd\x3e"
+$ECHO -ne "\x87\x71\x13\x39\xae\xfd\x2a\x93\x6e\xbb\x96\x02\x39\xea\xda\x5a"
+$ECHO -ne "\x87\xb8\xfa\x54\x2c\x49\xa3\xa0\xbb\xa5\xc4\x10\x5c\xd2\x10\x10"
+$ECHO -ne "\x0c\x88\xb2\xd4\xf4\x67\x6a\x93\x5b\xbb\x20\x06\xdc\x75\x7f\x3a"
+$ECHO -ne "\x9b\xa3\xb0\x76\x98\xd9\x77\x32\x97\xa5\xdc\x64\xa4\x7b\xa5\xae"
+$ECHO -ne "\xaa\x15\x2d\x59\x0c\xc1\x7a\x40\xd2\xc2\xbb\x45\x10\xe1\x9a\x46"
+$ECHO -ne "\x52\x91\xe4\x24\x21\x9c\x46\xee\x05\x57\x44\x5e\x41\xad\x5a\x08"
+$ECHO -ne "\x46\x0b\xa0\xdf\xb4\x59\x7a\xe4\x41\xa3\x0a\x59\x5e\x2b\x17\x20"
+$ECHO -ne "\x19\x02\x6c\xe6\xe2\x48\x85\x99\xb3\xba\xfc\x9c\xe3\xcd\xf9\x31"
+$ECHO -ne "\x5b\xf1\x86\x64\x9d\x8f\x93\x24\xa3\x29\x38\x94\xcb\x1e\x71\x87"
+$ECHO -ne "\x54\xf2\x27\x22\x4e\x57\x26\x9a\x82\xb5\x6e\x6c\x1c\xad\x2d\x2b"
+$ECHO -ne "\x22\x62\x0a\xd0\x23\x5d\x5a\x75\x15\xae\xa0\x26\x04\x21\x6d\x2c"
+$ECHO -ne "\xfe\x06\xd9\x60\x61\x20\x8e\xea\xef\xba\x59\x03\x64\xda\xe5\xb2"
+$ECHO -ne "\x30\xc0\x9c\xdc\xcf\x11\x77\xe9\x23\x54\x33\xb8\xe9\x05\xab\x4c"
+$ECHO -ne "\x5b\xb5\x4b\x2d\x03\x0c\x51\xc5\x80\x11\x51\xac\xeb\x8d\x4c\x25"
+$ECHO -ne "\x21\x98\x79\xb0\x38\x99\x9c\xbc\xe2\x96\xe9\x4a\xd0\xad\x56\x6a"
+$ECHO -ne "\x65\xe1\xd4\x90\x12\x4a\xa5\x48\x06\xc6\x48\x31\xac\xaf\x21\x0a"
+$ECHO -ne "\x56\xa2\x90\x12\xd7\x53\xa8\x48\x03\x75\x2e\x36\x14\xf4\x50\x89"
+$ECHO -ne "\x7c\x49\x4e\x4a\x2a\xbc\x46\xc3\x2d\x16\x4e\x42\x25\x28\xd4\xf2"
+$ECHO -ne "\x01\x47\xa3\x5a\xd1\x6a\x0c\x1c\x35\x9c\x52\xc8\x2d\xeb\xab\x15"
+$ECHO -ne "\x2a\x99\x51\x45\x22\x9c\x77\xaf\x85\x77\xbc\x84\xb2\xf5\x99\xe9"
+$ECHO -ne "\xd8\xd9\xc1\x90\x83\x02\xeb\xde\x8f\x91\x82\xa3\x0c\x73\x1a\x05"
+$ECHO -ne "\x6b\x9c\x98\x28\xc5\x56\x55\xe9\xf3\x74\x24\x81\x48\xaf\x21\xb4"
+$ECHO -ne "\x84\x2d\x6b\x45\x88\xc2\x90\xb1\x12\x12\x60\xc8\x6a\xec\x61\x33"
+$ECHO -ne "\xa2\xc3\xb3\x58\xbf\x29\x1c\x48\x97\x7a\xea\x20\x65\x03\x7f\x22"
+$ECHO -ne "\x45\xa5\xdd\x03\x5c\x52\xdc\x30\x85\xde\xd9\x47\x5e\xeb\x31\x65"
+$ECHO -ne "\x0b\xf3\x13\x80\xae\x3e\x07\x52\x2a\x47\xb9\x7b\x7c\xa8\x41\x79"
+$ECHO -ne "\x95\x2e\xcf\x3d\x60\x08\xe6\x26\x00\xd1\x82\x60\x70\x45\xa1\x4a"
+$ECHO -ne "\x48\xa2\x18\x76\x35\xb5\xe8\x6c\x0d\x42\xd2\xba\x2c\x13\x5b\x25"
+$ECHO -ne "\x27\xd4\x8d\x73\x7a\xf8\xd9\xfe\xf3\xd3\x81\x73\x83\xe8\x65\x60"
+$ECHO -ne "\xf3\x5b\x18\x07\x15\x04\x60\x67\x51\xca\xab\x91\x85\xdc\x61\x3a"
+$ECHO -ne "\xe9\x72\xc2\xd1\xa4\x68\xe2\x00\xc8\x0c\x5e\x82\xa0\x0b\x82\x16"
+$ECHO -ne "\x40\x82\xb2\x98\x7b\x76\xf8\x5b\xb5\xf6\x8d\xfb\xc9\x36\x8c\x1e"
+$ECHO -ne "\xa6\xc9\xb5\x95\xd8\x36\x28\x36\xee\xa9\xa2\x72\x66\x58\xf5\x90"
+$ECHO -ne "\x02\x95\xee\xa8\xfc\x79\xfa\x6f\x66\xf6\x47\xd6\x4f\x10\x95\x86"
+$ECHO -ne "\x54\x0d\xa0\x22\x26\x01\xbe\x63\xc5\xf1\xac\x36\x4a\xe1\x0f\x6b"
+$ECHO -ne "\xe7\xba\x4f\x20\x17\xc0\xf9\x02\x5d\xc4\xc0\xaf\xf0\x1f\x88\x54"
+$ECHO -ne "\xe4\x6e\xc0\xa2\xbb\x59\x6f\x82\x21\xbb\x50\x9c\x4e\x92\x83\x24"
+$ECHO -ne "\x23\xf8\x28\x4c\x45\x79\x88\x71\x3b\x05\x79\x98\x4f\xa1\x00\x2d"
+$ECHO -ne "\x6e\x68\x08\x46\x3b\xfb\xf0\x5c\x22\xf3\x42\x40\x7d\x5e\x67\x3f"
+$ECHO -ne "\xdf\xff\x2e\x73\x0d\x31\xb5\xc0\x78\x4c\x0f\x85\x0f\xdb\xe6\x2b"
+$ECHO -ne "\x86\x0f\xb3\x8f\x9d\x1a\xe6\xe2\xdb\x32\x68\xfb\x5a\x79\x0a\xf4"
+$ECHO -ne "\x25\x28\x01\x39\xd2\x7c\x05\x7b\x0b\x18\xbb\x9c\x68\x31\x8d\xb0"
+$ECHO -ne "\xfc\x69\x2d\x17\x2c\x33\x62\xa6\x24\x6f\x0e\xcc\xdc\xff\x7b\xdf"
+$ECHO -ne "\x78\xd3\x88\x5c\x4a\xbd\xeb\x14\xda\xe0\x0e\xd3\xf3\x5b\xd2\xaa"
+$ECHO -ne "\xd1\x64\x82\x42\x0c\xfe\x54\x90\x40\xeb\x75\x13\x63\x68\xb5\x52"
+$ECHO -ne "\x0a\xd9\x3b\xc5\xd0\x14\xe1\xd2\x35\xab\x2b\x8b\xed\x21\xf3\xe1"
+$ECHO -ne "\xcb\xed\xbc\x3e\x64\x81\x63\x5f\xaa\xf3\xb8\x05\x10\x29\xe1\x67"
+$ECHO -ne "\xae\x0a\x16\xbb\x71\x05\x02\x46\xba\xef\x30\xda\xe7\x3a\x18\x4b"
+$ECHO -ne "\xcb\x0e\x97\xd1\xe9\xf5\x7a\x73\xc1\x60\x91\xaf\x63\x4a\xc8\x57"
+$ECHO -ne "\xa6\xb0\x09\xb4\x95\x98\x87\xa9\xc4\x5e\x04\xe7\xef\xbf\xe5\x8c"
+$ECHO -ne "\x39\x8e\xe2\xdd\x9a\xc0\xbf\x37\x9c\x38\xa3\xad\xc6\x14\x86\x3f"
+$ECHO -ne "\x7d\xc1\x9d\xb5\xbb\x68\x58\x4c\x14\x03\xf1\x36\xd4\xf2\xce\xb6"
+$ECHO -ne "\xbc\x9d\xb3\x31\x55\x68\x0c\x5f\xd0\x30\xe2\x05\xae\x68\x4e\x11"
+$ECHO -ne "\x5a\x28\x15\x68\x8f\xac\xa3\x1b\x89\xad\x48\x89\x08\x08\xa3\x3b"
+$ECHO -ne "\x26\x5e\x32\x1d\x6d\x73\x2b\x20\x90\x08\x4a\x12\xb0\x1f\xf1\xa3"
+$ECHO -ne "\x37\xf8\xec\x30\x21\x06\xf3\xbb\x3b\x46\xf9\xaa\xeb\x1a\x31\x33"
+$ECHO -ne "\x2b\xa5\x87\x4d\x4d\xbc\x13\xda\xa9\xa0\x4c\xca\x0b\x46\xa6\x09"
+$ECHO -ne "\x41\x73\xbb\x3f\x71\x9f\x6a\x88\x41\x02\x20\x3f\x54\x6d\x42\xc8"
+$ECHO -ne "\x70\x6e\x64\xd0\x50\x88\x83\xc4\x33\x78\x6d\x04\xb6\x43\xed\x5e"
+$ECHO -ne "\x72\x71\x6b\xd5\x65\x80\xcf\x66\x46\x29\x10\xdb\x79\xd1\xa9\x95"
+$ECHO -ne "\xb7\x9d\xf4\xa9\x93\xb4\x5a\x00\xc6\xb2\xad\xbf\x7e\xaa\xe6\x8d"
+$ECHO -ne "\x8c\xc0\x6b\xf5\xc2\xad\x00\xfb\x08\x63\xe2\xb5\xb1\xe0\x76\xac"
+$ECHO -ne "\x0a\xe4\x72\xb5\x72\xbc\x24\x6b\xef\x62\x0f\xaa\xb9\x28\xd2\x3c"
+$ECHO -ne "\xf6\x3e\x26\x20\x8b\xcc\xd2\x61\xcd\xd4\xc7\xed\x39\xa8\x39\x4e"
+$ECHO -ne "\x7e\x05\x97\xeb\x29\x24\x3a\xb2\xc9\xbd\xad\xcf\xf0\x22\xbc\xdd"
+$ECHO -ne "\xb8\x8c\x2e\x6a\xbf\x4f\x67\x2f\xfc\x07\xd0\x53\x0c\x54\x30\x35"
+$ECHO -ne "\xf8\xf1\x76\x45\x26\x5a\x86\x11\x1e\xeb\x58\xc0\x2d\x6c\x3d\x87"
+$ECHO -ne "\xa6\xca\x8e\x89\x4f\x75\x88\xf9\xb9\x9a\x99\x8b\x68\x22\xe2\x52"
+$ECHO -ne "\x4d\x46\x8d\x44\x31\x2b\xc1\xb0\x19\xa7\x90\xfb\x95\xda\x19\x2f"
+$ECHO -ne "\x6e\x0d\xe2\xc1\x85\xd0\x1f\x9b\xd3\xae\x33\xe3\x55\xa4\x77\xf2"
+$ECHO -ne "\xf1\xd7\xa8\xf0\x57\x30\xc4\x3b\xe6\x55\x97\xf9\xe3\x89\x82\xda"
+$ECHO -ne "\xae\x02\x45\xb1\x86\x8c\x84\x4c\xb2\xcf\x82\xdb\x4e\x04\x45\xcc"
+$ECHO -ne "\x19\x53\x9e\x2f\x95\xa9\xc7\xa8\x08\x17\x61\xc1\x8c\x26\x7f\x9b"
+$ECHO -ne "\x07\x8c\xe7\x77\x2d\x12\xd2\xcd\xc6\x97\xcf\x29\x3a\x1e\xac\x2b"
+$ECHO -ne "\x69\xb9\xb4\x61\xee\xeb\xb3\xae\x60\x18\xa0\x3a\xe5\xc0\xb9\x58"
+$ECHO -ne "\x38\x4d\x32\x57\x81\x89\x99\x29\x73\xdd\x47\x43\x2f\x1e\x39\xc6"
+$ECHO -ne "\x06\xbf\x7f\x64\x9e\x91\xc3\x9f\x18\x1b\xba\xf8\xb5\x29\x5d\xe3"
+$ECHO -ne "\x46\x7e\xb5\x1a\xfd\x9b\xb0\x1b\x85\x06\xc3\xc5\x09\xdb\x82\xd0"
+$ECHO -ne "\xd1\xff\xe1\x0f\xeb\x37\x1d\xce\x65\x6d\x26\x55\xe0\x20\x00\xc4"
+$ECHO -ne "\x36\x2f\xba\x86\x26\xc6\x7b\xa4\xe9\xb1\x41\x20\x04\x11\xeb\x24"
+$ECHO -ne "\x3c\x72\xbf\xd3\xc5\xb3\xbd\xce\x14\x45\x2d\x50\x01\x00\x26\x39"
+$ECHO -ne "\x3c\x85\x17\x0e\x42\x66\x8a\x1c\x94\xa8\x90\x02\xc4\x42\xd8\xd1"
+$ECHO -ne "\xcc\x94\x7a\x25\xad\xfd\x8d\xa4\x0e\xe0\xcb\x92\x5e\x6f\x14\x2b"
+$ECHO -ne "\x29\xbd\xc0\x81\x20\x3f\x0b\x2c\x7a\x2c\xe7\xba\x6d\x99\x14\xbe"
+$ECHO -ne "\xd5\x39\xc8\x6f\x2e\xbd\x79\x59\x19\x75\xb6\xf5\x4f\x12\xf6\x8e"
+$ECHO -ne "\x40\xa0\x00\x8b\x12\xe8\xfb\xb7\x27\xaa\xd3\x36\x0c\xfc\xe1\x40"
+$ECHO -ne "\x01\xff\x8b\xb9\x22\x9c\x28\x48\x5f\xa5\xca\xf3\x80"
+}
+
+prep() {
+    rm -f t*
+    hello_$ext >t1.$ext
+    hello_$ext >t2.$ext
+}
+
+check() {
+    eval $2 >t_actual 2>&1
+    if $ECHO -ne "$expected" | cmp - t_actual; then
+       echo "$1: PASS"
+    else
+       echo "$1: FAIL"
+    fi
+}
+
+mkdir testdir 2>/dev/null
+(
+cd testdir || { echo "cannot cd testdir!"; exit 1; }
+
+expected="$unpack: z: No such file or directory
+1
+HELLO
+"
+prep; check "$unpack: doesnt exist" "${bb}$unpack z t1.$ext; echo \$?; cat t1"
+
+
+expected="$unpack: t.zz: unknown suffix - ignored
+1
+HELLO
+"
+prep; >t.zz; check "$unpack: unknown suffix" "${bb}$unpack t.zz t1.$ext; echo \$?; cat t1"
+
+
+# In this case file "t1" exists, and we skip t1.gz and unpack t2.gz
+expected="$unpack: can't open 't1': File exists
+1
+HELLO
+"
+prep; >t1; check "$unpack: already exists" "${bb}$unpack t1.$ext t2.$ext; echo \$?; cat t1 t2"
+
+
+# From old testsuite
+expected="HELLO\n0\n"
+prep; check "$unpack: stream unpack" "cat t1.$ext | ${bb}$unpack; echo $?"
+
+expected="ok\n"
+prep; check "$unpack: delete src" "${bb}$unpack t2.$ext; test ! -f t2.$ext && echo ok"
+
+)
+rm -rf testdir
+
+# This test is only for bunzip2
+if test "${0##*/}" = "bunzip2.tests"; then
+    if test1_bz2 | ${bb}bunzip2 >/dev/null \
+       && test "`test1_bz2 | ${bb}bunzip2 | md5sum`" = "61bbeee4be9c6f110a71447f584fda7b  -"
+    then
+       echo "$unpack: test bz2 file: PASS"
+    else
+       echo "$unpack: test bz2 file: FAIL"
+    fi
+fi
diff --git a/testsuite/bunzip2/bunzip2-reads-from-standard-input b/testsuite/bunzip2/bunzip2-reads-from-standard-input
new file mode 100644 (file)
index 0000000..e212a12
--- /dev/null
@@ -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 (file)
index 0000000..f1d1550
--- /dev/null
@@ -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 (file)
index 0000000..7d4016e
--- /dev/null
@@ -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 (executable)
index 0000000..26536c6
--- /dev/null
@@ -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/bzcat.tests b/testsuite/bzcat.tests
new file mode 100755 (executable)
index 0000000..0bc7442
--- /dev/null
@@ -0,0 +1,49 @@
+#!/bin/sh
+
+ext=bz2
+
+bb="busybox "
+
+unset LC_ALL
+unset LC_MESSAGES
+unset LANG
+unset LANGUAGE
+
+hello_gz() {
+    # Gzipped "HELLO\n"
+    #_________________________ vvv vvv vvv vvv - mtime
+    $ECHO -ne "\x1f\x8b\x08\x00\x85\x1d\xef\x45\x02\x03\xf3\x70\xf5\xf1\xf1\xe7"
+    $ECHO -ne "\x02\x00\x6e\xd7\xac\xfd\x06\x00\x00\x00"
+}
+
+hello_bz2() {
+    # Bzipped "HELLO\n"
+    $ECHO -ne "\x42\x5a\x68\x39\x31\x41\x59\x26\x53\x59\x5b\xb8\xe8\xa3\x00\x00"
+    $ECHO -ne "\x01\x44\x00\x00\x10\x02\x44\xa0\x00\x30\xcd\x00\xc3\x46\x29\x97"
+    $ECHO -ne "\x17\x72\x45\x38\x50\x90\x5b\xb8\xe8\xa3"
+}
+
+prep() {
+    rm -f t*
+    hello_$ext >t1.$ext
+    hello_$ext >t2.$ext
+}
+
+check() {
+    eval $2 >t_actual 2>&1
+    if $ECHO -ne "$expected" | cmp - t_actual; then
+       echo "$1: PASS"
+    else
+       echo "$1: FAIL"
+    fi
+}
+
+mkdir testdir 2>/dev/null
+(
+cd testdir || { echo "cannot cd testdir!"; exit 1; }
+
+expected="HELLO\nok\n"
+prep; check "bzcat: dont delete src" "${bb}bzcat t2.bz2; test -f t2.bz2 && echo ok"
+
+)
+rm -rf testdir
diff --git a/testsuite/cat/cat-prints-a-file b/testsuite/cat/cat-prints-a-file
new file mode 100644 (file)
index 0000000..e3f35a8
--- /dev/null
@@ -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 (file)
index 0000000..bc92318
--- /dev/null
@@ -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 (file)
index 0000000..b9bb628
--- /dev/null
@@ -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/comm.tests b/testsuite/comm.tests
new file mode 100755 (executable)
index 0000000..44169f9
--- /dev/null
@@ -0,0 +1,19 @@
+#!/bin/sh
+
+# Copyright 2008 by Denys Vlasenko <vda.linux@googlemail.com>
+# Licensed under GPL v2, see file LICENSE for details.
+
+. testing.sh
+
+# testing "description" "command" "result" "infile" "stdin"
+
+testing "comm test 1" "comm input -"              "\t123\n""456\n""abc\n""\tdef\n" "456\nabc\n" "123\ndef\n"
+testing "comm test 2" "comm - input"              "123\n""\t456\n""\tabc\n""def\n" "456\nabc\n" "123\ndef\n"
+testing "comm test 3" "comm input -"              "abc\n""\tdef\n""xyz\n"          "abc\nxyz\n" "def\n"
+testing "comm test 4" "comm - input"              "\tabc\n""def\n""\txyz\n"        "abc\nxyz\n" "def\n"
+testing "comm test 5" "comm input -"              "123\n""abc\n""\tdef\n"          "123\nabc\n" "def\n"
+testing "comm test 6" "comm - input"              "\t123\n""\tabc\n""def\n"        "123\nabc\n" "def\n"
+testing "comm unterminated line 1" "comm input -" "abc\n""\tdef\n"                 "abc"        "def"
+testing "comm unterminated line 2" "comm - input" "\tabc\n""def\n"                 "abc"        "def"
+
+exit $FAILCOUNT
diff --git a/testsuite/cp/cp-RHL-does_not_preserve-links b/testsuite/cp/cp-RHL-does_not_preserve-links
new file mode 100644 (file)
index 0000000..eed6c3e
--- /dev/null
@@ -0,0 +1,6 @@
+mkdir a
+>a/file
+ln -s file a/link
+busybox cp -RHL a b
+test ! -L b/link
+#sh </dev/tty >/dev/tty 2>&1
diff --git a/testsuite/cp/cp-a-files-to-dir b/testsuite/cp/cp-a-files-to-dir
new file mode 100644 (file)
index 0000000..abdbdf7
--- /dev/null
@@ -0,0 +1,15 @@
+echo file number one > file1
+echo file number two > file2
+ln -s file2 link1
+mkdir dir1
+# why???
+#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 (file)
index 0000000..0c0cd96
--- /dev/null
@@ -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 (file)
index 0000000..ad25aa1
--- /dev/null
@@ -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 (file)
index 0000000..c2225c6
--- /dev/null
@@ -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 (file)
index 0000000..d52a887
--- /dev/null
@@ -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 (file)
index 0000000..9571a56
--- /dev/null
@@ -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-dev-file b/testsuite/cp/cp-dev-file
new file mode 100644 (file)
index 0000000..055f0d9
--- /dev/null
@@ -0,0 +1,2 @@
+busybox cp /dev/null foo
+test -f foo
diff --git a/testsuite/cp/cp-dir-create-dir b/testsuite/cp/cp-dir-create-dir
new file mode 100644 (file)
index 0000000..a8d7b50
--- /dev/null
@@ -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 (file)
index 0000000..4c788ba
--- /dev/null
@@ -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 (file)
index 0000000..e17e8e6
--- /dev/null
@@ -0,0 +1,11 @@
+touch foo
+chmod a-r foo
+set +e
+if test `id -u` = 0; then
+    # run as user with nonzero uid
+    setuidgid 1 busybox cp foo bar
+else
+    busybox cp foo bar
+fi
+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 (file)
index 0000000..fdb8191
--- /dev/null
@@ -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 (file)
index 0000000..2d9f05e
--- /dev/null
@@ -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 (file)
index 0000000..4de7b85
--- /dev/null
@@ -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 (file)
index 0000000..301dc5f
--- /dev/null
@@ -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 (file)
index 0000000..f0f5065
--- /dev/null
@@ -0,0 +1,3 @@
+touch foo
+busybox cp foo bar
+test -f foo
diff --git a/testsuite/cpio.tests b/testsuite/cpio.tests
new file mode 100755 (executable)
index 0000000..eb3e576
--- /dev/null
@@ -0,0 +1,83 @@
+#!/bin/sh
+# Copyright 2008 by Denys Vlasenko
+# Licensed under GPL v2, see file LICENSE for details.
+
+. testing.sh
+
+# ls -ln shows date. Need to remove that, it's variable.
+# sed: coalesce spaces
+# cut: remove date
+FILTER_LS="sed 's/  */ /g' | cut -d' ' -f 1-5,9-"
+
+
+# newc cpio archive of directory cpio.testdir with empty x and y hardlinks
+hexdump="\
+\x42\x5a\x68\x39\x31\x41\x59\x26\x53\x59\x64\x1e\x91\x8c\x00\x00\
+\x48\x7f\x80\x4c\x48\x08\x00\x28\x01\xff\xe0\x3f\x24\x14\x00\x0e\
+\x20\xdc\x60\x20\x00\x92\x11\xea\xa0\x1a\x00\x00\x00\x03\x20\x8a\
+\x93\xd4\x9a\x68\x1a\x0d\x1e\x91\xa1\xa0\x06\x98\xe3\x5c\x2f\xd9\
+\x26\xa1\x25\x24\x20\xed\x47\xc7\x21\x40\x2b\x6e\xf2\xe6\xfe\x98\
+\x13\x68\xa8\xbd\x82\xb2\x4f\x26\x02\x24\x16\x5b\x22\x16\x72\x74\
+\x15\xcd\xc1\xa6\x9e\xa6\x5e\x6c\x16\x37\x35\x01\x99\xc4\x81\x21\
+\x29\x28\x4b\x69\x51\xa9\x3c\x1a\x9b\x0a\xe1\xe4\xb4\xaf\x85\x73\
+\xba\x23\x10\x59\xe8\xb3\xe1\xa1\x63\x05\x8c\x4f\xc5\xdc\x91\x4e\
+\x14\x24\x19\x07\xa4\x63\x00"
+
+user=$(id -u)
+group=$(id -g)
+
+rm -rf cpio.testdir cpio.testdir2 2>/dev/null
+
+# testing "test name" "command" "expected result" "file input" "stdin"
+
+testing "cpio extracts zero-sized hardlinks" \
+"$ECHO -ne '$hexdump' | bzcat | cpio -i; echo \$?;
+ls -ln cpio.testdir | $FILTER_LS" \
+"\
+1 blocks
+0
+-rw-r--r-- 2 $user $group 0 x
+-rw-r--r-- 2 $user $group 0 y
+" \
+       "" ""
+
+# Currently fails. Numerous: "1 blocks" versus "1 block",
+# "1 block" must go to stderr, does not list cpio.testdir/x and cpio.testdir/y
+testing "cpio lists hardlinks" \
+"$ECHO -ne '$hexdump' | bzcat | cpio -t 2>&1; echo \$?" \
+"\
+1 block
+cpio.testdir
+cpio.testdir/x
+cpio.testdir/y
+0
+" \
+       "" ""
+
+# More complex case
+rm -rf cpio.testdir cpio.testdir2 2>/dev/null
+mkdir cpio.testdir
+touch cpio.testdir/solo
+touch cpio.testdir/empty
+echo x >cpio.testdir/nonempty
+ln cpio.testdir/empty cpio.testdir/empty1
+ln cpio.testdir/nonempty cpio.testdir/nonempty1
+mkdir cpio.testdir2
+
+testing "cpio extracts zero-sized hardlinks 2" \
+"find cpio.testdir | cpio -H newc --create | (cd cpio.testdir2 && cpio -i); echo \$?;
+ls -ln cpio.testdir2/cpio.testdir | $FILTER_LS" \
+"\
+0
+-rw-r--r-- 2 $user $group 0 empty
+-rw-r--r-- 2 $user $group 0 empty1
+-rw-r--r-- 2 $user $group 2 nonempty
+-rw-r--r-- 2 $user $group 2 nonempty1
+-rw-r--r-- 1 $user $group 0 solo
+" \
+       "" ""
+
+# Clean up
+rm -rf cpio.testdir cpio.testdir2 2>/dev/null
+
+exit $FAILCOUNT
diff --git a/testsuite/cut.tests b/testsuite/cut.tests
new file mode 100755 (executable)
index 0000000..2788d1f
--- /dev/null
@@ -0,0 +1,18 @@
+#!/bin/sh
+
+# Copyright 2007 by Denys Vlasenko <vda.linux@googlemail.com>
+# 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
+
+testing "cut '-' (stdin) and multi file handling" \
+       "cut -d' ' -f2 - input" \
+       "over\n""quick\n" \
+       "the quick brown fox\n" \
+       "jumps over the lazy dog\n" \
+
+exit $FAILCOUNT
diff --git a/testsuite/cut/cut-cuts-a-character b/testsuite/cut/cut-cuts-a-character
new file mode 100644 (file)
index 0000000..d6c5efa
--- /dev/null
@@ -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 (file)
index 0000000..9680b76
--- /dev/null
@@ -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 (file)
index 0000000..4c7f440
--- /dev/null
@@ -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 (file)
index 0000000..1fbf277
--- /dev/null
@@ -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 (file)
index 0000000..a2b0cdb
--- /dev/null
@@ -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 (file)
index 0000000..34cd735
--- /dev/null
@@ -0,0 +1 @@
+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 (file)
index 0000000..f2a2091
--- /dev/null
@@ -0,0 +1,4 @@
+# TODO: gnu date doesn't accept '2000.11.22-11:22:33' format,
+# but accepts '2000-11-22 11:22:33'. We must follow.
+test x"01/01/99" = x"`busybox date -d 1999.01.01-11:22:33 '+%d/%m/%y'`"
+test x"22/11/00" = x"`busybox date -d 2000.11.22-11:22:33 '+%d/%m/%y'`"
diff --git a/testsuite/date/date-u-works b/testsuite/date/date-u-works
new file mode 100644 (file)
index 0000000..eea6e5a
--- /dev/null
@@ -0,0 +1 @@
+test x"Sat Jan  1 11:22:33 UTC 2000" = x"`TZ=CET-1CEST-2 busybox date -u -d 2000.01.01-11:22:33`"
diff --git a/testsuite/date/date-works b/testsuite/date/date-works
new file mode 100644 (file)
index 0000000..0802e88
--- /dev/null
@@ -0,0 +1,44 @@
+dt=`busybox date`
+# Expected format: Fri Apr 25 03:47:55 CEST 2008
+dt=`echo "$dt" | sed 's/^[^ ][^ ][^ ] [^ ][^ ][^ ] [ 0123][0-9] [012][0-9]:[0-5][0-9]:[0-6][0-9] [A-Z][A-Z]* [012][0-9][0-9][0-9]$/OK/'`
+test x"$dt" = x"OK"
+
+dt=`busybox date -d 1:2`
+dt=`echo "$dt" | cut -b12-19`
+test x"$dt" = x"01:02:00"
+
+dt=`busybox date -d 1:2:3`
+dt=`echo "$dt" | cut -b12-19`
+test x"$dt" = x"01:02:03"
+
+dt=`busybox date -d 1.2-3:4`
+dt=`echo "$dt" | cut -b5-19`
+test x"$dt" = x"Jan  2 03:04:00"
+
+dt=`busybox date -d 1.2-3:4:5`
+dt=`echo "$dt" | cut -b5-19`
+test x"$dt" = x"Jan  2 03:04:05"
+
+dt=`busybox date -d 1999.1.2-3:4`
+dt=`echo "$dt" | cut -b1-19`
+test x"$dt" = x"Sat Jan  2 03:04:00"
+
+dt=`busybox date -d 1999.1.2-3:4:5`
+dt=`echo "$dt" | cut -b1-19`
+test x"$dt" = x"Sat Jan  2 03:04:05"
+
+dt=`busybox date -d '1999-1-2 3:4:5'`
+dt=`echo "$dt" | cut -b1-19`
+test x"$dt" = x"Sat Jan  2 03:04:05"
+
+dt=`busybox date -d 01231133`
+dt=`echo "$dt" | cut -b5-19`
+test x"$dt" = x"Jan 23 11:33:00"
+
+dt=`busybox date -d 012311332000`
+dt=`echo "$dt" | cut -b1-19`
+test x"$dt" = x"Sun Jan 23 11:33:00"
+
+dt=`busybox date -d 012311332000.30`
+dt=`echo "$dt" | cut -b1-19`
+test x"$dt" = x"Sun Jan 23 11:33:30"
diff --git a/testsuite/date/date-works-1 b/testsuite/date/date-works-1
new file mode 100644 (file)
index 0000000..e318944
--- /dev/null
@@ -0,0 +1,129 @@
+dt=`busybox date -d 1:2 +%T`
+test x"$dt" = x"01:02:00"
+
+dt=`busybox date -d 1:2:3 +%T`
+test x"$dt" = x"01:02:03"
+
+host_date=/bin/date
+
+# date (GNU coreutils) 6.10 reports:
+#      date: invalid date '1.2-3:4'
+# busybox 1.11.0.svn date reports:
+#      date: invalid date '1/2 3:4'
+
+# TODO: (1) compare with strings, not "host date"
+# (2) implement d/m[/y] hh:mm[:ss] fmt in date applet
+#hdt=`$host_date -d '1/2 3:4'`
+#dt=`busybox date -d 1.2-3:4`
+#test x"$hdt" = x"$dt"
+
+#hdt=`$host_date -d '1/2 3:4:5'`
+#dt=`busybox date -d 1.2-3:4:5`
+#test x"$hdt" = x"$dt"
+
+#hdt=`$host_date -d '1/2/1999 3:4'`
+#dt=`busybox date -d 1999.1.2-3:4`
+#test x"$hdt" = x"$dt"
+
+#hdt=`$host_date -d '1/2/1999 3:4:5'`
+#dt=`busybox date -d 1999.1.2-3:4:5`
+#test x"$hdt" = x"$dt"
+
+hdt=`$host_date -d '1999-1-2 3:4:5'`
+dt=`busybox date -d '1999-1-2 3:4:5'`
+test x"$hdt" = x"$dt"
+
+# Avoiding using week day in this evaluation, as it's mostly different every year
+# date (GNU coreutils) 6.10 reports:
+#      date: invalid date '01231133'
+dt=`busybox date -d 01231133 +%c`
+dt=`echo "$dt" | cut -b5-19`
+test x"$dt" = x"Jan 23 11:33:00"
+
+# date (GNU coreutils) 6.10 reports:
+#      date: invalid date '012311332000'
+dt=`busybox date -d 012311332000 +%c`
+test x"$dt" = x"Sun Jan 23 11:33:00 2000"
+
+# date (GNU coreutils) 6.10 reports:
+#      date: invalid date '012311332000'
+dt=`busybox date -d 012311332000.30 +%c`
+test x"$dt" = x"Sun Jan 23 11:33:30 2000"
+
+lcbbd="LC_ALL=C busybox date"
+wd=$(eval $lcbbd +%a)          # weekday name
+mn=$(eval $lcbbd +%b)          # month name
+dm=$(eval $lcbbd +%e)          # day of month, space padded
+h=$(eval $lcbbd +%H)           # hour, zero padded
+m=$(eval $lcbbd +%M)           # minute, zero padded
+s=$(eval $lcbbd +%S)           # second, zero padded
+z=$(eval $lcbbd -u +%Z)                # time zone abbreviation
+y=$(eval $lcbbd +%Y)           # year
+
+res=OK
+case $wd in
+       Sun)
+               ;;
+       Mon)
+               ;;
+       Tue)
+               ;;
+       Wed)
+               ;;
+       Thu)
+               ;;
+       Fri)
+               ;;
+       Sat)
+               ;;
+       *)
+               res=BAD
+               ;;
+esac
+
+case $mn in
+       Jan)
+               ;;
+       Feb)
+               ;;
+       Mar)
+               ;;
+       Apr)
+               ;;
+       May)
+               ;;
+       Jun)
+               ;;
+       Jul)
+               ;;
+       Aug)
+               ;;
+       Sep)
+               ;;
+       Oct)
+               ;;
+       Nov)
+               ;;
+       Dec)
+               ;;
+       *)
+               res=BAD
+               ;;
+esac
+
+dm=${dm# *}
+[ $dm -ge 1 ] && [ $dm -le 31 ] || res=BAD
+h=${h#0}
+[ $h -ge 0 ] && [ $h -le 23 ] || res=BAD
+m=${m#0}
+[ $m -ge 0 ] && [ $m -le 59 ] || res=BAD
+s=${s#0}
+[ $s -ge 0 ] && [ $s -le 59 ] || res=BAD
+[ $z = UTC ] || res=BAD
+[ $y -ge 1970 ] || res=BAD
+
+test x"$res" = xOK
+
+# This should error out (by showing usage text). Testing for that
+dt=`busybox date -d 012311332000.30 %+c 2>&1 | head -n 1`
+test x"${dt#BusyBox * multi-call binary}" = x
diff --git a/testsuite/dd/dd-accepts-if b/testsuite/dd/dd-accepts-if
new file mode 100644 (file)
index 0000000..03d1af8
--- /dev/null
@@ -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 (file)
index 0000000..84405e6
--- /dev/null
@@ -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 (file)
index 0000000..d890eb0
--- /dev/null
@@ -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 (file)
index 0000000..2187dc0
--- /dev/null
@@ -0,0 +1,2 @@
+echo I WANT | busybox dd of=foo >/dev/null 2>bar
+grep -q records bar
diff --git a/testsuite/dd/dd-reports-write-errors b/testsuite/dd/dd-reports-write-errors
new file mode 100644 (file)
index 0000000..4920600
--- /dev/null
@@ -0,0 +1,2 @@
+busybox dd if="$0" of=/dev/full 2>/dev/null || status=$?
+test $status = 1
diff --git a/testsuite/diff.tests b/testsuite/diff.tests
new file mode 100755 (executable)
index 0000000..ac68a08
--- /dev/null
@@ -0,0 +1,124 @@
+#!/bin/sh
+# Copyright 2008 by Denys Vlasenko
+# Licensed under GPL v2, see file LICENSE for details.
+
+. testing.sh
+
+# testing "test name" "options" "expected result" "file input" "stdin"
+
+# diff outputs date/time in the header, which should not be analysed
+# NB: sed has tab character in s command!
+TRIM_TAB="sed 's/      .*//'"
+
+testing "diff of stdin" \
+       "diff -u - input | $TRIM_TAB" \
+"\
+--- -
++++ input
+@@ -1 +1,3 @@
++qwe
+ asd
++zxc
+" \
+       "qwe\nasd\nzxc\n" \
+       "asd\n"
+
+testing "diff of stdin, no newline in the file" \
+       "diff -u - input | $TRIM_TAB" \
+"\
+--- -
++++ input
+@@ -1 +1,3 @@
++qwe
+ asd
++zxc
+\\ No newline at end of file
+" \
+       "qwe\nasd\nzxc" \
+       "asd\n"
+
+# we also test that stdin is in fact NOT read
+testing "diff of stdin, twice" \
+       "diff - -; echo $?; wc -c" \
+       "0\n5\n" \
+       "" \
+       "stdin"
+
+# testing "test name" "options" "expected result" "file input" "stdin"
+
+rm -rf diff1 diff2
+mkdir diff1 diff2 diff2/subdir
+echo qwe >diff1/-
+echo asd >diff2/subdir/-
+testing "diff diff1 diff2/subdir" \
+       "diff -ur diff1 diff2/subdir | $TRIM_TAB" \
+"\
+--- diff1/-
++++ diff2/subdir/-
+@@ -1 +1 @@
+-qwe
++asd
+" \
+       "" ""
+
+# using directory structure from prev test...
+testing "diff dir dir2/file/-" \
+       "diff -ur diff1 diff2/subdir/- | $TRIM_TAB" \
+"\
+--- diff1/-
++++ diff2/subdir/-
+@@ -1 +1 @@
+-qwe
++asd
+" \
+       "" ""
+
+# using directory structure from prev test...
+mkdir diff1/test
+mkfifo diff2/subdir/test
+testing "diff of dir and fifo" \
+       "diff -ur diff1 diff2/subdir | $TRIM_TAB" \
+"\
+--- diff1/-
++++ diff2/subdir/-
+@@ -1 +1 @@
+-qwe
++asd
+Only in diff2/subdir: test
+" \
+       "" ""
+
+# using directory structure from prev test...
+rmdir diff1/test
+echo >diff1/test
+testing "diff of file and fifo" \
+       "diff -ur diff1 diff2/subdir | $TRIM_TAB" \
+"\
+--- diff1/-
++++ diff2/subdir/-
+@@ -1 +1 @@
+-qwe
++asd
+File diff2/subdir/test is not a regular file or directory and was skipped
+" \
+       "" ""
+
+# using directory structure from prev test...
+mkfifo diff1/test2
+testing "diff -rN does not read non-regular files" \
+       "diff -urN diff1 diff2/subdir | $TRIM_TAB" \
+"\
+--- diff1/-
++++ diff2/subdir/-
+@@ -1 +1 @@
+-qwe
++asd
+File diff2/subdir/test is not a regular file or directory and was skipped
+File diff1/test2 is not a regular file or directory and was skipped
+" \
+       "" ""
+
+# clean up
+rm -rf diff1 diff2
+
+exit $FAILCOUNT
diff --git a/testsuite/dirname/dirname-handles-absolute-path b/testsuite/dirname/dirname-handles-absolute-path
new file mode 100644 (file)
index 0000000..ca1a51b
--- /dev/null
@@ -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 (file)
index 0000000..04134a5
--- /dev/null
@@ -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 (file)
index 0000000..286f253
--- /dev/null
@@ -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 (file)
index 0000000..ffe4ab4
--- /dev/null
@@ -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 (file)
index 0000000..6bd62b8
--- /dev/null
@@ -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 (file)
index 0000000..24f9ae1
--- /dev/null
@@ -0,0 +1 @@
+test $(busybox dirname foo) = .
diff --git a/testsuite/dirname/dirname-works b/testsuite/dirname/dirname-works
new file mode 100644 (file)
index 0000000..f339c8f
--- /dev/null
@@ -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 (file)
index 0000000..3f8ff3d
--- /dev/null
@@ -0,0 +1,4 @@
+d=/bin
+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 (file)
index 0000000..6c2c5d0
--- /dev/null
@@ -0,0 +1,4 @@
+d=/bin
+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 (file)
index 0000000..c3d2ec0
--- /dev/null
@@ -0,0 +1,4 @@
+d=/bin
+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 (file)
index 0000000..bf0a90e
--- /dev/null
@@ -0,0 +1,4 @@
+d=/bin
+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 (file)
index 0000000..ae97077
--- /dev/null
@@ -0,0 +1,4 @@
+d=/bin
+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 (file)
index 0000000..46a336d
--- /dev/null
@@ -0,0 +1,4 @@
+d=/bin
+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 (file)
index 0000000..2ed03ca
--- /dev/null
@@ -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 (file)
index 0000000..479dac8
--- /dev/null
@@ -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 (file)
index 0000000..4e4e3b4
--- /dev/null
@@ -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 (file)
index 0000000..838671e
--- /dev/null
@@ -0,0 +1 @@
+test `busybox echo word | wc -c` -eq 5
diff --git a/testsuite/echo/echo-prints-slash-zero b/testsuite/echo/echo-prints-slash-zero
new file mode 100644 (file)
index 0000000..d246632
--- /dev/null
@@ -0,0 +1 @@
+test "`busybox echo -e -n 'msg\n\0' | od -t x1 | head -n 1`" = "0000000 6d 73 67 0a 00"
diff --git a/testsuite/expand.tests b/testsuite/expand.tests
new file mode 100755 (executable)
index 0000000..3f4cda3
--- /dev/null
@@ -0,0 +1,15 @@
+#!/bin/sh
+# Copyright 2008 by Denys Vlasenko
+# Licensed under GPL v2, see file LICENSE for details.
+
+. testing.sh
+
+# testing "test name" "options" "expected result" "file input" "stdin"
+
+testing "expand" \
+       "expand" \
+       "        12345678        12345678\n" \
+       "" \
+       "\t12345678\t12345678\n" \
+
+exit $FAILCOUNT
diff --git a/testsuite/expand/expand-works-like-GNU b/testsuite/expand/expand-works-like-GNU
new file mode 100644 (file)
index 0000000..ee8c793
--- /dev/null
@@ -0,0 +1,18 @@
+rm -f foo bar
+echo -e "\ty" | expand -t 3 ../../busybox > foo
+echo -e "\ty" | busybox unexpand -t 3 ../../busybox > bar
+set +e
+test ! -f foo -a -f bar
+if [ $? = 0 ] ; then
+       set -e
+       diff -q foo bar
+fi
+rm -f foo bar
+echo -e "\ty\tx" | expand -it 3 ../../busybox > foo
+echo -e "\ty\tx" | busybox unexpand -it 3 ../../busybox > bar
+set +e
+test ! -f foo -a -f bar
+if [ $? = 0 ] ; then
+       set -e
+       diff -q foo bar
+fi
diff --git a/testsuite/expr/expr-big b/testsuite/expr/expr-big
new file mode 100644 (file)
index 0000000..23dbbb3
--- /dev/null
@@ -0,0 +1,16 @@
+# busybox expr
+
+# 3*1000*1000*1000 overflows 32-bit signed int
+res=`busybox expr 0 '<' 3000000000`
+[ x"$res" = x1 ] || exit 1
+
+# 9223372036854775807 = 2^31-1
+res=`busybox expr 0 '<' 9223372036854775807`
+[ x"$res" = x1 ] || exit 1
+# coreutils fails this one!
+res=`busybox expr -9223372036854775800 '<' 9223372036854775807`
+[ x"$res" = x1 ] || exit 1
+
+# This one works only by chance
+# res=`busybox expr 0 '<' 9223372036854775808`
+# [ x"$res" = x1 ] || exit 1
diff --git a/testsuite/expr/expr-works b/testsuite/expr/expr-works
new file mode 100644 (file)
index 0000000..af49ac4
--- /dev/null
@@ -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 (file)
index 0000000..8a9aa0c
--- /dev/null
@@ -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 (file)
index 0000000..1a061f2
--- /dev/null
@@ -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 (file)
index 0000000..4c559a1
--- /dev/null
@@ -0,0 +1 @@
+busybox find . -xdev >/dev/null 2>&1
diff --git a/testsuite/grep.tests b/testsuite/grep.tests
new file mode 100755 (executable)
index 0000000..8cee1b9
--- /dev/null
@@ -0,0 +1,88 @@
+#!/bin/sh
+
+# 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" ""
+
+# GNU grep 2.5.3 outputs a new line character after the located string
+# even if there is no new line character in the input
+testing "grep (no newline at EOL)" "grep bug input" "bug\n" "bug" ""
+
+>empty
+testing "grep two files" "grep two input empty 2>/dev/null" \
+       "input:two\n" "one\ntwo\nthree\nthree\nthree\n" ""
+rm empty
+
+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"
+
+testing "grep handles NUL in files" "grep -a foo input" "\0foo\n" "\0foo\n\n" ""
+testing "grep handles NUL on stdin" "grep -a foo" "\0foo\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" ""
+testing "grep -F handles multiple expessions" "grep -F -e one -e two input ; echo \$?" \
+       "one\ntwo\n0\n" "one\ntwo\n" ""
+
+# -f file/-
+testing "grep can read regexps from stdin" "grep -f - input ; echo \$?" \
+       "two\nthree\n0\n" "tw\ntwo\nthree\n" "tw.\nthr\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.tests b/testsuite/gunzip.tests
new file mode 100755 (executable)
index 0000000..d781004
--- /dev/null
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+. bunzip2.tests
diff --git a/testsuite/gunzip/gunzip-reads-from-standard-input b/testsuite/gunzip/gunzip-reads-from-standard-input
new file mode 100644 (file)
index 0000000..7c498c0
--- /dev/null
@@ -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 (file)
index 0000000..8f0d9c8
--- /dev/null
@@ -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 (file)
index 0000000..8b51fdf
--- /dev/null
@@ -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 (file)
index 0000000..b9cb995
--- /dev/null
@@ -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 (file)
index 0000000..db43255
--- /dev/null
@@ -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 (file)
index 0000000..56ad3e3
--- /dev/null
@@ -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 (file)
index 0000000..e85698e
--- /dev/null
@@ -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 (file)
index 0000000..a9aeb92
--- /dev/null
@@ -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 (file)
index 0000000..68a3e67
--- /dev/null
@@ -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 (file)
index 0000000..172b944
--- /dev/null
@@ -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 (file)
index 0000000..f51a406
--- /dev/null
@@ -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 (file)
index 0000000..671fc53
--- /dev/null
@@ -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 (file)
index 0000000..2358cb0
--- /dev/null
@@ -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 (file)
index 0000000..db390e7
--- /dev/null
@@ -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 (file)
index 0000000..6b0fcb0
--- /dev/null
@@ -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 (file)
index 0000000..2f6e23f
--- /dev/null
@@ -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 (file)
index 0000000..e875e4c
--- /dev/null
@@ -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 (file)
index 0000000..c96b7d6
--- /dev/null
@@ -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 (file)
index 0000000..cab8d1d
--- /dev/null
@@ -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 (file)
index 0000000..47fb989
--- /dev/null
@@ -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 (file)
index 0000000..a8123ec
--- /dev/null
@@ -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 (file)
index 0000000..8856949
--- /dev/null
@@ -0,0 +1,4 @@
+[ -n "$d" ] || d=..
+LC_ALL=C ls -1 "$d" > logfile.gnu
+LC_ALL=C busybox ls -1 "$d" > logfile.bb
+diff -ubw logfile.gnu logfile.bb
diff --git a/testsuite/ls/ls-h-works b/testsuite/ls/ls-h-works
new file mode 100644 (file)
index 0000000..0c83f7c
--- /dev/null
@@ -0,0 +1,4 @@
+[ -n "$d" ] || d=..
+LC_ALL=C ls -h "$d" > logfile.gnu
+LC_ALL=C busybox ls -h "$d" > logfile.bb
+diff -ubw logfile.gnu logfile.bb
diff --git a/testsuite/ls/ls-l-works b/testsuite/ls/ls-l-works
new file mode 100644 (file)
index 0000000..1bad34b
--- /dev/null
@@ -0,0 +1,4 @@
+[ -n "$d" ] || d=..
+LC_ALL=C ls -l "$d" > logfile.gnu
+LC_ALL=C busybox ls -l "$d" > logfile.bb
+diff -ubw logfile.gnu logfile.bb
diff --git a/testsuite/ls/ls-s-works b/testsuite/ls/ls-s-works
new file mode 100644 (file)
index 0000000..0a9d752
--- /dev/null
@@ -0,0 +1,4 @@
+[ -n "$d" ] || d=..
+LC_ALL=C ls -1s "$d" > logfile.gnu
+LC_ALL=C busybox ls -1s "$d" > logfile.bb
+diff -ubw logfile.gnu logfile.bb
diff --git a/testsuite/makedevs.device_table.txt b/testsuite/makedevs.device_table.txt
new file mode 100644 (file)
index 0000000..4400083
--- /dev/null
@@ -0,0 +1,172 @@
+# When building a target filesystem, it is desirable to not have to
+# become root and then run 'mknod' a thousand times.  Using a device
+# table you can create device nodes and directories "on the fly".
+#
+# This is a sample device table file for use with genext2fs.  You can
+# do all sorts of interesting things with a device table file.  For
+# example, if you want to adjust the permissions on a particular file
+# you can just add an entry like:
+#   /sbin/foobar        f       2755    0       0       -       -       -       -       -
+# and (assuming the file /sbin/foobar exists) it will be made setuid
+# root (regardless of what its permissions are on the host filesystem.
+# Furthermore, you can use a single table entry to create a many device
+# minors.  For example, if I wanted to create /dev/hda and /dev/hda[0-15]
+# I could just use the following two table entries:
+#   /dev/hda    b       640     0       0       3       0       0       0       -
+#   /dev/hda    b       640     0       0       3       1       1       1       15
+#
+# Device table entries take the form of:
+# <name>    <type>      <mode>  <uid>   <gid>   <major> <minor> <start> <inc>   <count>
+# where name is the file name,  type can be one of:
+#       f       A regular file
+#       d       Directory
+#       c       Character special device file
+#       b       Block special device file
+#       p       Fifo (named pipe)
+# uid is the user id for the target file, gid is the group id for the
+# target file.  The rest of the entries (major, minor, etc) apply only
+# to device special files.
+
+# Have fun
+# -Erik Andersen <andersen@codepoet.org>
+#
+
+#<name>                <type>  <mode>  <uid>   <gid>   <major> <minor> <start> <inc>   <count>
+/dev           d       755     0       0       -       -       -       -       -
+/dev/pts       d       755     0       0       -       -       -       -       -
+/dev/shm       d       755     0       0       -       -       -       -       -
+/tmp           d       1777    0       0       -       -       -       -       -
+/etc           d       755     0       0       -       -       -       -       -
+/home/default  d       2755    1000    1000    -       -       -       -       -
+#<name>                                        <type>  <mode>  <uid>   <gid>   <major> <minor> <start> <inc>   <count>
+###/bin/busybox                                f       4755    0       0       -       -       -       -       -
+###/etc/shadow                         f       600     0       0       -       -       -       -       -
+###/etc/passwd                         f       644     0       0       -       -       -       -       -
+/etc/network/if-up.d                   d       755     0       0       -       -       -       -       -
+/etc/network/if-pre-up.d               d       755     0       0       -       -       -       -       -
+/etc/network/if-down.d                 d       755     0       0       -       -       -       -       -
+/etc/network/if-post-down.d            d       755     0       0       -       -       -       -       -
+###/usr/share/udhcpc/default.script    f       755     0       0       -       -       -       -       -
+# uncomment this to allow starting x as non-root
+#/usr/X11R6/bin/Xfbdev         f       4755    0       0       -       -       -       -       -
+
+# Normal system devices
+# <name>    <type>      <mode>  <uid>   <gid>   <major> <minor> <start> <inc>   <count>
+/dev/mem       c       640     0       0       1       1       0       0       -
+/dev/kmem      c       640     0       0       1       2       0       0       -
+/dev/null      c       666     0       0       1       3       0       0       -
+/dev/zero      c       666     0       0       1       5       0       0       -
+/dev/random    c       666     0       0       1       8       0       0       -
+/dev/urandom   c       666     0       0       1       9       0       0       -
+/dev/ram       b       640     0       0       1       1       0       0       -
+/dev/ram       b       640     0       0       1       0       0       1       4
+/dev/loop      b       640     0       0       7       0       0       1       2
+/dev/rtc       c       640     0       0       10      135     -       -       -
+/dev/console   c       666     0       0       5       1       -       -       -
+/dev/tty       c       666     0       0       5       0       -       -       -
+/dev/tty       c       666     0       0       4       0       0       1       8
+/dev/ttyp      c       666     0       0       3       0       0       1       10
+/dev/ptyp      c       666     0       0       2       0       0       1       10
+/dev/ptmx      c       666     0       0       5       2       -       -       -
+/dev/ttyP      c       666     0       0       57      0       0       1       4
+/dev/ttyS      c       666     0       0       4       64      0       1       4
+/dev/fb                c       640     0       5       29      0       0       32      4
+#/dev/ttySA    c       666     0       0       204     5       0       1       3
+/dev/psaux     c       666     0       0       10      1       0       0       -
+#/dev/ppp      c       666     0       0       108     0       -       -       -
+
+# Input stuff
+/dev/input     d       755     0       0       -       -       -       -       -
+/dev/input/mice        c       640     0       0       13      63      0       0       -
+/dev/input/mouse c     660     0       0       13      32      0       1       4
+/dev/input/event c     660     0       0       13      64      0       1       4
+#/dev/input/js c       660     0       0       13      0       0       1       4
+
+
+# MTD stuff
+/dev/mtd       c       640     0       0       90      0       0       2       4
+/dev/mtdblock  b       640     0       0       31      0       0       1       4
+
+#Tun/tap driver
+/dev/net       d       755     0       0       -       -       -       -       -
+/dev/net/tun   c       660     0       0       10      200     -       -       -
+
+# Audio stuff
+#/dev/audio    c       666     0       29      14      4       -       -       -
+#/dev/audio1   c       666     0       29      14      20      -       -       -
+#/dev/dsp      c       666     0       29      14      3       -       -       -
+#/dev/dsp1     c       666     0       29      14      19      -       -       -
+#/dev/sndstat  c       666     0       29      14      6       -       -       -
+
+# User-mode Linux stuff
+#/dev/ubda     b       640     0       0       98      0       0       0       -
+#/dev/ubda     b       640     0       0       98      1       1       1       15
+
+# IDE Devices
+/dev/hda       b       640     0       0       3       0       0       0       -
+/dev/hda       b       640     0       0       3       1       1       1       15
+/dev/hdb       b       640     0       0       3       64      0       0       -
+/dev/hdb       b       640     0       0       3       65      1       1       15
+#/dev/hdc      b       640     0       0       22      0       0       0       -
+#/dev/hdc      b       640     0       0       22      1       1       1       15
+#/dev/hdd      b       640     0       0       22      64      0       0       -
+#/dev/hdd      b       640     0       0       22      65      1       1       15
+#/dev/hde      b       640     0       0       33      0       0       0       -
+#/dev/hde      b       640     0       0       33      1       1       1       15
+#/dev/hdf      b       640     0       0       33      64      0       0       -
+#/dev/hdf      b       640     0       0       33      65      1       1       15
+#/dev/hdg      b       640     0       0       34      0       0       0       -
+#/dev/hdg      b       640     0       0       34      1       1       1       15
+#/dev/hdh      b       640     0       0       34      64      0       0       -
+#/dev/hdh      b       640     0       0       34      65      1       1       15
+
+# SCSI Devices
+#/dev/sda      b       640     0       0       8       0       0       0       -
+#/dev/sda      b       640     0       0       8       1       1       1       15
+#/dev/sdb      b       640     0       0       8       16      0       0       -
+#/dev/sdb      b       640     0       0       8       17      1       1       15
+#/dev/sdc      b       640     0       0       8       32      0       0       -
+#/dev/sdc      b       640     0       0       8       33      1       1       15
+#/dev/sdd      b       640     0       0       8       48      0       0       -
+#/dev/sdd      b       640     0       0       8       49      1       1       15
+#/dev/sde      b       640     0       0       8       64      0       0       -
+#/dev/sde      b       640     0       0       8       65      1       1       15
+#/dev/sdf      b       640     0       0       8       80      0       0       -
+#/dev/sdf      b       640     0       0       8       81      1       1       15
+#/dev/sdg      b       640     0       0       8       96      0       0       -
+#/dev/sdg      b       640     0       0       8       97      1       1       15
+#/dev/sdh      b       640     0       0       8       112     0       0       -
+#/dev/sdh      b       640     0       0       8       113     1       1       15
+#/dev/sg       c       640     0       0       21      0       0       1       15
+#/dev/scd      b       640     0       0       11      0       0       1       15
+#/dev/st       c       640     0       0       9       0       0       1       8
+#/dev/nst      c       640     0       0       9       128     0       1       8
+#/dev/st       c       640     0       0       9       32      1       1       4
+#/dev/st       c       640     0       0       9       64      1       1       4
+#/dev/st       c       640     0       0       9       96      1       1       4
+
+# Floppy disk devices
+#/dev/fd       b       640     0       0       2       0       0       1       2
+#/dev/fd0d360  b       640     0       0       2       4       0       0       -
+#/dev/fd1d360  b       640     0       0       2       5       0       0       -
+#/dev/fd0h1200 b       640     0       0       2       8       0       0       -
+#/dev/fd1h1200 b       640     0       0       2       9       0       0       -
+#/dev/fd0u1440 b       640     0       0       2       28      0       0       -
+#/dev/fd1u1440 b       640     0       0       2       29      0       0       -
+#/dev/fd0u2880 b       640     0       0       2       32      0       0       -
+#/dev/fd1u2880 b       640     0       0       2       33      0       0       -
+
+# All the proprietary cdrom devices in the world
+#/dev/aztcd    b       640     0       0       29      0       0       0       -
+#/dev/bpcd     b       640     0       0       41      0       0       0       -
+#/dev/capi20   c       640     0       0       68      0       0       1       2
+#/dev/cdu31a   b       640     0       0       15      0       0       0       -
+#/dev/cdu535   b       640     0       0       24      0       0       0       -
+#/dev/cm206cd  b       640     0       0       32      0       0       0       -
+#/dev/sjcd     b       640     0       0       18      0       0       0       -
+#/dev/sonycd   b       640     0       0       15      0       0       0       -
+#/dev/gscd     b       640     0       0       16      0       0       0       -
+#/dev/sbpcd    b       640     0       0       25      0       0       0       -
+#/dev/sbpcd    b       640     0       0       25      0       0       1       4
+#/dev/mcd      b       640     0       0       23      0       0       0       -
+#/dev/optcd    b       640     0       0       17      0       0       0       -
diff --git a/testsuite/makedevs.tests b/testsuite/makedevs.tests
new file mode 100755 (executable)
index 0000000..9e068bd
--- /dev/null
@@ -0,0 +1,139 @@
+#!/bin/sh
+# Copyright 2008 by Denys Vlasenko
+# Licensed under GPL v2, see file LICENSE for details.
+
+. testing.sh
+
+# ls -ln is showing date. Need to remove that, it's variable
+# sed: (1) "maj, min" -> "maj,min" (2) coalesce spaces
+# cut: remove date
+FILTER_LS="sed -e 's/,  */,/g' -e 's/  */ /g' | cut -d' ' -f 1-5,9-"
+# cut: remove size+date
+FILTER_LS2="sed -e 's/,  */,/g' -e 's/  */ /g' | cut -d' ' -f 1-4,9-"
+
+# testing "test name" "options" "expected result" "file input" "stdin"
+
+rm -rf makedevs.testdir
+mkdir makedevs.testdir
+
+testing "makedevs -d ../makedevs.device_table.txt ." \
+       "(cd makedevs.testdir && makedevs -d ../makedevs.device_table.txt . 2>&1);
+       find makedevs.testdir ! -type d | sort | xargs ls -lnR | $FILTER_LS" \
+"\
+rootdir=.
+table='../makedevs.device_table.txt'
+crw-rw-rw- 1 0 0 5,1 makedevs.testdir/dev/console
+crw-r----- 1 0 5 29,0 makedevs.testdir/dev/fb0
+crw-r----- 1 0 5 29,32 makedevs.testdir/dev/fb1
+crw-r----- 1 0 5 29,64 makedevs.testdir/dev/fb2
+crw-r----- 1 0 5 29,96 makedevs.testdir/dev/fb3
+brw-r----- 1 0 0 3,0 makedevs.testdir/dev/hda
+brw-r----- 1 0 0 3,1 makedevs.testdir/dev/hda1
+brw-r----- 1 0 0 3,10 makedevs.testdir/dev/hda10
+brw-r----- 1 0 0 3,11 makedevs.testdir/dev/hda11
+brw-r----- 1 0 0 3,12 makedevs.testdir/dev/hda12
+brw-r----- 1 0 0 3,13 makedevs.testdir/dev/hda13
+brw-r----- 1 0 0 3,14 makedevs.testdir/dev/hda14
+brw-r----- 1 0 0 3,15 makedevs.testdir/dev/hda15
+brw-r----- 1 0 0 3,2 makedevs.testdir/dev/hda2
+brw-r----- 1 0 0 3,3 makedevs.testdir/dev/hda3
+brw-r----- 1 0 0 3,4 makedevs.testdir/dev/hda4
+brw-r----- 1 0 0 3,5 makedevs.testdir/dev/hda5
+brw-r----- 1 0 0 3,6 makedevs.testdir/dev/hda6
+brw-r----- 1 0 0 3,7 makedevs.testdir/dev/hda7
+brw-r----- 1 0 0 3,8 makedevs.testdir/dev/hda8
+brw-r----- 1 0 0 3,9 makedevs.testdir/dev/hda9
+brw-r----- 1 0 0 3,64 makedevs.testdir/dev/hdb
+brw-r----- 1 0 0 3,65 makedevs.testdir/dev/hdb1
+brw-r----- 1 0 0 3,74 makedevs.testdir/dev/hdb10
+brw-r----- 1 0 0 3,75 makedevs.testdir/dev/hdb11
+brw-r----- 1 0 0 3,76 makedevs.testdir/dev/hdb12
+brw-r----- 1 0 0 3,77 makedevs.testdir/dev/hdb13
+brw-r----- 1 0 0 3,78 makedevs.testdir/dev/hdb14
+brw-r----- 1 0 0 3,79 makedevs.testdir/dev/hdb15
+brw-r----- 1 0 0 3,66 makedevs.testdir/dev/hdb2
+brw-r----- 1 0 0 3,67 makedevs.testdir/dev/hdb3
+brw-r----- 1 0 0 3,68 makedevs.testdir/dev/hdb4
+brw-r----- 1 0 0 3,69 makedevs.testdir/dev/hdb5
+brw-r----- 1 0 0 3,70 makedevs.testdir/dev/hdb6
+brw-r----- 1 0 0 3,71 makedevs.testdir/dev/hdb7
+brw-r----- 1 0 0 3,72 makedevs.testdir/dev/hdb8
+brw-r----- 1 0 0 3,73 makedevs.testdir/dev/hdb9
+crw-rw---- 1 0 0 13,64 makedevs.testdir/dev/input/event0
+crw-rw---- 1 0 0 13,65 makedevs.testdir/dev/input/event1
+crw-rw---- 1 0 0 13,66 makedevs.testdir/dev/input/event2
+crw-rw---- 1 0 0 13,67 makedevs.testdir/dev/input/event3
+crw-r----- 1 0 0 13,63 makedevs.testdir/dev/input/mice
+crw-rw---- 1 0 0 13,32 makedevs.testdir/dev/input/mouse0
+crw-rw---- 1 0 0 13,33 makedevs.testdir/dev/input/mouse1
+crw-rw---- 1 0 0 13,34 makedevs.testdir/dev/input/mouse2
+crw-rw---- 1 0 0 13,35 makedevs.testdir/dev/input/mouse3
+crw-r----- 1 0 0 1,2 makedevs.testdir/dev/kmem
+brw-r----- 1 0 0 7,0 makedevs.testdir/dev/loop0
+brw-r----- 1 0 0 7,1 makedevs.testdir/dev/loop1
+crw-r----- 1 0 0 1,1 makedevs.testdir/dev/mem
+crw-r----- 1 0 0 90,0 makedevs.testdir/dev/mtd0
+crw-r----- 1 0 0 90,2 makedevs.testdir/dev/mtd1
+crw-r----- 1 0 0 90,4 makedevs.testdir/dev/mtd2
+crw-r----- 1 0 0 90,6 makedevs.testdir/dev/mtd3
+brw-r----- 1 0 0 31,0 makedevs.testdir/dev/mtdblock0
+brw-r----- 1 0 0 31,1 makedevs.testdir/dev/mtdblock1
+brw-r----- 1 0 0 31,2 makedevs.testdir/dev/mtdblock2
+brw-r----- 1 0 0 31,3 makedevs.testdir/dev/mtdblock3
+crw-rw---- 1 0 0 10,200 makedevs.testdir/dev/net/tun
+crw-rw-rw- 1 0 0 1,3 makedevs.testdir/dev/null
+crw-rw-rw- 1 0 0 10,1 makedevs.testdir/dev/psaux
+crw-rw-rw- 1 0 0 5,2 makedevs.testdir/dev/ptmx
+crw-rw-rw- 1 0 0 2,0 makedevs.testdir/dev/ptyp0
+crw-rw-rw- 1 0 0 2,1 makedevs.testdir/dev/ptyp1
+crw-rw-rw- 1 0 0 2,2 makedevs.testdir/dev/ptyp2
+crw-rw-rw- 1 0 0 2,3 makedevs.testdir/dev/ptyp3
+crw-rw-rw- 1 0 0 2,4 makedevs.testdir/dev/ptyp4
+crw-rw-rw- 1 0 0 2,5 makedevs.testdir/dev/ptyp5
+crw-rw-rw- 1 0 0 2,6 makedevs.testdir/dev/ptyp6
+crw-rw-rw- 1 0 0 2,7 makedevs.testdir/dev/ptyp7
+crw-rw-rw- 1 0 0 2,8 makedevs.testdir/dev/ptyp8
+crw-rw-rw- 1 0 0 2,9 makedevs.testdir/dev/ptyp9
+brw-r----- 1 0 0 1,1 makedevs.testdir/dev/ram
+brw-r----- 1 0 0 1,0 makedevs.testdir/dev/ram0
+brw-r----- 1 0 0 1,1 makedevs.testdir/dev/ram1
+brw-r----- 1 0 0 1,2 makedevs.testdir/dev/ram2
+brw-r----- 1 0 0 1,3 makedevs.testdir/dev/ram3
+crw-rw-rw- 1 0 0 1,8 makedevs.testdir/dev/random
+crw-r----- 1 0 0 10,135 makedevs.testdir/dev/rtc
+crw-rw-rw- 1 0 0 5,0 makedevs.testdir/dev/tty
+crw-rw-rw- 1 0 0 4,0 makedevs.testdir/dev/tty0
+crw-rw-rw- 1 0 0 4,1 makedevs.testdir/dev/tty1
+crw-rw-rw- 1 0 0 4,2 makedevs.testdir/dev/tty2
+crw-rw-rw- 1 0 0 4,3 makedevs.testdir/dev/tty3
+crw-rw-rw- 1 0 0 4,4 makedevs.testdir/dev/tty4
+crw-rw-rw- 1 0 0 4,5 makedevs.testdir/dev/tty5
+crw-rw-rw- 1 0 0 4,6 makedevs.testdir/dev/tty6
+crw-rw-rw- 1 0 0 4,7 makedevs.testdir/dev/tty7
+crw-rw-rw- 1 0 0 57,0 makedevs.testdir/dev/ttyP0
+crw-rw-rw- 1 0 0 57,1 makedevs.testdir/dev/ttyP1
+crw-rw-rw- 1 0 0 57,2 makedevs.testdir/dev/ttyP2
+crw-rw-rw- 1 0 0 57,3 makedevs.testdir/dev/ttyP3
+crw-rw-rw- 1 0 0 4,64 makedevs.testdir/dev/ttyS0
+crw-rw-rw- 1 0 0 4,65 makedevs.testdir/dev/ttyS1
+crw-rw-rw- 1 0 0 4,66 makedevs.testdir/dev/ttyS2
+crw-rw-rw- 1 0 0 4,67 makedevs.testdir/dev/ttyS3
+crw-rw-rw- 1 0 0 3,0 makedevs.testdir/dev/ttyp0
+crw-rw-rw- 1 0 0 3,1 makedevs.testdir/dev/ttyp1
+crw-rw-rw- 1 0 0 3,2 makedevs.testdir/dev/ttyp2
+crw-rw-rw- 1 0 0 3,3 makedevs.testdir/dev/ttyp3
+crw-rw-rw- 1 0 0 3,4 makedevs.testdir/dev/ttyp4
+crw-rw-rw- 1 0 0 3,5 makedevs.testdir/dev/ttyp5
+crw-rw-rw- 1 0 0 3,6 makedevs.testdir/dev/ttyp6
+crw-rw-rw- 1 0 0 3,7 makedevs.testdir/dev/ttyp7
+crw-rw-rw- 1 0 0 3,8 makedevs.testdir/dev/ttyp8
+crw-rw-rw- 1 0 0 3,9 makedevs.testdir/dev/ttyp9
+crw-rw-rw- 1 0 0 1,9 makedevs.testdir/dev/urandom
+crw-rw-rw- 1 0 0 1,5 makedevs.testdir/dev/zero
+" \
+       "" ""
+
+# clean up
+rm -rf makedevs.testdir
+
+exit $FAILCOUNT
diff --git a/testsuite/md5sum/md5sum-verifies-non-binary-file b/testsuite/md5sum/md5sum-verifies-non-binary-file
new file mode 100644 (file)
index 0000000..8566a23
--- /dev/null
@@ -0,0 +1,3 @@
+touch foo
+md5sum foo > bar
+busybox md5sum -c bar
diff --git a/testsuite/mdev.tests b/testsuite/mdev.tests
new file mode 100755 (executable)
index 0000000..90379e6
--- /dev/null
@@ -0,0 +1,143 @@
+#!/bin/sh
+# Copyright 2008 by Denys Vlasenko
+# Licensed under GPL v2, see file LICENSE for details.
+
+. testing.sh
+
+# ls -ln is showing date. Need to remove that, it's variable
+# sed: (1) "maj, min" -> "maj,min" (2) coalesce spaces
+# cut: remove date
+FILTER_LS="sed -e 's/,  */,/g' -e 's/  */ /g' | cut -d' ' -f 1-5,9-"
+# cut: remove size+date
+FILTER_LS2="sed -e 's/,  */,/g' -e 's/  */ /g' | cut -d' ' -f 1-4,9-"
+
+# testing "test name" "options" "expected result" "file input" "stdin"
+
+rm -rf mdev.testdir
+mkdir mdev.testdir
+# We need mdev executable to be in chroot jail!
+# (will still fail with dynamically linked one, though...)
+cp ../busybox mdev.testdir/mdev
+mkdir mdev.testdir/bin
+cp ../busybox mdev.testdir/bin/sh 2>/dev/null # for testing cmd feature
+mkdir mdev.testdir/etc
+mkdir mdev.testdir/dev
+mkdir -p mdev.testdir/sys/block/sda
+echo "8:0" >mdev.testdir/sys/block/sda/dev
+
+# env - PATH=$PATH: on some systems chroot binary won't otherwise be found
+
+testing "mdev add /block/sda" \
+       "env - PATH=$PATH ACTION=add DEVPATH=/block/sda chroot mdev.testdir /mdev 2>&1;
+       ls -ln mdev.testdir/dev | $FILTER_LS" \
+"\
+brw-rw---- 1 0 0 8,0 sda
+" \
+       "" ""
+
+# continuing to use directory structure from prev test
+rm -rf mdev.testdir/dev/*
+echo ".* 1:1 666" >mdev.testdir/etc/mdev.conf
+echo "sda 2:2 444" >>mdev.testdir/etc/mdev.conf
+testing "mdev stops on first rule" \
+       "env - PATH=$PATH ACTION=add DEVPATH=/block/sda chroot mdev.testdir /mdev 2>&1;
+       ls -ln mdev.testdir/dev | $FILTER_LS" \
+"\
+brw-rw-rw- 1 1 1 8,0 sda
+" \
+       "" ""
+
+# continuing to use directory structure from prev test
+rm -rf mdev.testdir/dev/*
+echo "sda 0:0 444 >disk/scsiA" >mdev.testdir/etc/mdev.conf
+testing "mdev move/symlink rule '>bar/baz'" \
+       "env - PATH=$PATH ACTION=add DEVPATH=/block/sda chroot mdev.testdir /mdev 2>&1;
+       ls -lnR mdev.testdir/dev | $FILTER_LS2" \
+"\
+mdev.testdir/dev:
+drwxr-xr-x 2 0 0 disk
+lrwxrwxrwx 1 0 0 sda -> disk/scsiA
+
+mdev.testdir/dev/disk:
+br--r--r-- 1 0 0 scsiA
+" \
+       "" ""
+
+# continuing to use directory structure from prev test
+rm -rf mdev.testdir/dev/*
+echo "sda 0:0 444 >disk/" >mdev.testdir/etc/mdev.conf
+testing "mdev move/symlink rule '>bar/'" \
+       "env - PATH=$PATH ACTION=add DEVPATH=/block/sda chroot mdev.testdir /mdev 2>&1;
+       ls -lnR mdev.testdir/dev | $FILTER_LS2" \
+"\
+mdev.testdir/dev:
+drwxr-xr-x 2 0 0 disk
+lrwxrwxrwx 1 0 0 sda -> disk/sda
+
+mdev.testdir/dev/disk:
+br--r--r-- 1 0 0 sda
+" \
+       "" ""
+
+# continuing to use directory structure from prev test
+rm -rf mdev.testdir/dev/*
+# here we complicate things by having non-matching group 1 and using %0
+echo "s([0-9])*d([a-z]+) 0:0 644 >sd/%2_%0" >mdev.testdir/etc/mdev.conf
+testing "mdev regexp substring match + replace" \
+       "env - PATH=$PATH ACTION=add DEVPATH=/block/sda chroot mdev.testdir /mdev 2>&1;
+       ls -lnR mdev.testdir/dev | $FILTER_LS2" \
+"\
+mdev.testdir/dev:
+drwxr-xr-x 2 0 0 sd
+lrwxrwxrwx 1 0 0 sda -> sd/a_sda
+
+mdev.testdir/dev/sd:
+brw-r--r-- 1 0 0 a_sda
+" \
+       "" ""
+
+# continuing to use directory structure from prev test
+rm -rf mdev.testdir/dev/*
+echo "sda 0:0 644 @echo @echo TEST" >mdev.testdir/etc/mdev.conf
+testing "mdev command" \
+       "env - PATH=$PATH ACTION=add DEVPATH=/block/sda chroot mdev.testdir /mdev 2>&1;
+       ls -lnR mdev.testdir/dev | $FILTER_LS" \
+"\
+@echo TEST
+mdev.testdir/dev:
+brw-r--r-- 1 0 0 8,0 sda
+" \
+       "" ""
+
+# continuing to use directory structure from prev test
+rm -rf mdev.testdir/dev/*
+echo "sda 0:0 644 =block/ @echo @echo TEST" >mdev.testdir/etc/mdev.conf
+testing "mdev move and command" \
+       "env - PATH=$PATH ACTION=add DEVPATH=/block/sda chroot mdev.testdir /mdev 2>&1;
+       ls -lnR mdev.testdir/dev | $FILTER_LS2" \
+"\
+@echo TEST
+mdev.testdir/dev:
+drwxr-xr-x 2 0 0 block
+
+mdev.testdir/dev/block:
+brw-r--r-- 1 0 0 sda
+" \
+       "" ""
+
+# continuing to use directory structure from prev test
+rm -rf mdev.testdir/dev/*
+echo "@8,0 :1 644" >mdev.testdir/etc/mdev.conf
+testing "mdev #maj,min and no explicit uid" \
+       "env - PATH=$PATH ACTION=add DEVPATH=/block/sda chroot mdev.testdir /mdev 2>&1;
+       ls -lnR mdev.testdir/dev | $FILTER_LS" \
+"\
+mdev.testdir/dev:
+brw-r--r-- 1 0 1 8,0 sda
+" \
+       "" ""
+
+# clean up
+rm -rf mdev.testdir
+
+exit $FAILCOUNT
diff --git a/testsuite/mkdir/mkdir-makes-a-directory b/testsuite/mkdir/mkdir-makes-a-directory
new file mode 100644 (file)
index 0000000..6ca5c4d
--- /dev/null
@@ -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 (file)
index 0000000..992facb
--- /dev/null
@@ -0,0 +1,2 @@
+busybox mkdir -p foo/bar
+test -d foo -a -d foo/bar
diff --git a/testsuite/mkfs.minix.tests b/testsuite/mkfs.minix.tests
new file mode 100755 (executable)
index 0000000..90df931
--- /dev/null
@@ -0,0 +1,22 @@
+#!/bin/sh
+
+# mkfs.minix tests.
+# Copyright 2007 by Denys Vlasenko
+# Licensed under GPL v2, see file LICENSE for details.
+
+. testing.sh
+
+# testing "test name" "options" "expected result" "file input" "stdin"
+
+testing "mkfs.minix" \
+       "dd if=/dev/zero of=input bs=1k count=1024 2>/dev/null; mkfs.minix input; md5sum <input" \
+"352 inodes\n"\
+"1024 blocks\n"\
+"Firstdatazone=15 (15)\n"\
+"Zonesize=1024\n"\
+"Maxsize=268966912\n"\
+"4f35f7afeba07d56055bed1f29ae20b7  -\n" \
+       "" \
+       ""
+
+exit $FAILCOUNT
diff --git a/testsuite/mount.testroot b/testsuite/mount.testroot
new file mode 100755 (executable)
index 0000000..e18d046
--- /dev/null
@@ -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/mount.tests b/testsuite/mount.tests
new file mode 100755 (executable)
index 0000000..5374ecb
--- /dev/null
@@ -0,0 +1,35 @@
+#!/bin/sh
+
+# Copyright 2007 by Denys Vlasenko <vda.linux@googlemail.com>
+# Licensed under GPL v2, see file LICENSE for details.
+
+. testing.sh
+
+test "`id -u`" = 0 || {
+       echo "SKIPPED: must be root to test this"
+       exit 0
+}
+
+dd if=/dev/zero of=mount.image1m count=1 bs=1M 2>/dev/null || exit 1
+mkfs.minix -v mount.image1m >/dev/null 2>&1 || exit 1
+testdir=$PWD/testdir
+mkdir $testdir 2>/dev/null
+umount -d $testdir 2>/dev/null
+
+# testing "test name" "command" "expected result" "file input" "stdin"
+#   file input will be file called "input"
+#   test can create a file "actual" instead of writing to stdout
+
+testing "mount -o remount,mand" \
+"mount -o loop mount.image1m $testdir "\
+"&& grep -Fc $testdir </proc/mounts "\
+"&& mount -o remount,mand $testdir "\
+"&& grep -F $testdir </proc/mounts | grep -c '[, ]mand[, ]'" \
+       "1\n""1\n" \
+       "" ""
+
+umount -d $testdir
+rmdir $testdir
+rm mount.image1m
+
+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 (file)
index 0000000..9c7834b
--- /dev/null
@@ -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 (file)
index 0000000..c8eaba8
--- /dev/null
@@ -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 (file)
index 0000000..1fb355b
--- /dev/null
@@ -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 (file)
index 0000000..48afca4
--- /dev/null
@@ -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 (file)
index 0000000..edb4c37
--- /dev/null
@@ -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 (file)
index 0000000..eaa8215
--- /dev/null
@@ -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 (file)
index 0000000..77d088f
--- /dev/null
@@ -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 (file)
index 0000000..065c7f1
--- /dev/null
@@ -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 (file)
index 0000000..c413af0
--- /dev/null
@@ -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 (file)
index 0000000..bc9c313
--- /dev/null
@@ -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 (file)
index 0000000..b3ba3aa
--- /dev/null
@@ -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 (file)
index 0000000..ea565d2
--- /dev/null
@@ -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 (file)
index 0000000..7c572c4
--- /dev/null
@@ -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 (file)
index 0000000..48afca4
--- /dev/null
@@ -0,0 +1,4 @@
+touch foo
+busybox mv foo bar
+test ! -e foo
+test -f bar
diff --git a/testsuite/od.tests b/testsuite/od.tests
new file mode 100755 (executable)
index 0000000..69c2995
--- /dev/null
@@ -0,0 +1,17 @@
+#!/bin/sh
+# Copyright 2008 by Denys Vlasenko
+# Licensed under GPL v2, see file LICENSE for details.
+
+. testing.sh
+
+# testing "test name" "options" "expected result" "file input" "stdin"
+
+testing "od -b" \
+       "od -b" \
+"\
+0000000 110 105 114 114 117
+0000006
+" \
+       "" "HELLO"
+
+exit $FAILCOUNT
diff --git a/testsuite/parse.tests b/testsuite/parse.tests
new file mode 100755 (executable)
index 0000000..f1ee7b8
--- /dev/null
@@ -0,0 +1,109 @@
+#!/bin/sh
+
+# Copyright 2008 by Denys Vlasenko <vda.linux@googlemail.com>
+# Licensed under GPL v2, see file LICENSE for details.
+
+. testing.sh
+
+COLLAPSE=$(( 0x00010000))
+TRIM=$((     0x00020000))
+GREEDY=$((   0x00040000))
+MIN_DIE=$((  0x00100000))
+KEEP_COPY=$((0x00200000))
+ESCAPE=$((   0x00400000))
+NORMAL=$((   COLLAPSE | TRIM | GREEDY))
+
+# testing "description" "command" "result" "infile" "stdin"
+
+testing "parse mdev.conf" \
+       "parse -n 4 -m 3 -f $((NORMAL)) -" \
+       "[sda][0:0][644][@echo @echo TEST]\n" \
+       "-" \
+       " sda 0:0 644 @echo @echo TEST # echo trap\n"
+
+testing "parse notrim" \
+       "parse -n 4 -m 3 -f $((NORMAL - TRIM - COLLAPSE)) -" \
+       "[][sda][0:0][644 @echo @echo TEST ]\n" \
+       "-" \
+       " sda 0:0 644 @echo @echo TEST \n"
+
+FILE=__parse
+cat >$FILE <<EOF
+#
+# Device         Point               System                       Options
+#_______________________________________________________________
+/dev/hdb3       /                       ext2                 defaults      1          0
+   /dev/hdb1       /dosc               hpfs                 ro      1          0
+ /dev/fd0          /dosa              vfat                  rw,user,noauto,nohide       0              0
+       /dev/fd1          /dosb              vfat                  rw,user,noauto,nohide         0              0
+#
+ /dev/cdrom     /cdrom            iso9660          ro,user,noauto,nohide        0              0
+/dev/hdb5       /redhat            ext2                 rw,root,noauto,nohide   0              0 #sssd
+       /dev/hdb6       /win2home     ntfs                  rw,root,noauto,nohide        0              0# ssdsd
+/dev/hdb7       /win2skul        ntfs                  rw,root,noauto,nohide none       0              0
+none     /dev/pts           devpts             gid=5,mode=620                 0    0 
+     none                /proc               proc                defaults     0          0
+EOF
+
+cat >$FILE.res <<EOF
+[/dev/hdb3][/][ext2][defaults][1][0]
+[/dev/hdb1][/dosc][hpfs][ro][1][0]
+[/dev/fd0][/dosa][vfat][rw,user,noauto,nohide][0][0]
+[/dev/fd1][/dosb][vfat][rw,user,noauto,nohide][0][0]
+[/dev/cdrom][/cdrom][iso9660][ro,user,noauto,nohide][0][0]
+[/dev/hdb5][/redhat][ext2][rw,root,noauto,nohide][0][0]
+[/dev/hdb6][/win2home][ntfs][rw,root,noauto,nohide][0][0]
+[/dev/hdb7][/win2skul][ntfs][rw,root,noauto,nohide][none][0            0]
+[none][/dev/pts][devpts][gid=5,mode=620][0][0]
+[none][/proc][proc][defaults][0][0]
+EOF
+
+testing "parse polluted fstab" \
+       "parse -n 6 -m 6 $FILE" \
+       "`cat $FILE.res`\n" \
+       "" \
+       ""
+cp ../examples/inittab $FILE
+cat >$FILE.res <<EOF
+[][][sysinit][/etc/init.d/rcS]
+[][][askfirst][-/bin/sh]
+[tty2][][askfirst][-/bin/sh]
+[tty3][][askfirst][-/bin/sh]
+[tty4][][askfirst][-/bin/sh]
+[tty4][][respawn][/sbin/getty 38400 tty5]
+[tty5][][respawn][/sbin/getty 38400 tty6]
+[][][restart][/sbin/init]
+[][][ctrlaltdel][/sbin/reboot]
+[][][shutdown][/bin/umount -a -r]
+[][][shutdown][/sbin/swapoff -a]
+EOF
+
+testing "parse inittab from examples" \
+       "parse -n 4 -m 4 -f $((NORMAL - TRIM - COLLAPSE)) -d'#:' $FILE" \
+       "`cat $FILE.res`\n" \
+       "" \
+       ""
+
+cp ../examples/udhcp/udhcpd.conf $FILE
+cat >$FILE.res <<EOF
+[start][192.168.0.20]
+[end][192.168.0.254]
+[interface][eth0]
+[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]
+[option][domain][local]
+[option][lease][864000]
+EOF
+
+testing "parse udhcpd.conf from examples" \
+       "parse -n 127 $FILE" \
+       "`cat $FILE.res`\n" \
+       "" \
+       ""
+
+rm -f $FILE $FILE.res
+
+exit $FAILCOUNT
diff --git a/testsuite/patch.tests b/testsuite/patch.tests
new file mode 100755 (executable)
index 0000000..cfe69b7
--- /dev/null
@@ -0,0 +1,65 @@
+#!/bin/sh
+# Copyright 2008 by Denys Vlasenko
+# Licensed under GPL v2, see file LICENSE for details.
+
+. testing.sh
+
+# testing "test name" "options" "expected result" "file input" "stdin"
+
+testing "patch with old_file == new_file" \
+       "patch; echo $?; cat input" \
+"\
+patching file input
+0
+qwe
+asd
+zxc
+" \
+       "qwe\nzxc\n" \
+"\
+--- input      Jan 01 01:01:01 2000
++++ input      Jan 01 01:01:01 2000
+@@ -1,2 +1,3 @@
+ qwe
++asd
+ zxc
+" \
+
+testing "patch with nonexistent old_file" \
+       "patch; echo $?; cat input" \
+"\
+patching file input
+0
+qwe
+asd
+zxc
+" \
+       "qwe\nzxc\n" \
+"\
+--- input.doesnt_exist Jan 01 01:01:01 2000
++++ input      Jan 01 01:01:01 2000
+@@ -1,2 +1,3 @@
+ qwe
++asd
+ zxc
+" \
+
+testing "patch -R with nonexistent old_file" \
+       "patch -R; echo $?; cat input" \
+"\
+patching file input
+0
+qwe
+zxc
+" \
+       "qwe\nasd\nzxc\n" \
+"\
+--- input.doesnt_exist Jan 01 01:01:01 2000
++++ input      Jan 01 01:01:01 2000
+@@ -1,2 +1,3 @@
+ qwe
++asd
+ zxc
+" \
+
+exit $FAILCOUNT
diff --git a/testsuite/pidof.tests b/testsuite/pidof.tests
new file mode 100755 (executable)
index 0000000..a05a302
--- /dev/null
@@ -0,0 +1,31 @@
+#!/bin/sh
+
+# pidof tests.
+# Copyright 2005 by Bernhard Reutner-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
+# This test fails now because process name matching logic has changed,
+# but new logic is not "wrong" either... see find_pid_by_name.c comments
+#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/printf.tests b/testsuite/printf.tests
new file mode 100755 (executable)
index 0000000..f9d1dec
--- /dev/null
@@ -0,0 +1,108 @@
+#!/bin/sh
+# Copyright 2008 by Denys Vlasenko
+# Licensed under GPL v2, see file LICENSE for details.
+
+. testing.sh
+
+# Need this in order to not execute shell builtin
+bb="busybox "
+
+# testing "test name" "command" "expected result" "file input" "stdin"
+
+testing "printf produces no further output 1" \
+       "${bb}printf '\c' foo" \
+       "" \
+       "" ""
+
+testing "printf produces no further output 2" \
+       "${bb}printf '%s\c' foo bar" \
+       "foo" \
+       "" ""
+
+testing "printf repeatedly uses pattern for each argv" \
+       "${bb}printf '%s\n' foo \$HOME" \
+       "foo\n$HOME\n" \
+       "" ""
+
+testing "printf understands %b escaped_string" \
+       "${bb}printf '%b' 'a\tb' 'c\\d\n' 2>&1; echo \$?" \
+       "a\tbc\\d\n""0\n" \
+       "" ""
+
+testing "printf understands %d '\"x' \"'y\" \"'zTAIL\"" \
+       "${bb}printf '%d\n' '\"x' \"'y\" \"'zTAIL\" 2>&1; echo \$?" \
+       "120\n""121\n""122\n""0\n" \
+       "" ""
+
+testing "printf understands %s '\"x' \"'y\" \"'zTAIL\"" \
+       "${bb}printf '%s\n' '\"x' \"'y\" \"'zTAIL\" 2>&1; echo \$?" \
+       "\"x\n""'y\n""'zTAIL\n""0\n" \
+       "" ""
+
+testing "printf understands %23.12f" \
+       "${bb}printf '|%23.12f|\n' 5.25 2>&1; echo \$?" \
+       "|         5.250000000000|\n""0\n" \
+       "" ""
+
+testing "printf understands %*.*f" \
+       "${bb}printf '|%*.*f|\n' 23 12 5.25 2>&1; echo \$?" \
+       "|         5.250000000000|\n""0\n" \
+       "" ""
+
+testing "printf understands %*f with negative width" \
+       "${bb}printf '|%*f|\n' -23 5.25 2>&1; echo \$?" \
+       "|5.250000               |\n""0\n" \
+       "" ""
+
+testing "printf understands %.*f with negative precision" \
+       "${bb}printf '|%.*f|\n' -12 5.25 2>&1; echo \$?" \
+       "|5.250000|\n""0\n" \
+       "" ""
+
+testing "printf understands %*.*f with negative width/precision" \
+       "${bb}printf '|%*.*f|\n' -23 -12 5.25 2>&1; echo \$?" \
+       "|5.250000               |\n""0\n" \
+       "" ""
+
+testing "printf understands %zd" \
+       "${bb}printf '%zd\n' -5 2>&1; echo \$?" \
+       "-5\n""0\n" \
+       "" ""
+
+testing "printf understands %ld" \
+       "${bb}printf '%ld\n' -5 2>&1; echo \$?" \
+       "-5\n""0\n" \
+       "" ""
+
+testing "printf understands %Ld" \
+       "${bb}printf '%Ld\n' -5 2>&1; echo \$?" \
+       "-5\n""0\n" \
+       "" ""
+
+# We are "more correct" here than bash/coreutils: they happily print -2
+# as if it is a huge unsigned number
+testing "printf handles %u -N" \
+       "${bb}printf '%u\n' 1 -2 3 2>&1; echo \$?" \
+       "1\n""printf: -2: invalid number\n""0\n""3\n""0\n" \
+       "" ""
+
+# Actually, we are wrong here: exit code should be 1
+testing "printf handles %d bad_input" \
+       "${bb}printf '%d\n' 1 - 2 bad 3 123bad 4 2>&1; echo \$?" \
+"1\n""printf: -: invalid number\n""0\n"\
+"2\n""printf: bad: invalid number\n""0\n"\
+"3\n""printf: 123bad: invalid number\n""0\n"\
+"4\n""0\n" \
+       "" ""
+
+testing "printf aborts on bare %" \
+       "${bb}printf '%' a b c 2>&1; echo \$?" \
+       "printf: %: invalid format\n""1\n" \
+       "" ""
+
+testing "printf aborts on %r" \
+       "${bb}printf '%r' a b c 2>&1; echo \$?" \
+       "printf: %r: invalid format\n""1\n" \
+       "" ""
+
+exit $FAILCOUNT
diff --git a/testsuite/pwd/pwd-prints-working-directory b/testsuite/pwd/pwd-prints-working-directory
new file mode 100644 (file)
index 0000000..8575347
--- /dev/null
@@ -0,0 +1 @@
+test $(pwd) = $(busybox pwd)
diff --git a/testsuite/readlink.tests b/testsuite/readlink.tests
new file mode 100755 (executable)
index 0000000..0faa6ed
--- /dev/null
@@ -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 (file)
index 0000000..46571a9
--- /dev/null
@@ -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 (file)
index 0000000..222f5de
--- /dev/null
@@ -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 (executable)
index 0000000..cade871
--- /dev/null
@@ -0,0 +1,164 @@
+#!/bin/sh
+
+# Usage:
+# runtest [applet1] [applet2...]
+
+# Helper for helpers. Oh my...
+test x"$ECHO" != x"" || {
+       ECHO="echo"
+       test x"`echo -ne`" = x"" || {
+               # Compile and use a replacement 'echo' which understands -e -n
+               ECHO="$PWD/echo-ne"
+               test -x "$ECHO" || {
+                       gcc -Os -o "$ECHO" ../scripts/echo.c || exit 1
+               }
+       }
+       export ECHO
+}
+
+# Run one old-style test.
+# Tests are stored in applet/testcase shell scripts.
+# They are run using "sh -x -e applet/testcase".
+# Option -e will make testcase stop on the first failed command.
+run_applet_testcase()
+{
+       local applet="$1"
+       local testcase="$2"
+
+       local status=0
+       local uc_applet=$(echo "$applet" | tr a-z A-Z)
+       local testname="$testcase"
+
+       testname="${testname##*/}" # take basename
+       if grep "^# CONFIG_$uc_applet is not set$" "$bindir/.config" >/dev/null; then
+               echo "UNTESTED: $testname"
+               return 0
+       fi
+
+       if grep "^# FEATURE: " "$testcase" >/dev/null; then
+               local feature=$(sed -ne 's/^# FEATURE: //p' "$testcase")
+
+               if grep "^# $feature is not set$" "$bindir/.config" >/dev/null; then
+                       echo "UNTESTED: $testname"
+                       return 0
+               fi
+       fi
+
+       rm -rf ".tmpdir.$applet"
+       mkdir -p ".tmpdir.$applet"
+       cd ".tmpdir.$applet" || return 1
+
+#      echo "Running testcase $testcase"
+       d="$tsdir" \
+               sh -x -e "$testcase" >"$testname.stdout.txt" 2>&1 || status=$?
+       if [ $status -ne 0 ]; then
+               echo "FAIL: $testname"
+               if [ x"$VERBOSE" != x ]; then
+                       cat "$testname.stdout.txt"
+               fi
+       else
+               echo "PASS: $testname"
+       fi
+
+       cd ..
+       rm -rf ".tmpdir.$applet"
+
+       return $status
+}
+
+# Run all old-style tests for given applet
+run_oldstyle_applet_tests()
+{
+       local applet="$1"
+       local status=0
+
+       for testcase in "$tsdir/$applet"/*; do
+               # switch on basename of $testcase
+               case "${testcase##*/}" in
+                       .*)     continue ;;    # .svn, .git etc
+                       *~)     continue ;;    # backup files
+                       "CVS")  continue ;;
+                       \#*)    continue ;;    # CVS merge residues
+                       *.mine) continue ;;    # svn-produced junk
+                       *.r[0-9]*) continue ;; # svn-produced junk
+               esac
+               run_applet_testcase "$applet" "$testcase" || status=1
+       done
+       return $status
+}
+
+
+
+lcwd=$(pwd)
+[ x"$tsdir" != x ] || tsdir="$lcwd"
+[ x"$bindir" != x ] || bindir="${lcwd%/*}" # one directory up from $lcwd
+PATH="$bindir:$PATH"
+
+if [ x"$VERBOSE" = x ]; then
+       export VERBOSE=
+fi
+
+if [ x"$1" = x"-v" ]; then
+       export VERBOSE=1
+       shift
+fi
+
+implemented=$(
+       "$bindir/busybox" 2>&1 |
+       while read line; do
+               if [ x"$line" = x"Currently defined functions:" ]; then
+                       xargs | sed 's/,//g'
+                       break
+               fi
+       done
+       )
+
+applets="$implemented"
+if [ $# -ne 0 ]; then
+       applets="$@"
+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 $implemented; do
+       ln -s "$bindir/busybox" "$LINKSDIR/$i"
+done
+
+# Set up option flags so tests can be selective.
+export OPTIONFLAGS=:$(
+       sed -nr 's/^CONFIG_//p' "$bindir/.config" |
+       sed 's/=.*//' | xargs | sed 's/ /:/g'
+       )
+
+status=0
+for applet in $applets; do
+       # Any old-style tests for this applet?
+       if [ -d "$tsdir/$applet" ]; then
+               run_oldstyle_applet_tests "$applet" || status=1
+       fi
+
+       # Is this a new-style test?
+       if [ -f "$applet.tests" ]; then
+               if [ ! -h "$LINKSDIR/$applet" ]; then
+                       # (avoiding bash'ism "${applet:0:4}")
+                       if ! echo "$applet" | grep "^all_" >/dev/null; then
+                               echo "SKIPPED: $applet (not built)"
+                               continue
+                       fi
+               fi
+#              echo "Running test $tsdir/$applet.tests"
+               PATH="$LINKSDIR:$tsdir:$bindir:$PATH" \
+                       "$tsdir/$applet.tests" || status=1
+       fi
+done
+
+# Leaving the dir makes it somewhat easier to run failed test by hand
+#rm -rf "$LINKSDIR"
+
+if [ $status -ne 0 ] && [ x"$VERBOSE" = x ]; then
+       echo "Failures detected, running with -v (verbose) will give more info"
+fi
+exit $status
diff --git a/testsuite/sed.tests b/testsuite/sed.tests
new file mode 100755 (executable)
index 0000000..9a7f886
--- /dev/null
@@ -0,0 +1,210 @@
+#!/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"
+testing "sed s with \\t (GNU ext)" "sed 's/\t/ /'" "one two" "" "one\ttwo"
+
+# 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"
+testing "sed subst+write" \
+       "sed -e 's/i/z/' -e 'woutputw' input -; echo -n X; cat outputw" \
+       "thzngy\nagaznXthzngy\nagazn" "thingy" "again"
+rm outputw
+testing "sed trailing NUL" \
+       "sed 's/i/z/' input -" \
+       "a\0b\0\nc" "a\0b\0" "c"
+testing "sed escaped newline in command" \
+       "sed 's/a/z\\
+z/' input" \
+       "z\nz" "a" ""
+
+# 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"
+# sed match EOF inline: gnu sed 4.1.5 outputs this:
+#00000000  6f 6e 65 0a 6f 6f 6b 0a  6f 6f 6b 0a 74 77 6f 0a  |one.ook.ook.two.|
+#00000010  0a 74 68 72 65 65 0a 6f  6f 6b 0a 6f 6f 6b 0a 66  |.three.ook.ook.f|
+#00000020  6f 75 72                                          |our|
+# which looks buggy to me.
+$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"
+
+# testing "description" "arguments" "result" "infile" "stdin"
+
+testing "sed n command must reset 'substituted' bit" \
+       "sed 's/1/x/;T;n;: next;s/3/y/;t quit;n;b next;: quit;q'" \
+       "0\nx\n2\ny\n" "" "0\n1\n2\n3\n"
+
+exit $FAILCOUNT
diff --git a/testsuite/seq.tests b/testsuite/seq.tests
new file mode 100755 (executable)
index 0000000..cea4eef
--- /dev/null
@@ -0,0 +1,41 @@
+#!/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.0\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" "" "" ""
+
+testing "seq one argument with padding" "seq -w 003" "001\n002\n003\n" "" ""
+testing "seq two arguments with padding" "seq -w 005 7" "005\n006\n007\n" "" ""
+testing "seq count down by 3 with padding" "seq -w 8 -3 04" "08\n05\n" "" ""
+testing "seq count by .3 with padding" "seq -w 03 .3 0004" "003.0\n003.3\n003.6\n003.9\n" "" ""
+
+exit $FAILCOUNT
diff --git a/testsuite/sort.tests b/testsuite/sort.tests
new file mode 100755 (executable)
index 0000000..f700dc0
--- /dev/null
@@ -0,0 +1,119 @@
+#!/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 range with two -k options" "sort -k 2,2n -k 1,1r input" "\
+d 2
+b 2
+c 3
+" "\
+c 3
+b 2
+d 2
+" ""
+
+testing "sort with non-default leading delim 1" "sort -n -k2 -t/ input" "\
+/a/2
+/b/1
+" "\
+/a/2
+/b/1
+" ""
+
+testing "sort with non-default leading delim 2" "sort -n -k3 -t/ input" "\
+/b/1
+/a/2
+" "\
+/b/1
+/a/2
+" ""
+
+testing "sort with non-default leading delim 3" "sort -n -k3 -t/ input" "\
+//a/2
+//b/1
+" "\
+//a/2
+//b/1
+" ""
+
+testing "sort -u should consider field only when discarding" "sort -u -k2 input" "\
+a c
+" "\
+a c
+b c
+" ""
+
+testing "sort -z outputs NUL terminated lines" "sort -z input" "\
+one\0three\0two\0\
+" "\
+one\0two\0three\0\
+" ""
+
+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"
+
+exit $FAILCOUNT
diff --git a/testsuite/start-stop-daemon.tests b/testsuite/start-stop-daemon.tests
new file mode 100755 (executable)
index 0000000..ba77cde
--- /dev/null
@@ -0,0 +1,19 @@
+#!/bin/sh
+# Copyright 2008 by Denys Vlasenko
+# Licensed under GPL v2, see file LICENSE for details.
+
+. testing.sh
+
+# testing "test name" "cmd" "expected result" "file input" "stdin"
+
+testing "start-stop-daemon -x without -a" \
+       'start-stop-daemon -S -x true 2>&1; echo $?' \
+       "0\n" \
+       "" ""
+
+testing "start-stop-daemon -a without -x" \
+       'start-stop-daemon -S -a false 2>&1; echo $?' \
+       "1\n" \
+       "" ""
+
+exit $FAILCOUNT
diff --git a/testsuite/strings/strings-works-like-GNU b/testsuite/strings/strings-works-like-GNU
new file mode 100644 (file)
index 0000000..2d64710
--- /dev/null
@@ -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/sum.tests b/testsuite/sum.tests
new file mode 100755 (executable)
index 0000000..e537cf6
--- /dev/null
@@ -0,0 +1,24 @@
+#!/bin/sh
+
+# unit test for sum.
+# Copyright 2007 by Bernhard Reutner-Fischer
+# Licensed under GPL v2 or later, see file LICENSE for details.
+
+# AUDIT: Unit tests for sum
+
+. 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
+
+testing "sum -r file doesn't print file's name" \
+        "sum -r $0 | grep -c $0 && echo wrongly_printed_filename || echo yes" \
+       "0\nyes\n" "" ""
+testing "sum -r file file does print both names" \
+        "sum -r $0 $0 | grep -c $0 && echo yes || echo wrongly_omitted_filename" \
+       "2\nyes\n" "" ""
+testing "sum -s file does print file's name" \
+        "sum -s $0 | grep -c $0 && echo yes || echo wrongly_omitted_filename" \
+       "1\nyes\n" "" ""
+exit $FAILCOUNT
diff --git a/testsuite/tail/tail-n-works b/testsuite/tail/tail-n-works
new file mode 100644 (file)
index 0000000..0e1319f
--- /dev/null
@@ -0,0 +1,4 @@
+$ECHO -ne "abc\ndef\n123\n" >input
+$ECHO -ne "def\n123\n" >logfile.ok
+busybox tail -n 2 input > logfile.bb
+cmp logfile.ok logfile.bb
diff --git a/testsuite/tail/tail-works b/testsuite/tail/tail-works
new file mode 100644 (file)
index 0000000..f3434d1
--- /dev/null
@@ -0,0 +1,4 @@
+$ECHO -ne "abc\ndef\n123\n" >input
+$ECHO -ne "def\n123\n" >logfile.ok
+busybox tail -2 input > logfile.bb
+cmp logfile.ok logfile.bb
diff --git a/testsuite/tar/tar-archives-multiple-files b/testsuite/tar/tar-archives-multiple-files
new file mode 100644 (file)
index 0000000..245d9e9
--- /dev/null
@@ -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 (file)
index 0000000..26e8cbb
--- /dev/null
@@ -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 (file)
index 0000000..85e7f60
--- /dev/null
@@ -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 (file)
index 0000000..130d0e7
--- /dev/null
@@ -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 (file)
index 0000000..886c37c
--- /dev/null
@@ -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 (file)
index 0000000..ca72f24
--- /dev/null
@@ -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 (file)
index 0000000..a30e9f0
--- /dev/null
@@ -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 (file)
index 0000000..7897d81
--- /dev/null
@@ -0,0 +1,6 @@
+touch foo bar
+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 (file)
index 0000000..ca48e36
--- /dev/null
@@ -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 (file)
index 0000000..5b55e46
--- /dev/null
@@ -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 (file)
index 0000000..5033642
--- /dev/null
@@ -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 (file)
index 0000000..2de0f0e
--- /dev/null
@@ -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 (file)
index 0000000..155b27e
--- /dev/null
@@ -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 (file)
index 0000000..39013a1
--- /dev/null
@@ -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/tar/tar_with_link_with_size b/testsuite/tar/tar_with_link_with_size
new file mode 100644 (file)
index 0000000..5b61cc7
--- /dev/null
@@ -0,0 +1,29 @@
+# This tarball contains a softlink with size field != 0.
+# If not ignored, it makes hext header to be skipped
+# and data to be read as a header.
+# GNU tar 1.15.1 has a bug here: tf won't work, but xf will.
+tar1_bz2()
+{
+    $ECHO -ne "\x42\x5a\x68\x39\x31\x41\x59\x26\x53\x59\x14\x44\xe3\xdd\x00\x00"
+    $ECHO -ne "\x9a\xfb\x90\xca\x18\x00\xc0\x40\x03\xff\x80\x08\x00\x7b\xe3\xff"
+    $ECHO -ne "\x80\x04\x00\x00\x08\x30\x00\xd6\xb3\x09\x45\x19\x0d\x0d\x41\x84"
+    $ECHO -ne "\x1a\x68\xd0\x7a\x99\x90\x4a\x0a\x6d\x4c\xa3\x20\x7a\x41\xa0\x00"
+    $ECHO -ne "\x00\x55\x25\x34\x1a\x34\xd0\x00\x64\x64\x1a\x32\x3f\x76\x3c\x1c"
+    $ECHO -ne "\xd3\x3c\xa0\x84\x9b\x88\x05\x70\x90\xbb\x18\x28\x39\x29\xb3\x30"
+    $ECHO -ne "\xa8\x0a\x21\x70\x0c\x01\x32\x3b\xbe\xde\xd7\x13\x2e\xbd\x2a\x9c"
+    $ECHO -ne "\xa8\x42\x2a\x91\x15\xe2\xa1\xcd\x24\x37\x9c\x91\xaa\xc7\x14\xdb"
+    $ECHO -ne "\x4c\x08\xaa\xaf\x12\xeb\x6c\x37\x96\xb0\xa4\x25\x0c\xb4\x4b\xc5"
+    $ECHO -ne "\x52\x70\x3b\x25\x4c\x0e\x46\x67\x51\x54\x89\x13\x13\xf0\xa8\xe9"
+    $ECHO -ne "\x68\x4e\x8c\x81\xfc\x79\xe0\xb0\xd8\x79\x34\x94\x71\xa2\x0c\xbe"
+    $ECHO -ne "\x93\x61\x82\x95\x10\x88\xd1\xa6\x69\xaa\x38\x9c\xb6\xc2\xb2\x94"
+    $ECHO -ne "\x90\xc3\x82\x29\xe8\x8c\xb8\x95\x83\x32\x40\x61\x11\x11\xd3\xaa"
+    $ECHO -ne "\x3f\x8b\xb9\x22\x9c\x28\x48\x0a\x22\x71\xee\x80"
+}
+res1="\
+lrwxrwxrwx user/group         0 2008-07-19 15:02:37 firmware-372/sources/native/bin/chroot-setup.sh -> qemu-setup.sh
+-rwxr-xr-x user/group       512 2008-07-19 15:02:37 firmware-372/sources/native/bin/qemu-setup.sh"
+
+export TZ=UTC-2
+
+t=`tar1_bz2 | bunzip2 | busybox tar tvf -`
+test x"$res1" = x"$t"
diff --git a/testsuite/tar/tar_with_prefix_fields b/testsuite/tar/tar_with_prefix_fields
new file mode 100644 (file)
index 0000000..1c7124d
--- /dev/null
@@ -0,0 +1,261 @@
+tar1_bz2()
+{
+    $ECHO -ne "\x42\x5a\x68\x39\x31\x41\x59\x26\x53\x59\x12\xd1\x86\x30\x00\x0c"
+    $ECHO -ne "\xb8\x7f\x80\xff\x50\x08\xa0\x5e\xff\xff\xfd\x7f\xff\xff\xee\xff"
+    $ECHO -ne "\xff\xff\xfa\x00\x08\x60\x0f\xc5\x3e\xf4\xdc\x00\x00\x59\x25\xbd"
+    $ECHO -ne "\xb8\x7a\x02\xb5\x82\x78\x25\xb0\x89\x54\x10\x11\x44\x8b\x36\x36"
+    $ECHO -ne "\xc8\x97\x0d\x34\x9a\x21\xa9\x36\xa9\xed\x32\x02\x8d\xa6\x81\x8a"
+    $ECHO -ne "\x79\x13\x4d\x1a\x03\x10\x69\xa0\xd3\x40\x64\x0f\x44\x68\x3d\x41"
+    $ECHO -ne "\x2a\x7a\x20\x09\xa1\x34\x9a\x09\xa4\xc8\xf5\x4f\x46\xa6\x86\x32"
+    $ECHO -ne "\x4c\x08\x00\x00\xd0\x06\x9a\x00\xd3\xd4\x11\x49\xa7\xb5\x20\x1a"
+    $ECHO -ne "\x7a\x80\x00\x00\x00\x00\xd0\x68\x00\x00\x00\x00\x00\x49\xa8\x89"
+    $ECHO -ne "\xa9\x31\x4f\x22\xa7\xea\x9b\x61\x53\xf4\x93\xf2\xa3\x47\xa4\xd3"
+    $ECHO -ne "\x41\xea\x06\x41\xa0\x0d\x00\x00\x00\x1e\xa0\x70\x34\xd3\x4d\x06"
+    $ECHO -ne "\x86\x86\x86\x46\x80\x64\x01\xa1\xa0\x34\xd1\x90\x00\x03\x09\x88"
+    $ECHO -ne "\x0d\x04\x89\x08\x00\x82\x7a\x4c\x42\x64\xc9\x3d\x1a\x29\xe9\xa2"
+    $ECHO -ne "\x3d\x46\x9e\x46\x9a\x13\x26\x9e\x53\x10\x01\x91\xea\x68\x19\xf0"
+    $ECHO -ne "\x73\xf2\xe0\xd1\x3c\x80\x01\xb1\x48\x44\x08\x9a\xba\xf3\x9e\x87"
+    $ECHO -ne "\xec\xc4\x4b\x02\x92\x80\x75\x00\x56\x42\x88\x10\x68\xcc\x06\x22"
+    $ECHO -ne "\x7c\x2b\xa7\xc8\x21\x91\x13\xe5\x72\xc0\xe6\x0c\x03\x10\xf2\x89"
+    $ECHO -ne "\x9c\x67\x6b\xc3\xe6\xae\x98\x85\x0a\x7f\x25\x2e\x3d\x84\x5b\xeb"
+    $ECHO -ne "\xf3\xff\xb3\x52\xf7\x6e\xf6\x92\xd6\x33\x5f\x4f\xd1\x3d\xb7\xc4"
+    $ECHO -ne "\x0d\x50\x02\x49\x01\xaf\xd0\x69\xbb\xd3\xe9\x63\x0a\x68\x36\x92"
+    $ECHO -ne "\xf2\x03\x1d\xf2\xe2\x35\xbc\x73\xd4\x44\xf6\xa0\xe0\x31\xd7\x7d"
+    $ECHO -ne "\x56\x96\xcb\x52\xfc\x79\xe0\xeb\xf7\x34\xd8\xda\x18\x72\x30\x94"
+    $ECHO -ne "\x53\x45\xf5\x54\x56\x6c\x0b\x50\xa0\xbc\xbd\xcc\xd8\x21\xab\x7b"
+    $ECHO -ne "\xa8\xa4\xe4\x78\x25\x73\xbf\x4b\x30\x38\x71\xe9\x3c\x14\x5d\xa3"
+    $ECHO -ne "\x12\x04\x6b\x37\x9d\xe5\xce\xa5\xd9\xd1\xa5\x69\x09\x08\xc4\x48"
+    $ECHO -ne "\x4b\x34\x58\x81\x15\x18\x88\xac\x11\x51\x88\x35\x0d\xd3\x13\x18"
+    $ECHO -ne "\x67\x73\x20\x5c\x28\x03\x26\xcd\x6d\x20\x90\xba\xa4\x12\xb3\x08"
+    $ECHO -ne "\x27\x74\x6a\x99\xdf\xb1\x20\x3d\x85\xe7\x5f\xab\x0e\x2e\xdc\x23"
+    $ECHO -ne "\x99\xe1\xef\x34\x68\xcd\xa9\xb0\xbf\xda\xec\x81\xdd\x66\xca\x21"
+    $ECHO -ne "\x13\x47\xd7\xca\x48\xcf\xeb\x25\xbb\x79\x6d\x40\xd0\xe4\x69\x3c"
+    $ECHO -ne "\x8f\x09\x1e\x7b\xaa\x4b\x91\x39\xac\xd6\xd2\x0c\x85\x1d\xf7\x70"
+    $ECHO -ne "\x1f\x1e\x58\xbb\x22\x11\x29\x39\x14\x4d\x58\x81\x9f\xd7\x1e\x22"
+    $ECHO -ne "\x21\x91\x0a\x40\xd1\x87\x29\x99\x93\xf4\xf3\x25\x48\xbb\xb4\x24"
+    $ECHO -ne "\x2a\x1c\xa7\x28\xc1\x68\x08\x25\x00\xaa\x3d\xee\xae\xc1\xe1\x4f"
+    $ECHO -ne "\xe6\x9a\x26\x6b\xcf\xb1\x3e\xb9\x85\x04\xf4\xef\xff\x7a\x2f\x2a"
+    $ECHO -ne "\x04\x08\xe0\x4c\xb8\xbd\x8b\x81\xbf\xa2\xbe\x82\x52\x9b\x40\x63"
+    $ECHO -ne "\xe6\xf3\xb3\xe4\xe6\xe5\x94\x4a\xdd\xc3\x1b\xaf\x61\xf3\xbf\x5b"
+    $ECHO -ne "\x6d\xaa\xaa\x27\xe8\x50\x8d\x23\x97\xa4\xbd\xc3\xd2\xe6\xb5\x66"
+    $ECHO -ne "\x9a\x1a\x8e\x45\x2a\xed\x0b\x79\xb8\x89\x38\x4a\x04\x85\x0d\x1e"
+    $ECHO -ne "\x2b\x77\x51\x91\x5f\x9f\xe0\x2a\x49\x56\xd3\xa1\xde\xf6\xd7\x88"
+    $ECHO -ne "\x5a\x61\xe5\x04\x54\xdf\xa3\x92\xeb\xbf\x75\x39\xce\xfa\xf5\xde"
+    $ECHO -ne "\x30\xd7\x56\xd1\x7d\x2c\xdf\xda\x3e\x1c\xc8\xc2\x93\x61\x21\x20"
+    $ECHO -ne "\xb2\x22\x6d\xbe\x39\x52\x64\xf6\xb3\x91\x21\x86\xdb\x67\x72\x8f"
+    $ECHO -ne "\x49\xad\xe4\x93\x39\x5c\x34\x8f\x58\xdb\x58\xd3\x3c\x1e\x4c\x6c"
+    $ECHO -ne "\xbb\x70\x6f\x42\xcf\x9e\xbf\xb1\xcb\xa9\x8d\x05\xe7\xea\xea\xd7"
+    $ECHO -ne "\x3c\x67\x31\x69\x44\x33\xa4\x92\x9c\x65\xa4\x89\x5a\xae\xcf\xc9"
+    $ECHO -ne "\x55\x43\x62\x6d\xbf\x05\x3c\xd1\x0f\x01\x4a\xb5\x1d\xbb\x2c\xfb"
+    $ECHO -ne "\xa6\xb7\xb3\xb1\x1d\x66\xd3\xeb\x22\xd0\xb5\x5a\x4b\xc4\x47\x47"
+    $ECHO -ne "\x5a\x49\x85\x18\xbc\x15\x39\x3b\x92\xee\x51\x98\x33\x34\x5d\xb5"
+    $ECHO -ne "\xbb\x8b\x94\x8c\xde\x8e\x3f\x3d\x09\x4f\xba\xd3\xf6\x79\x74\x8e"
+    $ECHO -ne "\x82\x0d\x56\x85\xa2\xc7\xc6\xa6\x89\x29\x26\xa3\x53\x5e\x52\xf5"
+    $ECHO -ne "\x56\x74\x8b\x17\x82\xed\x7a\x8b\x68\x61\xa5\xc9\x7c\xde\x9f\x68"
+    $ECHO -ne "\x27\x4d\xea\x65\x68\x6f\x7d\x5e\x88\x73\x87\x6c\x92\xf2\xa9\x15"
+    $ECHO -ne "\x4e\xee\x4d\x41\xbb\x98\x5d\x8a\xaf\xcb\x11\x7b\x2a\xce\xf4\x1e"
+    $ECHO -ne "\x3a\x28\x48\x14\xfe\x7f\x09\x45\x48\xf1\x5b\xc1\xcb\xcd\x91\xba"
+    $ECHO -ne "\x3b\xe2\x7d\x57\x85\x66\x68\xec\x51\x82\x97\x88\xeb\x94\x3b\x78"
+    $ECHO -ne "\x6c\xf4\xf1\x3e\x38\x8d\x22\x16\xab\x3b\x13\xb3\x1b\x39\x94\x0e"
+    $ECHO -ne "\xa8\x26\xb7\x8d\xe9\x7d\x66\x23\x4b\x65\x07\xb7\x2b\xc9\x96\xb6"
+    $ECHO -ne "\x99\x12\x22\xbc\x90\xda\x51\xbc\xfd\x97\xa5\x7d\xbc\x12\xa6\x72"
+    $ECHO -ne "\xd3\xe3\x8c\xc7\x58\xe1\xf8\x28\xf4\x46\x49\x14\xd0\x9d\xb6\xed"
+    $ECHO -ne "\xce\x99\xc6\xbc\xed\xa3\xab\xa0\x8c\x9d\xce\x1a\x1a\xc2\xe6\x77"
+    $ECHO -ne "\xba\xae\xba\xd6\xc9\xb2\xd1\x65\x24\x7b\x0d\xd4\xf2\xac\x28\xc3"
+    $ECHO -ne "\x1c\xbe\x4a\x54\xe3\x0f\x8d\xad\xb2\x37\x9e\x1f\x81\x72\x2d\xab"
+    $ECHO -ne "\x8f\xb1\xcd\xf7\xb4\x51\x2f\x1d\xf8\xad\x77\x14\x37\xd2\x1a\x9a"
+    $ECHO -ne "\xc0\xf2\x48\xc6\x4c\x8d\xd3\x8d\xf1\xd9\x2e\x2c\xdd\x7a\x98\x3c"
+    $ECHO -ne "\x24\x76\xb9\x9d\x27\xcd\x71\x7d\x6c\xc7\x1f\x0a\x74\x8a\x6e\x54"
+    $ECHO -ne "\xec\x5a\xa1\x77\x60\x80\xef\x00\xa4\x5f\x9e\x8b\x2f\x02\x72\x9c"
+    $ECHO -ne "\x46\xd8\x79\x92\x4c\x8f\x4e\x37\xed\x0c\x58\xab\x44\xee\x1d\xd1"
+    $ECHO -ne "\xa1\xb0\xa5\x1f\xaf\xb0\x39\x01\x26\xb2\x4a\x20\x68\x4a\x18\x23"
+    $ECHO -ne "\xc3\x03\x84\x22\x18\xdb\x6d\x83\x60\xc1\x12\x09\x21\x84\x22\x48"
+    $ECHO -ne "\x7f\x1e\x17\xf5\xbe\xce\x4c\x4f\x9f\x9f\xee\xf4\xfe\xef\x9a\x34"
+    $ECHO -ne "\x91\x8f\x36\x1d\xbc\x73\xd7\xeb\xc8\x2e\x81\x25\xfa\x18\x76\x35"
+    $ECHO -ne "\x1f\x16\xdb\x20\x4b\x74\x6d\x94\x4e\xe5\x36\xed\xf5\x5d\x59\xaf"
+    $ECHO -ne "\x46\x70\xea\x03\xac\x50\xbb\x26\xab\x39\x9a\x4b\x6b\x09\x8c\x6d"
+    $ECHO -ne "\x34\xcf\xed\xaa\xf7\x56\x40\xf2\xab\x07\xca\x22\x71\x97\xc7\x35"
+    $ECHO -ne "\xe8\x06\x90\x7b\xec\xc3\x9f\xa4\xde\xd9\xdb\x43\xf1\xd5\x06\x58"
+    $ECHO -ne "\x72\x9e\x1f\x08\xb6\xc2\x05\x0d\x25\xfe\x7a\x85\xe5\x10\x12\x68"
+    $ECHO -ne "\x18\x7e\x8c\xa0\xfa\xb4\xc4\xc7\x4e\xa9\xf2\x13\xd7\xc2\x52\xb5"
+    $ECHO -ne "\xe3\x72\x37\x31\x1e\x4f\x99\xfd\xac\x97\x08\x88\x71\x88\xeb\x1a"
+    $ECHO -ne "\xf9\xa1\x10\x9c\x44\x08\x56\x4a\x77\xaa\x0f\x19\x5f\x5f\xb3\x95"
+    $ECHO -ne "\xee\x9b\x9f\x5b\xb5\xc9\x0a\xf4\x28\x16\x25\x34\x6c\x72\xda\x92"
+    $ECHO -ne "\xb4\x2c\xbd\x5e\xb1\xe8\xe5\x0f\x68\xf3\x44\x8a\xd5\xfa\x73\x5c"
+    $ECHO -ne "\x89\x2e\x99\x7d\xed\xe3\x5b\x3f\x48\x97\xeb\xb6\x76\x5c\xa5\x9d"
+    $ECHO -ne "\xef\x12\x1e\x42\x89\x52\xad\x28\x90\xe5\x2b\x88\xa0\x4f\x11\x92"
+    $ECHO -ne "\xcd\xcc\x63\x40\x1a\xc7\x10\x0c\x2f\xcd\x01\xf2\x07\x38\xac\x14"
+    $ECHO -ne "\xe5\x90\xc0\x30\x21\xe2\xe3\x72\x0e\x3e\x04\xc8\x9e\xa7\x00\xdb"
+    $ECHO -ne "\x91\xdd\x9d\x80\xa4\x69\x2a\x48\x37\x97\xa4\x26\x5d\xae\x84\x1e"
+    $ECHO -ne "\x88\xf4\x83\x04\x24\xc9\x1f\x94\x61\x25\xf9\x82\xdd\xed\x2d\x96"
+    $ECHO -ne "\xad\x06\x45\xdd\x88\xd7\x50\x40\x14\xdc\x7c\xdb\x0f\x53\x96\x27"
+    $ECHO -ne "\xcb\x67\xac\xa6\xc1\x15\x2f\xc3\xdb\x2c\xca\x94\xb3\xf3\xd1\x6a"
+    $ECHO -ne "\xba\x34\x83\xd1\xcc\x40\x3e\x76\xa1\x69\x7f\x49\x33\xdc\xa7\x3c"
+    $ECHO -ne "\x6a\x67\x15\xab\xdb\x52\xa0\xb8\xa6\x1e\xce\xe3\xaf\xf4\xa2\x62"
+    $ECHO -ne "\x35\x0f\x03\x40\x8e\x20\x12\x9c\xb6\x34\x71\x3a\x15\x5d\xe5\x34"
+    $ECHO -ne "\xa8\xd4\x05\x99\x6b\x9a\xb6\x41\x0b\x78\xc4\xd8\xd9\x7a\x65\xdc"
+    $ECHO -ne "\xdb\xe3\x42\xd5\x66\xf9\xb4\x83\x7e\xc0\xf4\x01\xc4\xcc\x3b\x0e"
+    $ECHO -ne "\x15\xdc\x15\xc2\x3e\x04\x2f\xfc\x6b\x72\xeb\xf6\xaa\x16\x20\xde"
+    $ECHO -ne "\xd3\x3a\xb1\x10\xc6\x3c\xe8\x2b\xb8\xea\xda\x19\x6e\x36\xaa\xa4"
+    $ECHO -ne "\x23\x6d\xa0\x40\xd1\x5a\x0b\x7e\xa4\xd5\x2d\xcb\xa9\x15\x35\xba"
+    $ECHO -ne "\x93\x92\x45\x41\xb0\x1a\xd1\x13\x31\xb6\x44\x98\x78\x28\x15\xe4"
+    $ECHO -ne "\xae\xba\x58\xd1\x75\x36\x34\x1a\xd8\x28\xf1\x4a\x4c\xbc\x1b\xa8"
+    $ECHO -ne "\xf7\x57\x92\xbc\xe2\xb5\xda\xb6\xa6\x1d\x83\x37\x96\x43\x20\x84"
+    $ECHO -ne "\xcb\xb6\xd9\x3f\xeb\xfa\xa0\xfe\x9a\x7d\xee\x47\x98\xc4\xe7\xc4"
+    $ECHO -ne "\xbd\xc6\xf0\x6d\xb2\x26\x10\x1e\x78\xef\xf3\x28\x3e\x35\xe6\xe4"
+    $ECHO -ne "\xe6\xf3\x0f\x26\x34\x13\x85\xd0\xcf\x55\x0f\x8b\xd7\xe9\xf4\xdf"
+    $ECHO -ne "\x70\x68\xc0\xb5\x30\x3c\xb1\x01\xe8\x28\xae\x80\x26\x01\x8b\x15"
+    $ECHO -ne "\x0f\x80\x48\x18\x4b\xe2\xed\x59\x92\x31\xcf\xd2\x8f\x42\xbf\xee"
+    $ECHO -ne "\xbd\x07\x91\x24\xc6\x66\x5e\x8c\x9a\x48\x63\xe7\xac\x8a\x1e\xc5"
+    $ECHO -ne "\x69\x16\x8d\xac\x67\xdc\x75\x75\x82\xca\x19\x28\x36\x4d\x10\xf9"
+    $ECHO -ne "\x41\xcb\x15\x05\x64\xc7\xb0\xc3\x64\xf3\x48\x71\x60\xf2\xbd\xcc"
+    $ECHO -ne "\x37\xb1\x36\xbc\xa7\x2e\x6b\x20\x11\x51\x42\xe1\x8a\x29\xac\x44"
+    $ECHO -ne "\x8f\x63\x56\x23\xd4\xd4\x07\xb4\x60\xa4\xb8\xcd\xee\x49\xa5\x42"
+    $ECHO -ne "\xcc\x52\x00\x6f\xdc\x44\x20\x57\x7d\x36\xd7\x48\x1a\x22\x2c\xd0"
+    $ECHO -ne "\x19\x43\x51\x5e\x1c\x8c\x5f\x70\xc2\x6b\xcf\xea\xd4\x97\x61\x72"
+    $ECHO -ne "\x33\xc3\x9a\xd4\x06\xf1\x8a\x9a\xfe\x21\x83\x0b\xea\xf1\xfa\x2c"
+    $ECHO -ne "\x52\x23\x2c\xb8\xc1\xe6\xc8\x9d\x9c\x5f\x8f\xf2\x4a\x86\x76\x92"
+    $ECHO -ne "\x78\x0f\x7d\x9d\x09\x38\xce\xe1\x9a\xf3\x60\xed\x65\x0b\x1a\x68"
+    $ECHO -ne "\xa6\x52\x39\x18\x1e\x45\xe3\x5d\xe0\x7d\xfb\xc6\xcc\x44\x18\x93"
+    $ECHO -ne "\xe9\x71\xa8\x18\x0d\x74\x48\x8a\x18\x0b\x61\xbf\xe1\xa9\x0e\x4c"
+    $ECHO -ne "\xad\x1b\xaf\x1a\x37\x39\x92\x4d\xcc\x96\x87\x46\x0d\x83\x06\x33"
+    $ECHO -ne "\x53\x35\xd9\x2c\x36\x98\x28\x1c\x52\xb1\x89\x55\x56\xcc\x37\x20"
+    $ECHO -ne "\x89\x84\x0e\x3d\x27\x2f\xc6\xfa\x78\x04\xe1\xd5\xc6\x90\x49\x16"
+    $ECHO -ne "\xfe\x0a\x16\x6f\x11\x54\x42\x22\xa1\x90\x2d\x19\x91\x28\x05\xf2"
+    $ECHO -ne "\x30\x6c\x14\x16\xd6\x8a\xce\xf6\xcd\x7c\x64\x76\x42\xe9\x28\xe9"
+    $ECHO -ne "\x1c\xd1\xb8\x9e\xcd\x53\xb2\x6b\x8d\x57\x57\x2a\xb8\x59\x58\x8c"
+    $ECHO -ne "\xd3\x12\x57\xa6\xe3\x48\x70\xf5\x55\x0f\x76\xb5\x27\x08\xd1\xa0"
+    $ECHO -ne "\xf8\x60\x09\xa1\xf2\x30\x43\x4a\x30\x46\xf7\x96\x19\xe9\x3a\x44"
+    $ECHO -ne "\xc0\xd8\xa8\x51\xae\x50\x92\x81\x81\xda\x10\xd3\x18\x62\x94\xd0"
+    $ECHO -ne "\x9e\x54\x0b\x22\xcc\xd0\xfe\x0c\x36\x44\x4d\x4d\x40\x5c\xa8\x35"
+    $ECHO -ne "\xb6\x53\x9c\x36\x9c\x5a\x0e\x0e\xb0\x5c\x29\x2a\x35\x66\xaa\x3a"
+    $ECHO -ne "\xcb\x23\x7b\xbb\xc8\x60\xbc\xb4\x28\xf4\x6e\xfe\x86\xfc\x16\x85"
+    $ECHO -ne "\x0c\xe0\x1d\xcf\xfd\x12\x28\xc6\x60\xd0\xe6\x2f\x76\xf0\x1a\x5b"
+    $ECHO -ne "\xfa\xa6\xc6\xea\x58\xbb\x26\x37\x84\xdd\x85\xd5\x37\x82\x76\xd9"
+    $ECHO -ne "\x14\x7a\xca\xed\x13\x72\xc3\xe1\xb9\x69\x45\xd4\xec\x44\x94\x26"
+    $ECHO -ne "\x8e\x0b\x90\xb6\x8b\x1f\x1e\x01\x96\x5a\xb9\x51\xa6\x27\xa2\x9b"
+    $ECHO -ne "\x38\xd9\x25\x32\x9b\x54\xfc\x45\xd1\xa8\x59\x35\x1a\xb0\xb2\x1a"
+    $ECHO -ne "\xc8\x88\x15\x42\x98\x50\x99\x12\x9e\xf5\x59\xb2\x5c\xc5\xa7\x34"
+    $ECHO -ne "\x35\xca\xb3\xed\xdc\xc9\x9f\x3e\x77\x8f\x6c\xde\xc8\x41\x6a\xc5"
+    $ECHO -ne "\x24\x85\x04\xa1\x2f\xe3\x47\x8c\x47\xd4\xdb\x74\x8c\xb6\x4c\xef"
+    $ECHO -ne "\xed\xad\x9f\x86\x31\xd8\xc8\x07\xc5\x11\x1c\x39\x3a\xf8\x75\x73"
+    $ECHO -ne "\xae\x78\x7d\x1d\x36\x5b\xd1\x23\x5d\x84\x17\x5d\x4b\xac\xd3\x70"
+    $ECHO -ne "\x8a\x83\x48\x48\x83\x7b\x5c\x99\x9e\x56\xbb\xfc\x0c\x4b\x04\xcf"
+    $ECHO -ne "\x83\x5d\xf8\x31\x2c\xc4\x5c\xa1\x68\x6a\x56\xe1\x7f\xbe\xd6\x59"
+    $ECHO -ne "\x6c\x55\xb0\x63\x41\xeb\x88\x69\xb6\x9b\x50\xc4\x31\xea\xb0\xd7"
+    $ECHO -ne "\xe2\xfb\x7b\xeb\xbb\x52\xc4\x97\x23\xe9\x16\x29\x18\x50\x4d\x0e"
+    $ECHO -ne "\x68\x62\xfb\x3f\xd9\x07\xb9\x89\x4d\x58\x7c\x32\x6d\x12\x3e\x9b"
+    $ECHO -ne "\x3a\x14\xee\xac\x3c\x8d\x09\x62\x30\x8e\xe0\x86\x84\xb9\xf3\x0d"
+    $ECHO -ne "\xf8\xad\x42\xa6\xbb\x7d\xd1\xf2\xf3\xc0\xe2\x32\xc4\x40\xaa\x8a"
+    $ECHO -ne "\x2a\xe9\xa9\x45\x83\x23\xf6\x90\x05\x24\x59\x22\x84\x50\x82\xc0"
+    $ECHO -ne "\x58\x41\x42\x18\x91\x3d\xd8\x80\xb1\x26\x68\xb2\xa8\xc0\x21\x14"
+    $ECHO -ne "\x18\xdf\x3a\x86\x25\x85\x56\xab\x20\x38\xcd\xdc\x98\x6e\x07\xc4"
+    $ECHO -ne "\x6b\x16\x55\xe0\x41\xe0\x41\xda\x29\x62\x8d\xba\xce\xa2\xcb\xfc"
+    $ECHO -ne "\x70\x78\x99\xf9\x16\x0b\x5a\x0c\xc5\xad\x18\xeb\xf0\xb5\xc9\x25"
+    $ECHO -ne "\x82\x16\xe0\x5d\xc1\xc4\xc6\xf0\x84\x6a\x45\x7d\xdb\x28\x46\xab"
+    $ECHO -ne "\xef\x32\xc9\x49\x50\x51\x60\x77\x1c\xfd\x58\x9c\x01\x3b\x7a\xfa"
+    $ECHO -ne "\x49\x47\x3e\x87\x1c\x39\xa6\x6a\xa4\xb7\x39\x93\xac\xac\xb0\x39"
+    $ECHO -ne "\x2f\xbc\xab\x9b\x52\x96\x24\x46\xc1\x95\xe4\x31\x89\x37\x18\xc8"
+    $ECHO -ne "\x2c\x22\x32\x2a\x8f\xb6\x58\x77\x57\x77\x2f\x09\xd0\x7c\xed\x74"
+    $ECHO -ne "\xaa\x7c\x86\x25\x45\x0c\x43\x4d\x31\xb0\x63\x40\xcf\x86\xfe\x75"
+    $ECHO -ne "\x76\xe0\xee\x99\xb5\x71\xe2\x4e\xe5\xc1\xf9\x2e\x48\xe2\xa6\x1b"
+    $ECHO -ne "\x28\xa5\xa3\xbe\xff\x37\xd1\xdd\x66\xa2\xe8\xd3\x88\x4d\x13\xd5"
+    $ECHO -ne "\x68\x51\x27\x41\xc3\x6c\x1b\x48\x67\x6a\xdf\x25\x2a\x40\xa1\x87"
+    $ECHO -ne "\x1d\x54\xb7\xe3\x91\xc2\x6b\x5b\xb9\x8c\xd5\x10\x11\x10\x16\xab"
+    $ECHO -ne "\x6b\xbe\x65\x6b\x73\xa7\x35\xa1\x09\x60\x60\xed\x96\x39\xc9\x40"
+    $ECHO -ne "\x5d\xdc\xee\x60\x49\x0c\x68\x18\x34\xb2\x6f\x2a\x95\x14\x29\x95"
+    $ECHO -ne "\x5b\x59\xd2\x1f\x63\x2a\xbe\xfd\xae\x09\x5c\xee\x11\xb5\x29\x36"
+    $ECHO -ne "\xca\xdf\x28\x8c\x65\x42\x46\x74\x0c\x39\x68\x30\xac\x2c\x2f\xd0"
+    $ECHO -ne "\x9b\xb3\x92\x19\x90\xa1\x07\xcc\xf6\xde\x64\x5f\x6f\xd7\xb6\xcc"
+    $ECHO -ne "\xe0\x70\x0f\x0b\xd2\x0e\x77\xa1\x70\xe3\x56\x90\x4b\x28\x58\xd0"
+    $ECHO -ne "\xd1\xe1\x9d\x18\x98\xba\x6b\x36\x54\xa9\x54\x09\x63\x49\x18\x55"
+    $ECHO -ne "\x60\xba\x11\xb1\x0a\x14\x45\x1f\xae\x08\x50\x09\x33\x00\xa2\xb2"
+    $ECHO -ne "\x71\x81\x75\x89\xb7\xb9\x0c\x73\xc0\x4c\x32\x89\x72\xac\xa9\xa3"
+    $ECHO -ne "\x47\x5f\x7d\x4e\x1b\x4d\xb9\xea\x84\x45\x00\x37\x3c\xb3\x7b\xf8"
+    $ECHO -ne "\xe7\x0f\xaa\x33\x1a\x9b\xc2\x0c\x35\x8a\xd4\x04\x46\x42\xcb\xab"
+    $ECHO -ne "\xaa\xc7\xe5\xc9\x20\x6e\x21\xa6\x8c\xed\x61\x86\x42\x87\x03\x25"
+    $ECHO -ne "\xde\x2c\x4a\x85\xcb\xb4\x36\xc9\xd4\x72\x60\x62\xc2\x19\xd0\x30"
+    $ECHO -ne "\x16\x6d\x58\x61\x62\x16\xe8\xd2\x0e\xd0\xf3\xdb\x53\x37\x07\x37"
+    $ECHO -ne "\x40\xc3\xe5\x5b\x9d\x16\x45\x60\x8e\xfb\x12\xc4\x5f\x9f\xdd\xe1"
+    $ECHO -ne "\x45\x5d\x45\x36\x21\xa0\xc0\xb8\x11\x98\x0f\x64\x98\x67\x1c\x11"
+    $ECHO -ne "\xa9\xa1\x65\x10\xb9\x22\x12\x91\x10\x9b\x10\x6f\x95\x2e\x34\x91"
+    $ECHO -ne "\x64\x82\xa4\x05\x02\xfc\x4a\x9f\x9c\x4d\x6c\x8d\x67\x26\x90\x63"
+    $ECHO -ne "\x04\x12\x6f\x0e\x55\x3c\x8e\xf2\x8d\xb4\x6b\x3d\xac\xcf\x84\x2e"
+    $ECHO -ne "\x60\x0f\x40\x62\x88\x3a\xcf\xbd\xea\xad\x40\x4c\x29\xe1\xb5\xb6"
+    $ECHO -ne "\x3e\x15\x86\xd5\xbe\xad\x27\xde\x2b\x32\xef\xcf\x97\x88\x8b\x17"
+    $ECHO -ne "\x80\x43\x0e\x20\x79\x3b\x36\x73\xb8\xad\x12\x0e\x87\x59\x5f\xd3"
+    $ECHO -ne "\x3c\x8c\x84\xc8\x54\x2b\x94\xc9\x2e\x36\x8b\x32\x48\xf1\xe3\x08"
+    $ECHO -ne "\xf0\x36\xc0\xb8\xc2\xa2\xa9\xe2\x52\x02\xf1\x9b\xcb\xde\xcc\xb5"
+    $ECHO -ne "\x5a\x6c\x05\x06\x31\x44\x41\x88\xa3\x05\x04\x16\x0d\x4a\x85\x20"
+    $ECHO -ne "\x79\xda\x89\x82\x1d\x5f\x5a\x11\x88\x89\x06\x05\xf5\xf4\xed\x75"
+    $ECHO -ne "\x62\x39\x37\x69\x11\x32\x3e\x8d\xe4\x60\x62\x52\xc9\xad\x82\x9a"
+    $ECHO -ne "\x9a\x2f\x06\x41\x26\xb4\x48\x70\x39\x2b\x8a\xb1\x5a\x53\xc6\x48"
+    $ECHO -ne "\x57\x17\xf5\xd8\x5a\xc6\x19\x83\x06\x0b\x9b\x04\xb8\xf5\xaf\x23"
+    $ECHO -ne "\x45\x87\x48\x50\x6d\x16\xea\xb4\x20\xb8\x49\x92\x6b\x0c\x76\x14"
+    $ECHO -ne "\x48\x53\xa1\x29\x74\xf6\xd7\x49\x44\x39\xba\xbd\x63\xa6\xf2\x81"
+    $ECHO -ne "\x8f\x5b\x5e\x46\x0a\x34\x95\x31\xc0\xdd\x60\x50\xd6\x0a\xa6\x29"
+    $ECHO -ne "\x3d\x36\x3a\xc7\xb8\xcf\x25\x5e\xf7\x82\x55\x88\xc2\x8b\x30\xd2"
+    $ECHO -ne "\x97\x90\x49\x94\xde\xe5\xaa\xeb\x42\x8c\x94\x2a\xa0\x0d\x9b\xb5"
+    $ECHO -ne "\x59\xbe\xcb\x35\xa3\x37\x54\x76\x35\x98\xcb\x1f\x13\x3f\x4a\xd1"
+    $ECHO -ne "\x45\x87\x67\xed\x66\x02\x06\x49\x1c\x59\x51\x1f\x4d\x85\x03\x46"
+    $ECHO -ne "\x65\x86\x4e\x4c\x6a\xd2\x24\x31\x5b\x6a\x3b\x19\x49\xe1\x83\x14"
+    $ECHO -ne "\xc1\xf0\x56\x61\x93\x8b\x33\x13\x54\x6f\x78\x4c\xa0\x85\xf0\xb3"
+    $ECHO -ne "\x17\xaa\xf2\x67\x02\x0c\x31\xed\x5e\x98\x02\x28\xc4\xe5\x87\xa2"
+    $ECHO -ne "\x70\xd1\xd5\x9c\xf8\xec\x19\x88\xa9\x0d\x0e\x4a\xe3\x47\x83\x6e"
+    $ECHO -ne "\xf7\x70\x3e\xa4\xa9\xc0\x4c\xfa\x2b\x3b\xd7\x6f\x96\xc3\x6d\x6d"
+    $ECHO -ne "\x71\x2a\x8d\x62\xf1\xd3\xdd\xb0\x33\xd4\x67\x19\x0d\x30\x85\x3a"
+    $ECHO -ne "\x06\x04\x85\x00\x48\x5d\x53\x35\xa0\x31\x56\x84\x82\xa5\xac\x22"
+    $ECHO -ne "\x02\x44\xaf\x6d\xc0\x61\x59\x23\x96\x72\x5a\x81\x9e\x0c\xe5\x79"
+    $ECHO -ne "\xda\xd0\x42\x5c\x89\xa5\x00\xec\x56\x41\x64\x8a\x11\x60\x79\xb1"
+    $ECHO -ne "\xed\x55\x16\x54\xe6\x51\x03\x34\x14\x60\x31\xd2\xf0\x0b\xce\xf2"
+    $ECHO -ne "\x4e\x4c\x45\x8c\xeb\x2a\x82\x3a\xa8\x52\xce\x8f\x4e\xf1\x89\xea"
+    $ECHO -ne "\x44\x91\x66\xdd\x6b\x49\xa3\x83\x0b\x19\x0e\x66\x5f\x02\x22\x58"
+    $ECHO -ne "\xe7\xc0\xa8\xce\x55\x48\xa6\x04\xf3\x03\xac\x62\xb2\xc0\xaa\xa0"
+    $ECHO -ne "\x09\xae\x5b\x96\xd0\xdd\xa9\x1f\xfb\x2d\x3d\xf5\x02\xe1\x86\x02"
+    $ECHO -ne "\x3e\xda\xd0\x5d\xba\x16\x39\xcd\x75\xa2\x47\x26\x74\x25\xa8\x5e"
+    $ECHO -ne "\xf3\x36\x0c\x37\x19\x17\x06\x66\xd0\x0b\x42\x41\x0a\xa0\xde\x93"
+    $ECHO -ne "\xd7\xb4\x9f\xfb\xc7\x4f\x65\x54\xda\xb8\x8b\x23\xde\x9c\x57\xcf"
+    $ECHO -ne "\x2d\x2a\x12\xda\xcc\xf6\x73\x83\x02\x4c\x0e\x42\x88\xda\x27\xb9"
+    $ECHO -ne "\xcb\x04\xb6\x07\x26\x78\xa1\xa1\x09\xa3\x6a\x86\xbd\x9d\xd4\xf9"
+    $ECHO -ne "\xc0\x81\xa6\x49\xa9\x72\xeb\x56\xbd\xf9\xea\x89\x4f\xae\x72\x28"
+    $ECHO -ne "\xb6\x57\x35\xbe\x94\xad\xc0\xff\x1e\xf2\x35\x24\xa0\x45\xd5\x09"
+    $ECHO -ne "\xc0\xe0\x10\xd0\x17\x90\xe2\xff\x8b\xb9\x22\x9c\x28\x48\x09\x68"
+    $ECHO -ne "\xc3\x18\x00"
+}
+
+tar2_bz2()
+{
+    $ECHO -ne "\x42\x5a\x68\x39\x31\x41\x59\x26\x53\x59\x16\x08\xfd\x60\x00\x00"
+    $ECHO -ne "\x01\xff\x80\x48\x80\x00\xa0\x40\x03\xff\xc0\x72\x00\x89\x40\xff"
+    $ECHO -ne "\xe7\xdf\xc0\x20\x00\x92\x11\x53\xf5\x13\xd3\x53\x7a\x41\xa8\xf2"
+    $ECHO -ne "\x9a\x60\x21\x9a\x40\xf4\x9b\xd2\x0d\x09\xa5\x3c\xa6\x4f\x44\xf5"
+    $ECHO -ne "\x34\x34\xd1\xb5\x01\xa0\x3d\x41\xa4\xe1\xeb\x4c\x5a\x01\x47\x3b"
+    $ECHO -ne "\xd1\x67\x1a\x4c\x3b\x21\x84\x23\x2c\x5c\xf7\xe0\xbd\x2a\xa4\xea"
+    $ECHO -ne "\xdc\xdb\x71\x80\x26\x98\x21\x0e\x76\x21\x30\xce\xe4\xad\x8c\xb5"
+    $ECHO -ne "\x68\x62\x35\xa1\xfd\x8e\x7b\x51\x70\x96\xb1\x2c\xa2\x99\x6c\xa1"
+    $ECHO -ne "\xc2\xcd\xea\xa7\x5e\x6b\x91\x4f\x73\x96\xe4\x48\x3c\xe7\x8c\x0f"
+    $ECHO -ne "\x03\x64\x5b\x7a\x43\xc1\x68\x86\x41\x83\x46\x0b\xba\xaa\x6a\x9b"
+    $ECHO -ne "\x59\x34\xf1\x1c\x08\x69\x1d\x41\xfb\x4a\x96\x1b\x14\x9e\x32\x89"
+    $ECHO -ne "\x69\x5f\x63\x9a\x22\xe4\x96\x34\xff\x12\x20\xd0\x25\x70\xdc\x5d"
+    $ECHO -ne "\xc9\x14\xe1\x42\x40\x58\x23\xf5\x80"
+}
+
+# NB: tar emits "tar: short read" on stderr because these test tars are
+# also lacking proper terminating zeroed blocks. But exitcode is 0.
+# This is intended.
+
+export TZ=UTC-1
+
+# Case 1: long name, with path in prefix field
+res1='-rw-r--r-- fm3/users      9869 2007-03-12 10:44:54 VirtualBox-1.5.6_OSE/src/libs/xpcom18a4/ipc/ipcd/extensions/transmngr/public/ipcITransactionService.idl'
+t=`tar1_bz2 | bunzip2 | busybox tar tvf -`
+test x"$res1" = x"$t"
+t=`tar1_bz2 | bunzip2 | busybox tar tv`
+test x"$res1" = x"$t"
+
+# Case 2: long dir name, with ENTIRE path in prefix field (name = "")
+res2='drwxr-xr-x fm3/users         0 2008-02-19 16:33:20 VirtualBox-1.5.6_OSE/src/VBox/Additions/linux/x11include/4.3/programs/Xserver/hw/xfree86/xf24_32bpp/'
+t=`tar2_bz2 | bunzip2 | busybox tar tvf -`
+test x"$res2" = x"$t"
+t=`tar2_bz2 | bunzip2 | busybox tar tv`
+test x"$res2" = x"$t"
diff --git a/testsuite/taskset.tests b/testsuite/taskset.tests
new file mode 100755 (executable)
index 0000000..4599364
--- /dev/null
@@ -0,0 +1,17 @@
+#!/bin/sh
+
+# Copyright 2006 Bernhard Reutner-Fischer
+# Licensed under GPL v2 or later, see file LICENSE for details.
+
+. testing.sh
+a="taskset"
+
+# testing "test name"              "opts" "expected result" "file inp" "stdin"
+testing "taskset (get from pid 1)" "$a -p 1 >/dev/null;echo \$?" "0\n" "" ""
+testing "taskset (invalid pid)"    "$a -p 0 >/dev/null 2>&1;echo \$?" "1\n" "" ""
+testing "taskset (set_aff, needs CAP_SYS_NICE)" \
+                                   "$a 0x1 $SHELL -c $a\ -p\ \$$\|grep\ \"current\ affinity\ mask:\ 1\" >/dev/null;echo \$?" \
+                                                               "0\n" "" ""
+
+unset a
+exit $FAILCOUNT
diff --git a/testsuite/tee/tee-appends-input b/testsuite/tee/tee-appends-input
new file mode 100644 (file)
index 0000000..cff20bf
--- /dev/null
@@ -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 (file)
index 0000000..26e2173
--- /dev/null
@@ -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/test.tests b/testsuite/test.tests
new file mode 100755 (executable)
index 0000000..b7c84d9
--- /dev/null
@@ -0,0 +1,79 @@
+#!/bin/sh
+
+# Copyright 2007 by Denys Vlasenko <vda.linux@googlemail.com>
+# Licensed under GPL v2, see file LICENSE for details.
+
+. testing.sh
+
+# testing "test name" "command" "expected result" "file input" "stdin"
+#   file input will be file called "input"
+#   test can create a file "actual" instead of writing to stdout
+
+# Need to call 'busybox test', otherwise shell builtin is used
+
+testing "test: should be false (1)" \
+       "busybox test; echo \$?" \
+       "1\n" \
+       "" ""
+
+testing "test '': should be false (1)" \
+       "busybox test ''; echo \$?" \
+       "1\n" \
+       "" ""
+
+testing "test !: should be true (0)" \
+       "busybox test !; echo \$?" \
+       "0\n" \
+       "" ""
+
+testing "test a: should be true (0)" \
+       "busybox test a; echo \$?" \
+       "0\n" \
+       "" ""
+
+testing "test --help: should be true (0)" \
+       "busybox test --help; echo \$?" \
+       "0\n" \
+       "" ""
+
+testing "test -f: should be true (0)" \
+       "busybox test -f; echo \$?" \
+       "0\n" \
+       "" ""
+
+testing "test ! -f: should be false (1)" \
+       "busybox test ! -f; echo \$?" \
+       "1\n" \
+       "" ""
+
+testing "test a = a: should be true (0)" \
+       "busybox test a = a; echo \$?" \
+       "0\n" \
+       "" ""
+
+testing "test -lt = -gt: should be false (1)" \
+       "busybox test -lt = -gt; echo \$?" \
+       "1\n" \
+       "" ""
+
+testing "test a -a !: should be true (0)" \
+       "busybox test a -a !; echo \$?" \
+       "0\n" \
+       "" ""
+
+testing "test -f = a -o b: should be true (0)" \
+       "busybox test -f = a -o b; echo \$?" \
+       "0\n" \
+       "" ""
+
+testing "test ! a = b -a ! c = c: should be false (1)" \
+       "busybox test ! a = b -a ! c = c; echo \$?" \
+       "1\n" \
+       "" ""
+
+testing "test ! a = b -a ! c = d: should be true (0)" \
+       "busybox test ! a = b -a ! c = d; echo \$?" \
+       "0\n" \
+       "" ""
+
+exit $FAILCOUNT
diff --git a/testsuite/testing.sh b/testsuite/testing.sh
new file mode 100755 (executable)
index 0000000..a57c4d6
--- /dev/null
@@ -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 "optional"
+# and a couple more...
+
+# 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 "optional")
+#
+# The "testing" function takes five arguments:
+#      $1) Test description
+#      $2) Command(s) to run. May have pipes, redirects, etc
+#      $3) Expected result on stdout
+#      $4) Data to be written to file "input"
+#      $5) Data to be written to stdin
+#
+# The exit value of testing is the exit value of $2 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:
+#   optional 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:" | grep ":$1:"`
+  # Not set?
+  if [ -z "$1" ] || [ -z "$OPTIONFLAGS" ] || [ ${#option} -ne 0 ]
+  then
+    SKIP=
+    return
+  fi
+  SKIP=1
+}
+
+# The testing function
+
+testing()
+{
+  NAME="$1"
+  [ -n "$1" ] || NAME="$2"
+
+  if [ $# -ne 5 ]
+  then
+    echo "Test $NAME has wrong number of arguments (must be 5) ($# $*)" >&2
+    exit 1
+  fi
+
+  [ -z "$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=$?
+
+  if cmp expected actual >/dev/null 2>/dev/null
+  then
+    echo "PASS: $NAME"
+  else
+    FAILCOUNT=$(($FAILCOUNT + 1))
+    echo "FAIL: $NAME"
+    [ -z "$VERBOSE" ] || diff -u expected actual
+  fi
+  rm -f input expected actual
+
+  [ -z "$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.
+
+mkchroot()
+{
+  [ $# -lt 2 ] && return
+
+  $ECHO -n .
+
+  dest=$1
+  shift
+  for i in "$@"
+  do
+    #bashism: [ "${i:0:1}" == "/" ] || i=$(which $i)
+    i=$(which $i) # no-op for /bin/prog
+    [ -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.
+
+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 (file)
index 0000000..4b49354
--- /dev/null
@@ -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 (file)
index 0000000..8852592
--- /dev/null
@@ -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 (file)
index 0000000..a869ec2
--- /dev/null
@@ -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.tests b/testsuite/tr.tests
new file mode 100644 (file)
index 0000000..f91cc1f
--- /dev/null
@@ -0,0 +1,31 @@
+#!/bin/sh
+
+# Copyright 2009 by Denys Vlasenko <vda.linux@googlemail.com>
+# Licensed under GPL v2, see file LICENSE for details.
+
+. testing.sh
+
+# testing "description" "arguments" "result" "infile" "stdin"
+
+testing "tr does not treat [] in [a-z] as special" \
+       "tr '[q-z]' '_Q-Z+'" \
+       "_QWe+" "" "[qwe]"
+
+testing "tr understands 0-9A-F" \
+       "tr -cd '[0-9A-F]'" \
+       "19AF" "" "19AFH\n"
+
+testing "tr understands [:xdigit:]" \
+       "tr -cd '[:xdigit:]'" \
+       "19AF" "" "19AFH\n"
+
+testing "tr does not stop after [:digit:]" \
+       "tr '[:digit:]y-z' 111111111123" \
+       "111abcx23\n" "" "789abcxyz\n"
+
+testing "tr has correct xdigit sequence" \
+       "tr '[:xdigit:]Gg' 1111111151242222333330xX" \
+       "#1111111151242222x333330X\n" "" \
+       "#0123456789ABCDEFGabcdefg\n"
+
+exit $FAILCOUNT
diff --git a/testsuite/tr/tr-d-alnum-works b/testsuite/tr/tr-d-alnum-works
new file mode 100644 (file)
index 0000000..d440f8f
--- /dev/null
@@ -0,0 +1,4 @@
+echo testing | tr -d '[[:alnum:]]' > logfile.gnu
+echo testing | busybox tr -d '[[:alnum:]]' > logfile.bb
+
+diff -u logfile.gnu logfile.bb
diff --git a/testsuite/tr/tr-d-works b/testsuite/tr/tr-d-works
new file mode 100644 (file)
index 0000000..a86bfbd
--- /dev/null
@@ -0,0 +1,4 @@
+echo testing | tr -d aeiou > logfile.gnu
+echo testing | busybox tr -d aeiou > logfile.bb
+
+diff -u logfile.gnu logfile.bb
diff --git a/testsuite/tr/tr-non-gnu b/testsuite/tr/tr-non-gnu
new file mode 100644 (file)
index 0000000..ffa6951
--- /dev/null
@@ -0,0 +1 @@
+echo fdhrnzvfu bffvsentr | busybox tr '[a-z]' '[n-z][a-m]'
diff --git a/testsuite/tr/tr-rejects-wrong-class b/testsuite/tr/tr-rejects-wrong-class
new file mode 100644 (file)
index 0000000..9753936
--- /dev/null
@@ -0,0 +1,19 @@
+echo t12esting | tr -d '[[:alpha:]]' > logfile.gnu
+echo t12esting | tr -d '[:alpha:]'  >> logfile.gnu
+echo t12esting | tr -d '[[:alpha:]' >> logfile.gnu
+echo t12esting | tr -d '[[:alpha:' >> logfile.gnu
+echo t12esting | tr -d '[[:alpha' >> logfile.gnu
+echo t12esting | tr -d '[:alpha:]' >> logfile.gnu
+echo t12esting | tr -d '[:alpha:' >> logfile.gnu
+echo t12esting | tr -d '[:alpha' >> logfile.gnu
+
+echo t12esting | busybox tr -d '[[:alpha:]]' > logfile.bb
+echo t12esting | busybox tr -d '[:alpha:]'  >> logfile.bb
+echo t12esting | busybox tr -d '[[:alpha:]' >> logfile.bb
+echo t12esting | busybox tr -d '[[:alpha:' >> logfile.bb
+echo t12esting | busybox tr -d '[[:alpha' >> logfile.bb
+echo t12esting | busybox tr -d '[:alpha:]' >> logfile.bb
+echo t12esting | busybox tr -d '[:alpha:' >> logfile.bb
+echo t12esting | busybox tr -d '[:alpha' >> logfile.bb
+
+diff -u logfile.gnu logfile.bb
diff --git a/testsuite/tr/tr-works b/testsuite/tr/tr-works
new file mode 100644 (file)
index 0000000..2c0a9d1
--- /dev/null
@@ -0,0 +1,26 @@
+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
+       run_tr "        .,:"    '[:punct:]'     12
+       run_tr "        .,:"    '[:cntrl:]'     12
+}
+
+bb=        tr_test > logfile.gnu
+bb=busybox tr_test > logfile.bb
+diff -u logfile.gnu logfile.bb
diff --git a/testsuite/true/true-is-silent b/testsuite/true/true-is-silent
new file mode 100644 (file)
index 0000000..1d1bdb2
--- /dev/null
@@ -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 (file)
index 0000000..cdf2d55
--- /dev/null
@@ -0,0 +1 @@
+busybox true
diff --git a/testsuite/umlwrapper.sh b/testsuite/umlwrapper.sh
new file mode 100755 (executable)
index 0000000..e55e4db
--- /dev/null
@@ -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/unexpand.tests b/testsuite/unexpand.tests
new file mode 100755 (executable)
index 0000000..e7f42c5
--- /dev/null
@@ -0,0 +1,30 @@
+#!/bin/sh
+# Copyright 2008 by Denys Vlasenko
+# Licensed under GPL v2, see file LICENSE for details.
+
+. testing.sh
+
+# testing "test name" "options" "expected result" "file input" "stdin"
+
+testing "unexpand case 1" "unexpand" \
+       "\t12345678\n" "" "        12345678\n" \
+
+testing "unexpand case 2" "unexpand" \
+       "\t 12345678\n" "" "         12345678\n" \
+
+testing "unexpand case 3" "unexpand" \
+       "\t  12345678\n" "" "          12345678\n" \
+
+testing "unexpand case 4" "unexpand" \
+       "\t12345678\n" "" "       \t12345678\n" \
+
+testing "unexpand case 5" "unexpand" \
+       "\t12345678\n" "" "      \t12345678\n" \
+
+testing "unexpand case 6" "unexpand" \
+       "\t12345678\n" "" "     \t12345678\n" \
+
+testing "unexpand case 7" "unexpand" \
+       "123\t 45678\n" "" "123 \t 45678\n" \
+
+exit $FAILCOUNT
diff --git a/testsuite/unexpand/unexpand-works-like-GNU b/testsuite/unexpand/unexpand-works-like-GNU
new file mode 100644 (file)
index 0000000..a525836
--- /dev/null
@@ -0,0 +1,52 @@
+rm -f foo bar
+echo "       y" | unexpand ../../busybox > foo
+echo "       y" | busybox unexpand ../../busybox > bar
+set +e
+test ! -f foo -a -f bar
+if [ $? = 0 ] ; then
+       set -e
+       diff -q foo bar
+fi
+rm -f foo bar
+echo "        y" | unexpand ../../busybox > foo
+echo "        y" | busybox unexpand ../../busybox > bar
+set +e
+test ! -f foo -a -f bar
+if [ $? = 0 ] ; then
+       set -e
+       diff -q foo bar
+fi
+echo "       y       y" | unexpand ../../busybox > foo
+echo "       y       y" | busybox unexpand ../../busybox > bar
+set +e
+test ! -f foo -a -f bar
+if [ $? = 0 ] ; then
+       set -e
+       diff -q foo bar
+fi
+rm -f foo bar
+echo "        y        y" | unexpand ../../busybox > foo
+echo "        y        y" | busybox unexpand ../../busybox > bar
+set +e
+test ! -f foo -a -f bar
+if [ $? = 0 ] ; then
+       set -e
+       diff -q foo bar
+fi
+echo "       y       y" | unexpand -a ../../busybox > foo
+echo "       y       y" | busybox unexpand -a ../../busybox > bar
+set +e
+test ! -f foo -a -f bar
+if [ $? = 0 ] ; then
+       set -e
+       diff -q foo bar
+fi
+rm -f foo bar
+echo "        y        y" | unexpand -a ../../busybox > foo
+echo "        y        y" | busybox unexpand -a ../../busybox > bar
+set +e
+test ! -f foo -a -f bar
+if [ $? = 0 ] ; then
+       set -e
+       diff -q foo bar
+fi
diff --git a/testsuite/uniq.tests b/testsuite/uniq.tests
new file mode 100755 (executable)
index 0000000..8961d66
--- /dev/null
@@ -0,0 +1,87 @@
+#!/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
+#-w max chars
+
+# 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
+"
+testing "uniq -w (compare max characters)" "uniq -w 2" \
+"cc1
+" "" \
+"cc1
+cc2
+cc3
+"
+
+testing "uniq -s -w (skip fields and compare max chars)" \
+"uniq -s 2 -w 2" \
+"aaccaa
+" "" \
+"aaccaa
+aaccbb
+bbccaa
+"
+
+# -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 (executable)
index 0000000..975079d
--- /dev/null
@@ -0,0 +1,38 @@
+#!/bin/sh
+
+# Tests for unzip.
+# Copyright 2006 Rob Landley <rob@landley.net>
+# Copyright 2006 Glenn McGrath
+# 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 (file)
index 0000000..80e5787
--- /dev/null
@@ -0,0 +1,2 @@
+busybox uptime
+
diff --git a/testsuite/uuencode.tests b/testsuite/uuencode.tests
new file mode 100755 (executable)
index 0000000..cb658db
--- /dev/null
@@ -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 (file)
index 0000000..7083645
--- /dev/null
@@ -0,0 +1,2 @@
+# 1 line, 4 words, 20 chars.
+test "`echo i\'m a little teapot | busybox wc | sed 's/  */ /g' | sed 's/^ //'`" = '1 4 20'
diff --git a/testsuite/wc/wc-counts-characters b/testsuite/wc/wc-counts-characters
new file mode 100644 (file)
index 0000000..7558646
--- /dev/null
@@ -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 (file)
index 0000000..5be6ed0
--- /dev/null
@@ -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 (file)
index 0000000..331650e
--- /dev/null
@@ -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 (file)
index 0000000..78831fc
--- /dev/null
@@ -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 (file)
index 0000000..fdb5d47
--- /dev/null
@@ -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 (file)
index 0000000..5b59183
--- /dev/null
@@ -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 (file)
index 0000000..7be9a80
--- /dev/null
@@ -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 (file)
index 0000000..9b4d095
--- /dev/null
@@ -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 (file)
index 0000000..63ceb9f
--- /dev/null
@@ -0,0 +1,4 @@
+BUSYBOX=$(type -p busybox)
+SAVED_PATH=$PATH
+unset PATH
+$BUSYBOX which ls
diff --git a/testsuite/xargs.tests b/testsuite/xargs.tests
new file mode 100755 (executable)
index 0000000..3652449
--- /dev/null
@@ -0,0 +1,29 @@
+#!/bin/sh
+# Copyright 2008 by Denys Vlasenko
+# Licensed under GPL v2, see file LICENSE for details.
+
+. testing.sh
+
+# testing "test name" "command" "expected result" "file input" "stdin"
+
+testing "xargs -E _ stops on underscore" \
+       "xargs -E _" \
+       "a\n" \
+       "" "a\n_\nb\n"
+
+testing "xargs -E ''" \
+       "xargs -E ''" \
+       "a _ b\n" \
+       "" "a\n_\nb\n"
+
+testing "xargs -e without param" \
+       "xargs -e" \
+       "a _ b\n" \
+       "" "a\n_\nb\n"
+
+testing "xargs does not stop on underscore ('new' GNU behavior)" \
+       "xargs" \
+       "a _ b\n" \
+       "" "a\n_\nb\n"
+
+exit $FAILCOUNT
diff --git a/testsuite/xargs/xargs-works b/testsuite/xargs/xargs-works
new file mode 100644 (file)
index 0000000..c95869e
--- /dev/null
@@ -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 (file)
index 0000000..e5c053f
--- /dev/null
@@ -0,0 +1,873 @@
+#
+# For a description of the syntax of this configuration file,
+# see scripts/kbuild/config-language.txt.
+#
+
+menu "Linux System Utilities"
+
+config ACPID
+       bool "acpid"
+       default n
+       help
+         acpid listens to ACPI events coming either in textual form from
+         /proc/acpi/event (though it is marked deprecated it is still widely
+         used and _is_ a standard) or in binary form from specified evdevs
+         (just use /dev/input/event*).
+
+         It parses the event to retrieve ACTION and a possible PARAMETER.
+         It then spawns /etc/acpi/<ACTION>[/<PARAMETER>] either via run-parts
+         (if the resulting path is a directory) or directly as an executable.
+
+         N.B. acpid relies on run-parts so have the latter installed.
+
+config FEATURE_ACPID_COMPAT
+       bool "Accept and ignore redundant options"
+       default n
+       depends on ACPID
+       help
+         Accept and ignore compatibility options -g -m -s -S -v.
+
+config BLKID
+       bool "blkid"
+       default n
+       select VOLUMEID
+       help
+         Lists labels and UUIDs of all filesystems.
+         WARNING:
+         With all submodules selected, it will add ~8k to busybox.
+
+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 FINDFS
+       bool "findfs"
+       default n
+       select VOLUMEID
+       help
+         Prints the name of a filesystem with given label or UUID.
+         WARNING:
+         With all submodules selected, it will add ~8k to busybox.
+
+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 MKFS_VFAT
+       bool "mkfs_vfat"
+       default n
+       help
+         Utility to create FAT32 filesystems.
+
+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 FEATURE_HEXDUMP_REVERSE
+       bool "Support -R, reverse of 'hexdump -Cv'"
+       default n
+       depends on HEXDUMP
+       help
+         The hexdump utility is used to display binary data in an ascii
+         readable way. This option creates binary data from an ascii input.
+         NB: this option is non-standard. It's unwise to use it in scripts
+         aimed to be portable.
+
+config HD
+       bool "hd"
+       default n
+       select HEXDUMP
+       help
+         hd is an alias to hexdump -C.
+
+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.
+
+         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 for dynamically creating device
+         nodes in the /dev directory.
+
+         For more information, please see docs/mdev.txt
+
+config FEATURE_MDEV_CONF
+       bool "Support /etc/mdev.conf"
+       default n
+       depends on MDEV
+       help
+         Add support for the mdev config file to control ownership and
+         permissions of the device nodes.
+
+         For more information, please see docs/mdev.txt
+
+config FEATURE_MDEV_RENAME
+       bool "Support subdirs/symlinks"
+       default n
+       depends on FEATURE_MDEV_CONF
+       help
+         Add support for renaming devices and creating symlinks.
+
+         For more information, please see docs/mdev.txt
+
+config FEATURE_MDEV_RENAME_REGEXP
+       bool "Support regular expressions substitutions when renaming device"
+       default n
+       depends on FEATURE_MDEV_RENAME
+       help
+         Add support for regular expressions substitutions when renaming
+         device.
+
+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 for
+         executing commands when devices are created/removed.
+
+         For more information, please see docs/mdev.txt
+
+config FEATURE_MDEV_LOAD_FIRMWARE
+       bool "Support loading of firmwares"
+       default n
+       depends on MDEV
+       help
+         Some devices need to load firmware before they can be usable.
+
+         These devices will request userspace look up the files in
+         /lib/firmware/ and if it exists, send it to the kernel for
+         loading into the hardware.
+
+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 || TOP
+       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 VOLUMEID
+       bool #No description makes it a hidden option
+       default n
+
+config FEATURE_VOLUMEID_EXT
+       bool "Ext filesystem"
+       default n
+       depends on VOLUMEID
+       help
+         TODO
+
+config FEATURE_VOLUMEID_REISERFS
+       bool "Reiser filesystem"
+       default n
+       depends on VOLUMEID
+       help
+         TODO
+
+config FEATURE_VOLUMEID_FAT
+       bool "fat filesystem"
+       default n
+       depends on VOLUMEID
+       help
+         TODO
+
+config FEATURE_VOLUMEID_HFS
+       bool "hfs filesystem"
+       default n
+       depends on VOLUMEID
+       help
+         TODO
+
+config FEATURE_VOLUMEID_JFS
+       bool "jfs filesystem"
+       default n
+       depends on VOLUMEID
+       help
+         TODO
+
+### config FEATURE_VOLUMEID_UFS
+###    bool "ufs filesystem"
+###    default n
+###    depends on VOLUMEID
+###    help
+###      TODO
+
+config FEATURE_VOLUMEID_XFS
+       bool "xfs filesystem"
+       default n
+       depends on VOLUMEID
+       help
+         TODO
+
+config FEATURE_VOLUMEID_NTFS
+       bool "ntfs filesystem"
+       default n
+       depends on VOLUMEID
+       help
+         TODO
+
+config FEATURE_VOLUMEID_ISO9660
+       bool "iso9660 filesystem"
+       default n
+       depends on VOLUMEID
+       help
+         TODO
+
+config FEATURE_VOLUMEID_UDF
+       bool "udf filesystem"
+       default n
+       depends on VOLUMEID
+       help
+         TODO
+
+config FEATURE_VOLUMEID_LUKS
+       bool "luks filesystem"
+       default n
+       depends on VOLUMEID
+       help
+         TODO
+
+config FEATURE_VOLUMEID_LINUXSWAP
+       bool "linux swap filesystem"
+       default n
+       depends on VOLUMEID
+       help
+         TODO
+
+### config FEATURE_VOLUMEID_LVM
+###    bool "lvm"
+###    default n
+###    depends on VOLUMEID
+###    help
+###      TODO
+
+config FEATURE_VOLUMEID_CRAMFS
+       bool "cramfs filesystem"
+       default n
+       depends on VOLUMEID
+       help
+         TODO
+
+### config FEATURE_VOLUMEID_HPFS
+###    bool "hpfs filesystem"
+###    default n
+###    depends on VOLUMEID
+###    help
+###      TODO
+
+config FEATURE_VOLUMEID_ROMFS
+       bool "romfs filesystem"
+       default n
+       depends on VOLUMEID
+       help
+         TODO
+
+config FEATURE_VOLUMEID_SYSV
+       bool "sysv filesystem"
+       default n
+       depends on VOLUMEID
+       help
+         TODO
+
+### config FEATURE_VOLUMEID_MINIX
+###    bool "minix filesystem"
+###    default n
+###    depends on VOLUMEID
+###    help
+###      TODO
+
+### These only detect partition tables - not used (yet?)
+### config FEATURE_VOLUMEID_MAC
+###    bool "mac filesystem"
+###    default n
+###    depends on VOLUMEID
+###    help
+###      TODO
+###
+### config FEATURE_VOLUMEID_MSDOS
+###    bool "msdos filesystem"
+###    default n
+###    depends on VOLUMEID
+###    help
+###      TODO
+
+config FEATURE_VOLUMEID_OCFS2
+       bool "ocfs2 filesystem"
+       default n
+       depends on VOLUMEID
+       help
+         TODO
+
+### config FEATURE_VOLUMEID_HIGHPOINTRAID
+###    bool "highpoint raid"
+###    default n
+###    depends on VOLUMEID
+###    help
+###      TODO
+
+### config FEATURE_VOLUMEID_ISWRAID
+###    bool "intel raid"
+###    default n
+###    depends on VOLUMEID
+###    help
+###      TODO
+
+### config FEATURE_VOLUMEID_LSIRAID
+###    bool "lsi raid"
+###    default n
+###    depends on VOLUMEID
+###    help
+###      TODO
+
+### config FEATURE_VOLUMEID_VIARAID
+###    bool "via raid"
+###    default n
+###    depends on VOLUMEID
+###    help
+###      TODO
+
+### config FEATURE_VOLUMEID_SILICONRAID
+###    bool "silicon raid"
+###    default n
+###    depends on VOLUMEID
+###    help
+###      TODO
+
+### config FEATURE_VOLUMEID_NVIDIARAID
+###    bool "nvidia raid"
+###    default n
+###    depends on VOLUMEID
+###    help
+###      TODO
+
+### config FEATURE_VOLUMEID_PROMISERAID
+###    bool "promise raid"
+###    default n
+###    depends on VOLUMEID
+###    help
+###      TODO
+
+config FEATURE_VOLUMEID_LINUXRAID
+       bool "linuxraid"
+       default n
+       depends on VOLUMEID
+       help
+         TODO
+
+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_FAKE
+       bool "Support option -f"
+       default n
+       depends on MOUNT
+       help
+         Enable support for faking a file system mount.
+
+config FEATURE_MOUNT_VERBOSE
+       bool "Support option -v"
+       default n
+       depends on MOUNT
+       help
+         Enable multi-level -v[vv...] verbose messages. Useful if you
+         debug mount problems and want to see what is exactly passed
+         to the kernel.
+
+config FEATURE_MOUNT_HELPERS
+       bool "Support mount helpers"
+       default n
+       depends on MOUNT
+       help
+         Enable mounting of virtual file systems via external helpers.
+         E.g. "mount obexfs#-b00.11.22.33.44.55 /mnt" will in effect call
+         "obexfs -b00.11.22.33.44.55 /mnt"
+         Also "mount -t sometype [-o opts] fs /mnt" will try
+         "sometype [-o opts] fs /mnt" if simple mount syscall fails.
+         The idea is to use such virtual filesystems in /etc/fstab.
+
+config FEATURE_MOUNT_LABEL
+       bool "Support specifiying devices by label or UUID"
+       default n
+       depends on MOUNT
+       select VOLUMEID
+       help
+         This allows for specifying a device by label or uuid, rather than by
+         name. This feature utilizes the same functionality as blkid/findfs.
+
+config FEATURE_MOUNT_NFS
+       bool "Support mounting NFS file systems"
+       default n
+       depends on MOUNT
+       select 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 RDEV
+       bool "rdev"
+       default n
+       help
+         Print the device node associated with the filesystem mounted at '/'.
+
+config READPROFILE
+       bool "readprofile"
+       default n
+       help
+         This allows you to parse /proc/profile for basic profiling.
+
+config RTCWAKE
+       bool "rtcwake"
+       default n
+       help
+         Enter a system sleep state until specified wakeup time.
+
+config SCRIPT
+       bool "script"
+       default n
+       help
+         The script makes typescript of terminal session.
+
+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 FEATURE_SWAPON_PRI
+       bool "Support priority option -p"
+       default n
+       depends on SWAPONOFF
+       help
+         Enable support for setting swap device priority in swapon.
+
+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 "Support option -a"
+       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
+       select FEATURE_MOUNT_FAKE
+       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 (file)
index 0000000..ac071c6
--- /dev/null
@@ -0,0 +1,39 @@
+# 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_ACPID)             += acpid.o
+lib-$(CONFIG_BLKID)             += blkid.o
+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_FINDFS)            += findfs.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_MKFS_VFAT)         += mkfs_vfat.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_RDEV)              += rdev.o
+lib-$(CONFIG_READPROFILE)       += readprofile.o
+lib-$(CONFIG_RTCWAKE)           += rtcwake.o
+lib-$(CONFIG_SCRIPT)            += script.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/acpid.c b/util-linux/acpid.c
new file mode 100644 (file)
index 0000000..49ea52d
--- /dev/null
@@ -0,0 +1,167 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * simple ACPI events listener
+ *
+ * Copyright (C) 2008 by Vladimir Dronnikov <dronnikov@gmail.com>
+ *
+ * Licensed under GPLv2, see file LICENSE in this tarball for details.
+ */
+#include "libbb.h"
+
+#include <linux/input.h>
+#ifndef SW_RFKILL_ALL
+# define SW_RFKILL_ALL 3
+#endif
+
+/*
+ * acpid listens to ACPI events coming either in textual form
+ * from /proc/acpi/event (though it is marked deprecated,
+ * it is still widely used and _is_ a standard) or in binary form
+ * from specified evdevs (just use /dev/input/event*).
+ * It parses the event to retrieve ACTION and a possible PARAMETER.
+ * It then spawns /etc/acpi/<ACTION>[/<PARAMETER>] either via run-parts
+ * (if the resulting path is a directory) or directly.
+ * If the resulting path does not exist it logs it via perror
+ * and continues listening.
+ */
+
+static void process_event(const char *event)
+{
+       struct stat st;
+       char *handler = xasprintf("./%s", event);
+       const char *args[] = { "run-parts", handler, NULL };
+
+       // debug info
+       if (option_mask32 & 8) { // -d
+               bb_error_msg("%s", event);
+       }
+
+       // spawn handler
+       // N.B. run-parts would require scripts to have #!/bin/sh
+       // handler is directory? -> use run-parts
+       // handler is file? -> run it directly
+       if (0 == stat(event, &st))
+               spawn((char **)args + (0==(st.st_mode & S_IFDIR)));
+       else
+               bb_simple_perror_msg(event);
+       free(handler);
+}
+
+/*
+ * acpid [-c conf_dir] [-l log_file] [-e proc_event_file] [evdev_event_file...]
+*/
+
+int acpid_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int acpid_main(int argc, char **argv)
+{
+       struct pollfd *pfd;
+       int i, nfd;
+       const char *opt_conf = "/etc/acpi";
+       const char *opt_input = "/proc/acpi/event";
+       const char *opt_logfile = "/var/log/acpid.log";
+
+       getopt32(argv, "c:e:l:d"
+               USE_FEATURE_ACPID_COMPAT("g:m:s:S:v"),
+               &opt_conf, &opt_input, &opt_logfile
+               USE_FEATURE_ACPID_COMPAT(, NULL, NULL, NULL, NULL, NULL)
+       );
+
+       // daemonize unless -d given
+       if (!(option_mask32 & 8)) { // ! -d
+               bb_daemonize_or_rexec(0, argv);
+               close(2);
+               xopen(opt_logfile, O_WRONLY | O_CREAT | O_TRUNC);
+       }
+
+       argv += optind;
+
+       // goto configuration directory
+       xchdir(opt_conf);
+
+       // prevent zombies
+       signal(SIGCHLD, SIG_IGN);
+
+       // no explicit evdev files given? -> use proc event interface
+       if (!*argv) {
+               // proc_event file is just a "config" :)
+               char *token[4];
+               parser_t *parser = config_open(opt_input);
+
+               // dispatch events
+               while (config_read(parser, token, 4, 4, "\0 ", PARSE_NORMAL)) {
+                       char *event = xasprintf("%s/%s", token[1], token[2]);
+                       process_event(event);
+                       free(event);
+               }
+
+               if (ENABLE_FEATURE_CLEAN_UP)
+                       config_close(parser);
+               return EXIT_SUCCESS;
+       }
+
+       // evdev files given, use evdev interface
+
+       // open event devices
+       pfd = xzalloc(sizeof(*pfd) * (argc - optind));
+       nfd = 0;
+       while (*argv) {
+               pfd[nfd].fd = open_or_warn(*argv++, O_RDONLY | O_NONBLOCK);
+               if (pfd[nfd].fd >= 0)
+                       pfd[nfd++].events = POLLIN;
+       }
+
+       // dispatch events
+       while (/* !bb_got_signal && */ poll(pfd, nfd, -1) > 0) {
+               for (i = 0; i < nfd; i++) {
+                       const char *event;
+                       struct input_event ev;
+
+                       if (!(pfd[i].revents & POLLIN))
+                               continue;
+
+                       if (sizeof(ev) != full_read(pfd[i].fd, &ev, sizeof(ev)))
+                               continue;
+//bb_info_msg("%d: %d %d %4d", i, ev.type, ev.code, ev.value);
+
+                       // filter out unneeded events
+                       if (ev.value != 1)
+                               continue;
+
+                       event = NULL;
+
+                       // N.B. we will conform to /proc/acpi/event
+                       // naming convention when assigning event names
+
+                       // TODO: do we want other events?
+
+                       // power and sleep buttons delivered as keys pressed
+                       if (EV_KEY == ev.type) {
+                               if (KEY_POWER == ev.code)
+                                       event = "PWRF/00000080";
+                               else if (KEY_SLEEP == ev.code)
+                                       event = "SLPB/00000080";
+                       }
+                       // switches
+                       else if (EV_SW == ev.type) {
+                               if (SW_LID == ev.code)
+                                       event = "LID/00000080";
+                               else if (SW_RFKILL_ALL == ev.code)
+                                       event = "RFKILL";
+                       }
+                       // filter out unneeded events
+                       if (!event)
+                               continue;
+
+                       // spawn event handler
+                       process_event(event);
+               }
+       }
+
+       if (ENABLE_FEATURE_CLEAN_UP) {
+               for (i = 0; i < nfd; i++)
+                       close(pfd[i].fd);
+               free(pfd);
+       }
+
+       return EXIT_SUCCESS;
+}
diff --git a/util-linux/blkid.c b/util-linux/blkid.c
new file mode 100644 (file)
index 0000000..ec699d1
--- /dev/null
@@ -0,0 +1,18 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Print UUIDs on all filesystems
+ *
+ * Copyright (C) 2008 Denys Vlasenko.
+ *
+ * Licensed under GPLv2, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.h"
+#include "volume_id.h"
+
+int blkid_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int blkid_main(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
+{
+       display_uuid_cache();
+       return 0;
+}
diff --git a/util-linux/dmesg.c b/util-linux/dmesg.c
new file mode 100644 (file)
index 0000000..f52026c
--- /dev/null
@@ -0,0 +1,67 @@
+/* vi: set sw=4 ts=4: */
+/*
+ *
+ * dmesg - display/control kernel ring buffer.
+ *
+ * Copyright 2006 Rob Landley <rob@landley.net>
+ * Copyright 2006 Bernhard Reutner-Fischer <rep.nop@aon.at>
+ *
+ * Licensed under GPLv2, see file LICENSE in this tarball for details.
+ */
+
+#include <sys/klog.h>
+#include "libbb.h"
+
+int dmesg_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int dmesg_main(int argc UNUSED_PARAM, char **argv)
+{
+       int len;
+       char *buf;
+       char *size, *level;
+       unsigned flags = getopt32(argv, "cs:n:", &size, &level);
+       enum {
+               OPT_c = 1<<0,
+               OPT_s = 1<<1,
+               OPT_n = 1<<2
+       };
+
+       if (flags & OPT_n) {
+               if (klogctl(8, NULL, xatoul_range(level, 0, 10)))
+                       bb_perror_msg_and_die("klogctl");
+               return EXIT_SUCCESS;
+       }
+
+       len = (flags & OPT_s) ? xatoul_range(size, 2, INT_MAX) : 16384;
+       buf = xmalloc(len);
+       len = klogctl(3 + (flags & OPT_c), buf, len);
+       if (len < 0)
+               bb_perror_msg_and_die("klogctl");
+       if (len == 0)
+               return EXIT_SUCCESS;
+
+       /* Skip <#> at the start of lines, and make sure we end with a newline. */
+
+       if (ENABLE_FEATURE_DMESG_PRETTY) {
+               int last = '\n';
+               int in = 0;
+
+               do {
+                       if (last == '\n' && buf[in] == '<')
+                               in += 3;
+                       else {
+                               last = buf[in++];
+                               bb_putchar(last);
+                       }
+               } while (in < len);
+               if (last != '\n')
+                       bb_putchar('\n');
+       } else {
+               full_write(STDOUT_FILENO, buf, len);
+               if (buf[len-1] != '\n')
+                       bb_putchar('\n');
+       }
+
+       if (ENABLE_FEATURE_CLEAN_UP) free(buf);
+
+       return EXIT_SUCCESS;
+}
diff --git a/util-linux/fbset.c b/util-linux/fbset.c
new file mode 100644 (file)
index 0000000..affeab0
--- /dev/null
@@ -0,0 +1,416 @@
+/* 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 "libbb.h"
+
+#define DEFAULTFBDEV  FB_0
+#define DEFAULTFBMODE "/etc/fb.modes"
+
+enum {
+       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_SHOW = 13,
+       CMD_CHANGE = 14,
+
+#if ENABLE_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
+};
+
+/* 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[9];
+       const unsigned char param_count;
+       const unsigned char code;
+} g_cmdoptions[] = {
+       /*"12345678" + NUL */
+       { "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   },
+       { "show"    , 0, CMD_SHOW     },
+       { "s"       , 0, CMD_SHOW     },
+#if ENABLE_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
+};
+
+#if ENABLE_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
+
+#if ENABLE_FEATURE_FBSET_READMODE
+static void ss(uint32_t *x, uint32_t flag, char *buf, const char *what)
+{
+       if (strcmp(buf, what) == 0)
+               *x &= ~flag;
+       else
+               *x |= flag;
+}
+
+static int read_mode_db(struct fb_var_screeninfo *base, const char *fn,
+                                       const char *mode)
+{
+       char *token[2], *p, *s;
+       parser_t *parser = config_open(fn);
+
+       while (config_read(parser, token, 2, 1, "# \t\r", PARSE_NORMAL)) {
+               if (strcmp(token[0], "mode") != 0 || !token[1])
+                       continue;
+               p = strstr(token[1], mode);
+               if (!p)
+                       continue;
+               s = p + strlen(mode);
+               //bb_info_msg("CHECK[%s][%s][%d]", mode, p-1, *s);
+               /* exact match? */
+               if (((!*s || isspace(*s)) && '"' != s[-1]) /* end-of-token */
+                || ('"' == *s && '"' == p[-1]) /* ends with " but starts with " too! */
+               ) {
+                       //bb_info_msg("FOUND[%s][%s][%s][%d]", token[1], p, mode, isspace(*s));
+                       break;
+               }
+       }
+
+       if (!token[0])
+               return 0;
+
+       while (config_read(parser, token, 2, 1, "# \t", PARSE_NORMAL)) {
+               int i;
+
+//bb_info_msg("???[%s][%s]", token[0], token[1]);
+               if (strcmp(token[0], "endmode") == 0) {
+//bb_info_msg("OK[%s]", mode);
+                       return 1;
+               }
+               p = token[1];
+               i = index_in_strings(
+                       "geometry\0timings\0interlaced\0double\0vsync\0hsync\0csync\0extsync\0",
+                       token[0]);
+               switch (i) {
+               case 0:
+                       /* 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));
+//bb_info_msg("GEO[%s]", p);
+                       break;
+               case 1:
+                       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));
+//bb_info_msg("TIM[%s]", p);
+                       break;
+               case 2:
+               case 3: {
+                       static const uint32_t syncs[] = {FB_VMODE_INTERLACED, FB_VMODE_DOUBLE};
+                       ss(&base->vmode, syncs[i-2], p, "false");
+//bb_info_msg("VMODE[%s]", p);
+                       break;
+               }
+               case 4:
+               case 5:
+               case 6: {
+                       static const uint32_t syncs[] = {FB_SYNC_VERT_HIGH_ACT, FB_SYNC_HOR_HIGH_ACT, FB_SYNC_COMP_HIGH_ACT};
+                       ss(&base->sync, syncs[i-4], p, "low");
+//bb_info_msg("SYNC[%s]", p);
+                       break;
+               }
+               case 7:
+                       ss(&base->sync, FB_SYNC_EXT, p, "false");
+//bb_info_msg("EXTSYNC[%s]", p);
+                       break;
+               }
+       }
+       return 0;
+}
+#endif
+
+static void setmode(struct fb_var_screeninfo *base,
+                                       struct fb_var_screeninfo *set)
+{
+       if ((int32_t) set->xres > 0)
+               base->xres = set->xres;
+       if ((int32_t) set->yres > 0)
+               base->yres = set->yres;
+       if ((int32_t) set->xres_virtual > 0)
+               base->xres_virtual = set->xres_virtual;
+       if ((int32_t) set->yres_virtual > 0)
+               base->yres_virtual = set->yres_virtual;
+       if ((int32_t) set->bits_per_pixel > 0)
+               base->bits_per_pixel = set->bits_per_pixel;
+}
+
+static 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"
+#if ENABLE_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),
+#if ENABLE_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);
+}
+
+int fbset_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int fbset_main(int argc, char **argv)
+{
+       enum {
+               OPT_CHANGE   = (1 << 0),
+               OPT_SHOW     = (1 << 1),
+               OPT_READMODE = (1 << 2),
+               OPT_ALL      = (1 << 9),
+       };
+       struct fb_var_screeninfo var, varset;
+       int fh, i;
+       unsigned options = 0;
+
+       const char *fbdev = DEFAULTFBDEV;
+       const 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) != NULL; argc--, argv++) {
+               if (thisarg[0] == '-') for (i = 0; i < ARRAY_SIZE(g_cmdoptions); i++) {
+                       if (strcmp(thisarg + 1, g_cmdoptions[i].name) != 0)
+                               continue;
+                       if (argc <= 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_ALL:
+                               options |= OPT_ALL;
+                               break;
+                       case CMD_SHOW:
+                               options |= OPT_SHOW;
+                               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;
+#if ENABLE_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
+                       }
+                       switch (g_cmdoptions[i].code) {
+                       case CMD_FB:
+                       case CMD_DB:
+                       case CMD_ALL:
+                       case CMD_SHOW:
+                               break;
+                       default:
+                               options |= OPT_CHANGE; /* the other commands imply changes */
+                       }
+                       argc -= g_cmdoptions[i].param_count;
+                       argv += g_cmdoptions[i].param_count;
+                       goto contin;
+               }
+               if (argc != 1)
+                       bb_show_usage();
+               mode = *argv;
+               options |= OPT_READMODE;
+ contin: ;
+       }
+
+       fh = xopen(fbdev, O_RDONLY);
+       xioctl(fh, FBIOGET_VSCREENINFO, &var);
+       if (options & OPT_READMODE) {
+#if !ENABLE_FEATURE_FBSET_READMODE
+               bb_show_usage();
+#else
+               if (!read_mode_db(&var, modefile, mode)) {
+                       bb_error_msg_and_die("unknown video mode '%s'", mode);
+               }
+#endif
+       }
+
+       if (options & OPT_CHANGE) {
+               setmode(&var, &varset);
+               if (options & OPT_ALL)
+                       var.activate = FB_ACTIVATE_ALL;
+               xioctl(fh, FBIOPUT_VSCREENINFO, &var);
+       }
+       if (options == 0 || options & OPT_SHOW)
+               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 (file)
index 0000000..3831ab4
--- /dev/null
@@ -0,0 +1,131 @@
+/* vi: set sw=4 ts=4: */
+/* fdformat.c  -  Low-level formats a floppy disk - Werner Almesberger
+ * 5 July 2003 -- modified for Busybox by Erik Andersen
+ *
+ * Licensed under GPLv2, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.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. */
+
+int fdformat_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int fdformat_main(int argc UNUSED_PARAM, char **argv)
+{
+       int fd, n, cyl, read_bytes, verify;
+       unsigned char *data;
+       struct stat st;
+       struct floppy_struct param;
+       struct format_descr descr;
+
+       opt_complementary = "=1"; /* must have 1 param */
+       verify = !getopt32(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, &param);
+
+       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);
+
+       /* n == track */
+       for (n = 0; n < param.track; n++) {
+               descr.head = 0;
+               descr.track = n;
+               xioctl(fd, FDFMTTRK, &descr);
+               printf("%3d\b\b\b", n);
+               if (param.head == 2) {
+                       descr.head = 1;
+                       xioctl(fd, FDFMTTRK, &descr);
+               }
+       }
+
+       xioctl(fd, FDFMTEND, NULL);
+       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 (file)
index 0000000..dc61e23
--- /dev/null
@@ -0,0 +1,2997 @@
+/* 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.
+ */
+
+#ifndef _LARGEFILE64_SOURCE
+/* For lseek64 */
+#define _LARGEFILE64_SOURCE
+#endif
+#include <assert.h>             /* assert */
+#include "libbb.h"
+
+/* 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 DEFAULT_SECTOR_SIZE_STR "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
+
+
+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,
+};
+
+
+/* Used for sector numbers. Today's disk sizes make it necessary */
+typedef unsigned long long ullong;
+
+struct hd_geometry {
+       unsigned char heads;
+       unsigned char sectors;
+       unsigned short cylinders;
+       unsigned long start;
+};
+
+#define HDIO_GETGEO     0x0301  /* get device geometry */
+
+static const char msg_building_new_label[] ALIGN1 =
+"Building a new %s. Changes will remain in memory only,\n"
+"until you decide to write them. After that the previous content\n"
+"won't be recoverable.\n\n";
+
+static const char msg_part_already_defined[] ALIGN1 =
+"Partition %d is already defined, delete it before re-adding\n";
+
+
+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 */
+} PACKED;
+
+static const char unable_to_open[] ALIGN1 = "can't open '%s'";
+static const char unable_to_read[] ALIGN1 = "can't read from %s";
+static const char unable_to_seek[] ALIGN1 = "can't seek on %s";
+
+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 { OPEN_MAIN, TRY_ONLY, CREATE_EMPTY_DOS, CREATE_EMPTY_SUN };
+
+static void update_units(void);
+#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 char *const *sys);
+static unsigned read_int(unsigned low, unsigned dflt, unsigned high, unsigned base, const char *mesg);
+#endif
+static const char *partition_type(unsigned char type);
+static void get_geometry(void);
+#if ENABLE_FEATURE_SUN_LABEL || ENABLE_FEATURE_FDISK_WRITABLE
+static int get_boot(enum action what);
+#else
+static int get_boot(void);
+#endif
+
+#define PLURAL   0
+#define SINGULAR 1
+
+static unsigned get_start_sect(const struct partition *p);
+static unsigned 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.
+ */
+struct pte {
+       struct partition *part_table;   /* points into sectorbuffer */
+       struct partition *ext_pointer;  /* points into sectorbuffer */
+       ullong offset;          /* disk sector number */
+       char *sectorbuffer;     /* disk sector contents */
+#if ENABLE_FEATURE_FDISK_WRITABLE
+       char changed;           /* boolean */
+#endif
+};
+
+/* DOS partition types */
+
+static const char *const 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 W95 FAT32 (LBA)",
+       "\x1e" "Hidden W95 FAT16 (LBA)",
+       "\x3c" "Part.Magic 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" "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
+       NULL
+};
+
+enum {
+       dev_fd = 3                  /* the disk */
+};
+
+/* Globals */
+struct globals {
+       char *line_ptr;
+
+       const char *disk_device;
+       int g_partitions; // = 4;       /* maximum partition + 1 */
+       unsigned units_per_sector; // = 1;
+       unsigned sector_size; // = DEFAULT_SECTOR_SIZE;
+       unsigned user_set_sector_size;
+       unsigned sector_offset; // = 1;
+       unsigned g_heads, g_sectors, g_cylinders;
+       smallint /* enum label_type */ current_label_type;
+       smallint display_in_cyl_units; // = 1;
+#if ENABLE_FEATURE_OSF_LABEL
+       smallint possibly_osf_label;
+#endif
+
+       smallint listing;               /* no aborts for fdisk -l */
+       smallint dos_compatible_flag; // = 1;
+#if ENABLE_FEATURE_FDISK_WRITABLE
+       //int dos_changed;
+       smallint nowarn;                /* no warnings for fdisk -l/-s */
+#endif
+       int ext_index;                  /* the prime extended partition */
+       unsigned user_cylinders, user_heads, user_sectors;
+       unsigned pt_heads, pt_sectors;
+       unsigned kern_heads, kern_sectors;
+       ullong extended_offset;         /* offset of link pointers */
+       ullong total_number_of_sectors;
+
+       jmp_buf listingbuf;
+       char line_buffer[80];
+       char partname_buffer[80];
+       /* Raw disk label. For DOS-type partition tables the MBR,
+        * with descriptions of the primary partitions. */
+       char MBRbuffer[MAX_SECTOR_SIZE];
+       /* Partition tables */
+       struct pte ptes[MAXIMUM_PARTS];
+};
+#define G (*ptr_to_globals)
+#define line_ptr             (G.line_ptr            )
+#define disk_device          (G.disk_device         )
+#define g_partitions         (G.g_partitions        )
+#define units_per_sector     (G.units_per_sector    )
+#define sector_size          (G.sector_size         )
+#define user_set_sector_size (G.user_set_sector_size)
+#define sector_offset        (G.sector_offset       )
+#define g_heads              (G.g_heads             )
+#define g_sectors            (G.g_sectors           )
+#define g_cylinders          (G.g_cylinders         )
+#define current_label_type   (G.current_label_type  )
+#define display_in_cyl_units (G.display_in_cyl_units)
+#define possibly_osf_label   (G.possibly_osf_label  )
+#define listing                 (G.listing                )
+#define dos_compatible_flag     (G.dos_compatible_flag    )
+#define nowarn                  (G.nowarn                 )
+#define ext_index               (G.ext_index              )
+#define user_cylinders          (G.user_cylinders         )
+#define user_heads              (G.user_heads             )
+#define user_sectors            (G.user_sectors           )
+#define pt_heads                (G.pt_heads               )
+#define pt_sectors              (G.pt_sectors             )
+#define kern_heads              (G.kern_heads             )
+#define kern_sectors            (G.kern_sectors           )
+#define extended_offset         (G.extended_offset        )
+#define total_number_of_sectors (G.total_number_of_sectors)
+#define listingbuf      (G.listingbuf     )
+#define line_buffer     (G.line_buffer    )
+#define partname_buffer (G.partname_buffer)
+#define MBRbuffer       (G.MBRbuffer      )
+#define ptes            (G.ptes           )
+#define INIT_G() do { \
+       SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
+       sector_size = DEFAULT_SECTOR_SIZE; \
+       sector_offset = 1; \
+       g_partitions = 4; \
+       display_in_cyl_units = 1; \
+       units_per_sector = 1; \
+       dos_compatible_flag = 1; \
+} while (0)
+
+
+/* TODO: move to libbb? */
+static ullong bb_BLKGETSIZE_sectors(int fd)
+{
+       uint64_t v64;
+       unsigned long longsectors;
+
+       if (ioctl(fd, BLKGETSIZE64, &v64) == 0) {
+               /* Got bytes, convert to 512 byte sectors */
+               return (v64 >> 9);
+       }
+       /* Needs temp of type long */
+       if (ioctl(fd, BLKGETSIZE, &longsectors))
+               longsectors = 0;
+       return longsectors;
+}
+
+
+#define IS_EXTENDED(i) \
+       ((i) == EXTENDED || (i) == WIN98_EXTENDED || (i) == LINUX_EXTENDED)
+
+#define cround(n)       (display_in_cyl_units ? ((n)/units_per_sector)+1 : (n))
+
+#define scround(x)      (((x)+units_per_sector-1)/units_per_sector)
+
+#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) \
+       do { \
+               s = sector % g_sectors + 1;  \
+               sector /= g_sectors;         \
+               h = sector % g_heads;        \
+               sector /= g_heads;           \
+               c = sector & 0xff;           \
+               s |= (sector >> 2) & 0xc0;   \
+       } while (0)
+
+static void
+close_dev_fd(void)
+{
+       /* Not really closing, but making sure it is open, and to harmless place */
+       xmove_fd(xopen(bb_dev_null, O_RDONLY), dev_fd);
+}
+
+#if ENABLE_FEATURE_FDISK_WRITABLE
+/* Read line; return 0 or first printable char */
+static int
+read_line(const char *prompt)
+{
+       int sz;
+
+       sz = read_line_input(prompt, line_buffer, sizeof(line_buffer), NULL);
+       if (sz <= 0)
+               exit(EXIT_SUCCESS); /* Ctrl-D or Ctrl-C */
+
+       if (line_buffer[sz-1] == '\n')
+               line_buffer[--sz] = '\0';
+
+       line_ptr = line_buffer;
+       while (*line_ptr && !isgraph(*line_ptr))
+               line_ptr++;
+       return *line_ptr;
+}
+#endif
+
+/*
+ * Return partition name - uses static storage
+ */
+static const char *
+partname(const char *dev, int pno, int lth)
+{
+       const char *p;
+       int w, wp;
+       int bufsiz;
+       char *bufp;
+
+       bufp = partname_buffer;
+       bufsiz = sizeof(partname_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;
+}
+
+#if ENABLE_FEATURE_FDISK_WRITABLE
+static void
+set_all_unchanged(void)
+{
+       int i;
+
+       for (i = 0; i < MAXIMUM_PARTS; i++)
+               ptes[i].changed = 0;
+}
+
+static ALWAYS_INLINE void
+set_changed(int i)
+{
+       ptes[i].changed = 1;
+}
+#endif /* FEATURE_FDISK_WRITABLE */
+
+static ALWAYS_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";
+       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
+static ALWAYS_INLINE void
+write_part_table_flag(char *b)
+{
+       b[510] = 0x55;
+       b[511] = 0xaa;
+}
+
+static char
+read_nonempty(const char *mesg)
+{
+       while (!read_line(mesg))
+               continue;
+       return *line_ptr;
+}
+
+static char
+read_maybe_empty(const char *mesg)
+{
+       if (!read_line(mesg)) {
+               line_ptr = line_buffer;
+               line_ptr[0] = '\n';
+               line_ptr[1] = '\0';
+       }
+       return line_ptr[0];
+}
+
+static int
+read_hex(const char *const *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 (v > 0xff)
+                       /* Bad input also triggers this */
+                       continue;
+               return v;
+       }
+}
+#endif /* FEATURE_FDISK_WRITABLE */
+
+static void fdisk_fatal(const char *why)
+{
+       if (listing) {
+               close_dev_fd();
+               longjmp(listingbuf, 1);
+       }
+       bb_error_msg_and_die(why, disk_device);
+}
+
+static void
+seek_sector(ullong secno)
+{
+       secno *= sector_size;
+#if ENABLE_FDISK_SUPPORT_LARGE_DISKS
+       if (lseek64(dev_fd, (off64_t)secno, SEEK_SET) == (off64_t) -1)
+               fdisk_fatal(unable_to_seek);
+#else
+       if (secno > MAXINT(off_t)
+        || lseek(dev_fd, (off_t)secno, SEEK_SET) == (off_t) -1
+       ) {
+               fdisk_fatal(unable_to_seek);
+       }
+#endif
+}
+
+#if ENABLE_FEATURE_FDISK_WRITABLE
+static void
+write_sector(ullong secno, const void *buf)
+{
+       seek_sector(secno);
+       xwrite(dev_fd, buf, sector_size);
+}
+#endif
+
+
+#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)
+STATIC_OSF void bsd_select(void);
+STATIC_OSF void xbsd_print_disklabel(int);
+#include "fdisk_osf.c"
+
+#if ENABLE_FEATURE_SGI_LABEL || ENABLE_FEATURE_SUN_LABEL
+static uint16_t
+fdisk_swap16(uint16_t x)
+{
+       return (x << 8) | (x >> 8);
+}
+
+static uint32_t
+fdisk_swap32(uint32_t x)
+{
+       return (x << 24) |
+              ((x & 0xFF00) << 8) |
+              ((x & 0xFF0000) >> 8) |
+              (x >> 24);
+}
+#endif
+
+STATIC_SGI const char *const 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);
+#if ENABLE_FEATURE_FDISK_ADVANCED
+STATIC_SGI void sgi_set_xcyl(void);
+#endif
+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 char *const 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 add_sun_partition(int n, int sys);
+#if ENABLE_FEATURE_FDISK_ADVANCED
+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);
+#endif
+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"
+
+
+#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 /* 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 unsigned
+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, unsigned nr_sects)
+{
+       store4_little_endian(p->size4, nr_sects);
+}
+#endif
+
+static unsigned
+get_nr_sects(const struct partition *p)
+{
+       return read4_little_endian(p->size4);
+}
+
+/* Allocate a buffer and read a partition table sector */
+static void
+read_pte(struct pte *pe, ullong offset)
+{
+       pe->offset = offset;
+       pe->sectorbuffer = xzalloc(sector_size);
+       seek_sector(offset);
+       /* xread would make us abort - bad for fdisk -l */
+       if (full_read(dev_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".
+ */
+#ifdef UNUSED
+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);
+}
+#endif
+
+static void
+menu(void)
+{
+       puts("Command Action");
+       if (LABEL_IS_SUN) {
+               puts("a\ttoggle a read only flag");           /* sun */
+               puts("b\tedit bsd disklabel");
+               puts("c\ttoggle the mountable flag");         /* sun */
+               puts("d\tdelete a partition");
+               puts("l\tlist known partition types");
+               puts("n\tadd a new partition");
+               puts("o\tcreate a new empty DOS partition table");
+               puts("p\tprint the partition table");
+               puts("q\tquit without saving changes");
+               puts("s\tcreate a new empty Sun disklabel");  /* sun */
+               puts("t\tchange a partition's system id");
+               puts("u\tchange display/entry units");
+               puts("v\tverify the partition table");
+               puts("w\twrite table to disk and exit");
+#if ENABLE_FEATURE_FDISK_ADVANCED
+               puts("x\textra functionality (experts only)");
+#endif
+       } else if (LABEL_IS_SGI) {
+               puts("a\tselect bootable partition");    /* sgi flavour */
+               puts("b\tedit bootfile entry");          /* sgi */
+               puts("c\tselect sgi swap partition");    /* sgi flavour */
+               puts("d\tdelete a partition");
+               puts("l\tlist known partition types");
+               puts("n\tadd a new partition");
+               puts("o\tcreate a new empty DOS partition table");
+               puts("p\tprint the partition table");
+               puts("q\tquit without saving changes");
+               puts("s\tcreate a new empty Sun disklabel");  /* sun */
+               puts("t\tchange a partition's system id");
+               puts("u\tchange display/entry units");
+               puts("v\tverify the partition table");
+               puts("w\twrite table to disk and exit");
+       } else if (LABEL_IS_AIX) {
+               puts("o\tcreate a new empty DOS partition table");
+               puts("q\tquit without saving changes");
+               puts("s\tcreate a new empty Sun disklabel");  /* sun */
+       } else {
+               puts("a\ttoggle a bootable flag");
+               puts("b\tedit bsd disklabel");
+               puts("c\ttoggle the dos compatibility flag");
+               puts("d\tdelete a partition");
+               puts("l\tlist known partition types");
+               puts("n\tadd a new partition");
+               puts("o\tcreate a new empty DOS partition table");
+               puts("p\tprint the partition table");
+               puts("q\tquit without saving changes");
+               puts("s\tcreate a new empty Sun disklabel");  /* sun */
+               puts("t\tchange a partition's system id");
+               puts("u\tchange display/entry units");
+               puts("v\tverify the partition table");
+               puts("w\twrite table to disk and exit");
+#if ENABLE_FEATURE_FDISK_ADVANCED
+               puts("x\textra functionality (experts only)");
+#endif
+       }
+}
+#endif /* FEATURE_FDISK_WRITABLE */
+
+
+#if ENABLE_FEATURE_FDISK_ADVANCED
+static void
+xmenu(void)
+{
+       puts("Command Action");
+       if (LABEL_IS_SUN) {
+               puts("a\tchange number of alternate cylinders");      /*sun*/
+               puts("c\tchange number of cylinders");
+               puts("d\tprint the raw data in the partition table");
+               puts("e\tchange number of extra sectors per cylinder");/*sun*/
+               puts("h\tchange number of heads");
+               puts("i\tchange interleave factor");                  /*sun*/
+               puts("o\tchange rotation speed (rpm)");               /*sun*/
+               puts("p\tprint the partition table");
+               puts("q\tquit without saving changes");
+               puts("r\treturn to main menu");
+               puts("s\tchange number of sectors/track");
+               puts("v\tverify the partition table");
+               puts("w\twrite table to disk and exit");
+               puts("y\tchange number of physical cylinders");       /*sun*/
+       } else if (LABEL_IS_SGI) {
+               puts("b\tmove beginning of data in a partition"); /* !sun */
+               puts("c\tchange number of cylinders");
+               puts("d\tprint the raw data in the partition table");
+               puts("e\tlist extended partitions");          /* !sun */
+               puts("g\tcreate an IRIX (SGI) partition table");/* sgi */
+               puts("h\tchange number of heads");
+               puts("p\tprint the partition table");
+               puts("q\tquit without saving changes");
+               puts("r\treturn to main menu");
+               puts("s\tchange number of sectors/track");
+               puts("v\tverify the partition table");
+               puts("w\twrite table to disk and exit");
+       } else if (LABEL_IS_AIX) {
+               puts("b\tmove beginning of data in a partition"); /* !sun */
+               puts("c\tchange number of cylinders");
+               puts("d\tprint the raw data in the partition table");
+               puts("e\tlist extended partitions");          /* !sun */
+               puts("g\tcreate an IRIX (SGI) partition table");/* sgi */
+               puts("h\tchange number of heads");
+               puts("p\tprint the partition table");
+               puts("q\tquit without saving changes");
+               puts("r\treturn to main menu");
+               puts("s\tchange number of sectors/track");
+               puts("v\tverify the partition table");
+               puts("w\twrite table to disk and exit");
+       } else {
+               puts("b\tmove beginning of data in a partition"); /* !sun */
+               puts("c\tchange number of cylinders");
+               puts("d\tprint the raw data in the partition table");
+               puts("e\tlist extended partitions");          /* !sun */
+               puts("f\tfix partition order");               /* !sun, !aix, !sgi */
+#if ENABLE_FEATURE_SGI_LABEL
+               puts("g\tcreate an IRIX (SGI) partition table");/* sgi */
+#endif
+               puts("h\tchange number of heads");
+               puts("p\tprint the partition table");
+               puts("q\tquit without saving changes");
+               puts("r\treturn to main menu");
+               puts("s\tchange number of sectors/track");
+               puts("v\tverify the partition table");
+               puts("w\twrite table to disk and exit");
+       }
+}
+#endif /* ADVANCED mode */
+
+#if ENABLE_FEATURE_FDISK_WRITABLE
+static const char *const *
+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 /* FEATURE_FDISK_WRITABLE */
+
+static const char *
+partition_type(unsigned char type)
+{
+       int i;
+       const char *const *types = get_sys_types();
+
+       for (i = 0; types[i]; i++)
+               if ((unsigned char)types[i][0] == type)
+                       return types[i] + 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);
+}
+
+static void
+list_types(const char *const *sys)
+{
+       enum { COLS = 3 };
+
+       unsigned last[COLS];
+       unsigned done, next, size;
+       int i;
+
+       for (size = 0; sys[size]; size++)
+               continue;
+
+       done = 0;
+       for (i = COLS-1; i >= 0; i--) {
+               done += (size + i - done) / (i + 1);
+               last[COLS-1 - i] = done;
+       }
+
+       i = done = next = 0;
+       do {
+               printf("%c%2x %-22.22s", i ? ' ' : '\n',
+                       (unsigned char)sys[next][0],
+                       sys[next] + 1);
+               next = last[i++] + done;
+               if (i >= COLS || next >= last[i]) {
+                       i = 0;
+                       next = ++done;
+               }
+       } while (done < last[0]);
+       bb_putchar('\n');
+}
+#endif /* 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, ullong start, ullong stop, int sysid)
+{
+       struct partition *p;
+       ullong 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 / (g_sectors * g_heads) > 1023))
+               start = g_heads * g_sectors * 1024 - 1;
+       set_hsc(p->head, p->sector, p->cyl, start);
+       if (dos_compatible_flag && (stop / (g_sectors * g_heads) > 1023))
+               stop = g_heads * g_sectors * 1024 - 1;
+       set_hsc(p->end_head, p->end_sector, p->end_cyl, stop);
+       ptes[i].changed = 1;
+}
+#endif
+
+static int
+warn_geometry(void)
+{
+       if (g_heads && g_sectors && g_cylinders)
+               return 0;
+
+       printf("Unknown value(s) for:");
+       if (!g_heads)
+               printf(" heads");
+       if (!g_sectors)
+               printf(" sectors");
+       if (!g_cylinders)
+               printf(" cylinders");
+       printf(
+#if ENABLE_FEATURE_FDISK_WRITABLE
+               " (settable in the extra functions menu)"
+#endif
+               "\n");
+       return 1;
+}
+
+static void
+update_units(void)
+{
+       int cyl_units = g_heads * g_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 && g_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",
+                       g_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[g_partitions];
+
+               if (g_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[g_partitions - 1];
+#if ENABLE_FEATURE_FDISK_WRITABLE
+                       printf("Warning: deleting partitions after %d\n",
+                               g_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", g_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", g_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;
+               g_partitions++;
+       }
+
+#if ENABLE_FEATURE_FDISK_WRITABLE
+       /* remove empty links */
+ remove:
+       for (i = 4; i < g_partitions; i++) {
+               struct pte *pe = &ptes[i];
+
+               if (!get_nr_sects(pe->part_table)
+                && (g_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(msg_building_new_label, "DOS disklabel");
+
+       current_label_type = LABEL_DOS;
+
+#if ENABLE_FEATURE_OSF_LABEL
+       possibly_osf_label = 0;
+#endif
+       g_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 /* FEATURE_FDISK_WRITABLE */
+
+static void
+get_sectorsize(void)
+{
+       if (!user_set_sector_size) {
+               int arg;
+               if (ioctl(dev_fd, BLKSSZGET, &arg) == 0)
+                       sector_size = arg;
+               if (sector_size != DEFAULT_SECTOR_SIZE)
+                       printf("Note: sector size is %d "
+                               "(not " DEFAULT_SECTOR_SIZE_STR ")\n",
+                               sector_size);
+       }
+}
+
+static void
+get_kernel_geometry(void)
+{
+       struct hd_geometry geometry;
+
+       if (!ioctl(dev_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;
+
+       get_sectorsize();
+       sec_fac = sector_size / 512;
+#if ENABLE_FEATURE_SUN_LABEL
+       guess_device_type();
+#endif
+       g_heads = g_cylinders = g_sectors = 0;
+       kern_heads = kern_sectors = 0;
+       pt_heads = pt_sectors = 0;
+
+       get_kernel_geometry();
+       get_partition_table_geometry();
+
+       g_heads = user_heads ? user_heads :
+               pt_heads ? pt_heads :
+               kern_heads ? kern_heads : 255;
+       g_sectors = user_sectors ? user_sectors :
+               pt_sectors ? pt_sectors :
+               kern_sectors ? kern_sectors : 63;
+       total_number_of_sectors = bb_BLKGETSIZE_sectors(dev_fd);
+
+       sector_offset = 1;
+       if (dos_compatible_flag)
+               sector_offset = g_sectors;
+
+       g_cylinders = total_number_of_sectors / (g_heads * g_sectors * sec_fac);
+       if (!g_cylinders)
+               g_cylinders = user_cylinders;
+}
+
+/*
+ * Opens disk_device and optionally reads MBR.
+ *    FIXME: document what each 'what' value will do!
+ * Returns:
+ *   -1: no 0xaa55 flag present (possibly entire disk BSD)
+ *    0: found or created label
+ *    1: I/O error
+ */
+#if ENABLE_FEATURE_SUN_LABEL || ENABLE_FEATURE_FDISK_WRITABLE
+static int get_boot(enum action what)
+#else
+static int get_boot(void)
+#define get_boot(what) get_boot()
+#endif
+{
+       int i, fd;
+
+       g_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_FDISK_WRITABLE
+// ALERT! highly idiotic design!
+// We end up here when we call get_boot() recursively
+// via get_boot() [table is bad] -> create_doslabel() -> get_boot(CREATE_EMPTY_DOS).
+// or get_boot() [table is bad] -> create_sunlabel() -> get_boot(CREATE_EMPTY_SUN).
+// (just factor out re-init of ptes[0,1,2,3] in a separate fn instead?)
+// So skip opening device _again_...
+       if (what == CREATE_EMPTY_DOS  USE_FEATURE_SUN_LABEL(|| what == CREATE_EMPTY_SUN))
+               goto created_table;
+
+       fd = open(disk_device, (option_mask32 & OPT_l) ? O_RDONLY : O_RDWR);
+
+       if (fd < 0) {
+               fd = open(disk_device, O_RDONLY);
+               if (fd < 0) {
+                       if (what == TRY_ONLY)
+                               return 1;
+                       fdisk_fatal(unable_to_open);
+               }
+               printf("'%s' is opened for read only\n", disk_device);
+       }
+       xmove_fd(fd, dev_fd);
+       if (512 != full_read(dev_fd, MBRbuffer, 512)) {
+               if (what == TRY_ONLY) {
+                       close_dev_fd();
+                       return 1;
+               }
+               fdisk_fatal(unable_to_read);
+       }
+#else
+       fd = open(disk_device, O_RDONLY);
+       if (fd < 0)
+               return 1;
+       if (512 != full_read(fd, MBRbuffer, 512)) {
+               close(fd);
+               return 1;
+       }
+       xmove_fd(fd, dev_fd);
+#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
+       if (!valid_part_table_flag(MBRbuffer))
+               return -1;
+#else
+       if (!valid_part_table_flag(MBRbuffer)) {
+               if (what == OPEN_MAIN) {
+                       printf("Device contains neither a valid DOS "
+                                 "partition table, nor Sun, SGI or OSF "
+                                 "disklabel\n");
+#ifdef __sparc__
+                       USE_FEATURE_SUN_LABEL(create_sunlabel();)
+#else
+                       create_doslabel();
+#endif
+                       return 0;
+               }
+               /* TRY_ONLY: */
+               return -1;
+       }
+ created_table:
+#endif /* FEATURE_FDISK_WRITABLE */
+
+
+       USE_FEATURE_FDISK_WRITABLE(warn_cylinders();)
+       warn_geometry();
+
+       for (i = 0; i < 4; i++) {
+               if (IS_EXTENDED(ptes[i].part_table->sys_ind)) {
+                       if (g_partitions != 4)
+                               printf("Ignoring extra extended "
+                                       "partition %d\n", i + 1);
+                       else
+                               read_extended(i);
+               }
+       }
+
+       for (i = 3; i < g_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);
+                       USE_FEATURE_FDISK_WRITABLE(pe->changed = 1;)
+               }
+       }
+
+       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, const 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 *= g_heads * g_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) {
+                               ullong bytes;
+                               unsigned long unit;
+
+                               bytes = (ullong) 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) {
+                       i = dflt;
+                       printf("Using default value %u\n", i);
+               }
+               if (i >= low && i <= high)
+                       break;
+               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 = 1 - dos_compatible_flag;
+       if (dos_compatible_flag) {
+               sector_offset = g_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) {
+                       g_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 */
+               --g_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 (g_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 (g_partitions > 5) {
+                       g_partitions--;
+                       while (i < g_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, g_partitions);
+       } else {
+               i = get_partition(0, g_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.\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");
+                               break;
+                       }
+               }
+
+               if (sys < 256) {
+#if ENABLE_FEATURE_SUN_LABEL
+                       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");
+#endif
+#if ENABLE_FEATURE_SGI_LABEL
+                       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");
+                       }
+#endif
+                       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 /* FEATURE_FDISK_WRITABLE */
+
+
+/* check_consistency() and linear2chs() 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
+linear2chs(unsigned ls, unsigned *c, unsigned *h, unsigned *s)
+{
+       int spc = g_heads * g_sectors;
+
+       *c = ls / spc;
+       ls = ls % spc;
+       *h = ls / g_sectors;
+       *s = ls % g_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 (!g_heads || !g_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) */
+       linear2chs(get_start_sect(p), &lbc, &lbh, &lbs);
+
+/* compute logical ending (c, h, s) */
+       linear2chs(get_start_sect(p) + get_nr_sects(p) - 1, &lec, &leh, &les);
+
+/* Same physical / logical beginning? */
+       if (g_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 (g_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 != (g_heads - 1) || pes != g_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",
+                  g_heads, g_sectors, g_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;
+       ullong last_p_start_pos = 0, p_start_pos;
+       int i, last_i = 0;
+
+       for (i = 0; i < g_partitions; i++) {
+               if (i == 4) {
+                       last_i = 4;
+                       last_p_start_pos = 0;
+               }
+               pe = &ptes[i];
+               p = pe->part_table;
+               if (p->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 < g_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 < g_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 < g_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("Ordering is already correct\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 < g_partitions; i++) {
+               const struct pte *pe = &ptes[i];
+               ullong psects;
+               ullong 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 */
+                               ? '*' : '?',
+                       (ullong) cround(get_partition_start(pe)),           /* start */
+                       (ullong) cround(get_partition_start(pe) + psects    /* end */
+                               - (psects ? 1 : 0)),
+                       (ullong) 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, g_heads, g_sectors, g_cylinders);
+       printf("Nr AF  Hd Sec  Cyl  Hd Sec  Cyl      Start       Size ID\n");
+       for (i = 0; i < g_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(ullong *first, ullong *last)
+{
+       int i;
+       const struct pte *pe = &ptes[0];
+       const struct partition *p;
+
+       for (i = 0; i < g_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, ullong start)
+{
+       ullong total, real_s, real_c;
+
+       real_s = sector(s) - 1;
+       real_c = cylinder(s, c);
+       total = (real_c * g_sectors + real_s) * g_heads + h;
+       if (!total)
+               printf("Partition %d contains sector 0\n", n);
+       if (h >= g_heads)
+               printf("Partition %d: head %d greater than maximum %d\n",
+                       n, h + 1, g_heads);
+       if (real_s >= g_sectors)
+               printf("Partition %d: sector %d greater than "
+                       "maximum %d\n", n, s, g_sectors);
+       if (real_c >= g_cylinders)
+               printf("Partition %d: cylinder %llu greater than "
+                       "maximum %d\n", n, real_c + 1, g_cylinders);
+       if (g_cylinders <= 1024 && start != total)
+               printf("Partition %d: previous sectors %llu disagrees with "
+                       "total %llu\n", n, start, total);
+}
+
+static void
+verify(void)
+{
+       int i, j;
+       unsigned total = 1;
+       ullong first[g_partitions], last[g_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 < g_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];
+               ullong e_last = get_start_sect(pex->part_table) +
+                       get_nr_sects(pex->part_table) - 1;
+
+               for (i = 4; i < g_partitions; i++) {
+                       total++;
+                       p = ptes[i].part_table;
+                       if (!p->sys_ind) {
+                               if (i != 4 || i + 1 < g_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 > g_heads * g_sectors * g_cylinders)
+               printf("Total allocated sectors %d greater than the maximum "
+                       "%d\n", total, g_heads * g_sectors * g_cylinders);
+       else {
+               total = g_heads * g_sectors * g_cylinders - total;
+               if (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;
+       ullong limit, temp;
+       ullong start, stop = 0;
+       ullong first[g_partitions], last[g_partitions];
+
+       if (p && p->sys_ind) {
+               printf(msg_part_already_defined, n + 1);
+               return;
+       }
+       fill_bounds(first, last);
+       if (n < 4) {
+               start = sector_offset;
+               if (display_in_cyl_units || !total_number_of_sectors)
+                       limit = (ullong) g_heads * g_sectors * g_cylinders - 1;
+               else
+                       limit = total_number_of_sectors - 1;
+               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 < g_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 < g_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 %lld is already allocated\n", temp);
+                       temp = start;
+                       num_read = 0;
+               }
+               if (!num_read && start == temp) {
+                       ullong 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 < g_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)
+                       g_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;
+               g_partitions = 5;
+       }
+}
+
+static void
+add_logical(void)
+{
+       if (g_partitions > 5 || ptes[4].part_table->sys_ind) {
+               struct pte *pe = &ptes[g_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;
+               g_partitions++;
+       }
+       add_partition(g_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, g_partitions), LINUX_NATIVE);
+               return;
+       }
+       if (LABEL_IS_SGI) {
+               sgi_add_partition(get_partition(0, g_partitions), LINUX_NATIVE);
+               return;
+       }
+       if (LABEL_IS_AIX) {
+               printf("Sorry - this fdisk cannot handle AIX disk labels.\n"
+"If you want to add DOS-type partitions, create a new empty DOS partition\n"
+"table first (use 'o'). 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 && g_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[80];
+               snprintf(line, sizeof(line),
+                       "Command action\n"
+                       "   %s\n"
+                       "   p   primary partition (1-4)\n",
+                       (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;
+                       }
+                       if (c == 'l' && extended_offset) {
+                               add_logical();
+                               return;
+                       }
+                       if (c == 'e' && !extended_offset) {
+                               i = get_nonexisting_partition(0, 4);
+                               if (i >= 0)
+                                       add_partition(i, EXTENDED);
+                               return;
+                       }
+                       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 < g_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 i;
+
+       printf("Calling ioctl() to re-read partition table\n");
+       sync();
+       /* sleep(2); Huh? */
+       i = ioctl_or_perror(dev_fd, BLKRRPART, NULL,
+                       "WARNING: rereading partition table "
+                       "failed, kernel still uses old table");
+#if 0
+       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");
+#endif
+
+       if (leave) {
+               if (ENABLE_FEATURE_CLEAN_UP)
+                       close_dev_fd();
+               exit(i != 0);
+       }
+}
+#endif /* 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) {
+                       bb_putchar('\n');
+                       l = -1;
+               }
+       }
+       if (l > 0)
+               bb_putchar('\n');
+       bb_putchar('\n');
+}
+
+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 < g_partitions; i++)
+                       print_buffer(ptes[i].sectorbuffer);
+       }
+}
+
+static void
+move_begin(int i)
+{
+       struct pte *pe = &ptes[i];
+       struct partition *p = pe->part_table;
+       ullong 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) {
+               bb_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, g_partitions));
+                       break;
+               case 'c':
+                       user_cylinders = g_cylinders =
+                               read_int(1, g_cylinders, 1048576, 0,
+                                       "Number of cylinders");
+                       if (LABEL_IS_SUN)
+                               sun_set_ncyl(g_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 = g_heads = read_int(1, g_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':
+                       if (ENABLE_FEATURE_CLEAN_UP)
+                               close_dev_fd();
+                       bb_putchar('\n');
+                       exit(EXIT_SUCCESS);
+               case 'r':
+                       return;
+               case 's':
+                       user_sectors = g_sectors = read_int(1, g_sectors, 63, 0,
+                                          "Number of sectors");
+                       if (dos_compatible_flag) {
+                               sector_offset = g_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_for_read(buf);
+       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
+open_list_and_close(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;
+
+       /* Open disk_device, save file descriptor to dev_fd */
+       errno = 0;
+       gb = get_boot(TRY_ONLY);
+       if (gb > 0) {   /* I/O error */
+               /* Ignore other errors, since we try IDE
+                  and SCSI hard disks which may not be
+                  installed on the system. */
+               if (user_specified || errno == EACCES)
+                       bb_perror_msg("can't open '%s'", device);
+               return;
+       }
+
+       if (gb < 0) { /* no DOS signature */
+               list_disk_geometry();
+               if (LABEL_IS_AIX)
+                       goto ret;
+#if ENABLE_FEATURE_OSF_LABEL
+               if (bsd_trydev(device) < 0)
+#endif
+                       printf("Disk %s doesn't contain a valid "
+                               "partition table\n", device);
+       } else {
+               list_table(0);
+#if ENABLE_FEATURE_FDISK_WRITABLE
+               if (!LABEL_IS_SUN && g_partitions > 4) {
+                       delete_partition(ext_index);
+               }
+#endif
+       }
+ ret:
+       close_dev_fd();
+}
+
+/* for fdisk -l: try all things in /proc/partitions
+   that look like a partition name (do not end in a digit) */
+static void
+list_devs_in_proc_partititons(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++)
+                       continue;
+               if (isdigit(s[-1]))
+                       continue;
+               sprintf(devname, "/dev/%s", ptname);
+               open_list_and_close(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) MAIN_EXTERNALLY_VISIBLE;
+int fdisk_main(int argc, char **argv)
+{
+       unsigned opt;
+       /*
+        *  fdisk -v
+        *  fdisk -l [-b sectorsize] [-u] device ...
+        *  fdisk -s [partition] ...
+        *  fdisk [-b sectorsize] [-u] device
+        *
+        * Options -C, -H, -S set the geometry.
+        */
+       INIT_G();
+
+       close_dev_fd(); /* needed: fd 3 must not stay closed */
+
+       opt_complementary = "b+:C+:H+:S+"; /* numeric params */
+       opt = getopt32(argv, "b:C:H:lS:u" USE_FEATURE_FDISK_BLKSIZE("s"),
+                               &sector_size, &user_cylinders, &user_heads, &user_sectors);
+       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.
+               */
+               if (sector_size != 512 && sector_size != 1024
+                && sector_size != 2048)
+                       bb_show_usage();
+               sector_offset = 2;
+               user_set_sector_size = 1;
+       }
+       if (user_heads <= 0 || user_heads >= 256)
+               user_heads = 0;
+       if (user_sectors <= 0 || user_sectors >= 64)
+               user_sectors = 0;
+       if (opt & OPT_u)
+               display_in_cyl_units = 0; // -u
+
+#if ENABLE_FEATURE_FDISK_WRITABLE
+       if (opt & OPT_l) {
+               nowarn = 1;
+#endif
+               if (*argv) {
+                       listing = 1;
+                       do {
+                               open_list_and_close(*argv, 1);
+                       } while (*++argv);
+               } else {
+                       /* we don't have device names, */
+                       /* use /proc/partitions instead */
+                       list_devs_in_proc_partititons();
+               }
+               return 0;
+#if ENABLE_FEATURE_FDISK_WRITABLE
+       }
+#endif
+
+#if ENABLE_FEATURE_FDISK_BLKSIZE
+       if (opt & OPT_s) {
+               int j;
+
+               nowarn = 1;
+               if (argc <= 0)
+                       bb_show_usage();
+               for (j = 0; j < argc; j++) {
+                       unsigned long long size;
+                       fd = xopen(argv[j], O_RDONLY);
+                       size = bb_BLKGETSIZE_sectors(fd) / 2;
+                       close(fd);
+                       if (argc == 1)
+                               printf("%lld\n", size);
+                       else
+                               printf("%s: %lld\n", argv[j], size);
+               }
+               return 0;
+       }
+#endif
+
+#if ENABLE_FEATURE_FDISK_WRITABLE
+       if (argc != 1)
+               bb_show_usage();
+
+       disk_device = argv[0];
+       get_boot(OPEN_MAIN);
+
+       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) {
+               int c;
+               bb_putchar('\n');
+               c = tolower(read_nonempty("Command (m for help): "));
+               switch (c) {
+               case 'a':
+                       if (LABEL_IS_DOS)
+                               toggle_active(get_partition(1, g_partitions));
+                       else if (LABEL_IS_SUN)
+                               toggle_sunflags(get_partition(1, g_partitions),
+                                               0x01);
+                       else if (LABEL_IS_SGI)
+                               sgi_set_bootpartition(
+                                       get_partition(1, g_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, g_partitions),
+                                               0x10);
+                       else if (LABEL_IS_SGI)
+                               sgi_set_swappartition(
+                                               get_partition(1, g_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, g_partitions);
+                               } else {
+                                       j = get_partition(1, g_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':
+                       if (ENABLE_FEATURE_CLEAN_UP)
+                               close_dev_fd();
+                       bb_putchar('\n');
+                       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 /* FEATURE_FDISK_WRITABLE */
+}
diff --git a/util-linux/fdisk_aix.c b/util-linux/fdisk_aix.c
new file mode 100644 (file)
index 0000000..2c0d2a6
--- /dev/null
@@ -0,0 +1,73 @@
+#if ENABLE_FEATURE_AIX_LABEL
+/*
+ * Copyright (C) Andreas Neuper, Sep 1998.
+ *
+ * Licensed under GPLv2, see file LICENSE in this tarball for details.
+ */
+
+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 smallint aix_other_endian; /* bool */
+static smallint aix_volumes = 1; /* max 15 */
+
+/*
+ * only dealing with free blocks here
+ */
+
+static void
+aix_info(void)
+{
+       puts("\n"
+"There is a valid AIX label on this disk.\n"
+"Unfortunately Linux cannot handle these disks at the moment.\n"
+"Nevertheless some advice:\n"
+"1. fdisk will destroy its contents on write.\n"
+"2. Be sure that this disk is NOT a still vital part of a volume group.\n"
+"   (Otherwise you may erase the other disks as well, if unmirrored.)\n"
+"3. Before deleting this physical volume be sure to remove the disk\n"
+"   logically from your AIX machine. (Otherwise you become an AIXpert).\n"
+       );
+}
+
+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;
+       g_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 (file)
index 0000000..ea5cd3c
--- /dev/null
@@ -0,0 +1,1052 @@
+/*
+ * 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.
+ */
+
+#if ENABLE_FEATURE_OSF_LABEL
+
+#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
+};
+
+
+/*
+ * 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 char *const 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
+};
+
+
+/*
+ * 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()
+*/
+
+#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 uint16_t xbsd_dkcksum(struct xbsd_disklabel *lp);
+static int xbsd_initlabel(struct partition *p);
+static int xbsd_readlabel(struct partition *p);
+static int xbsd_writelabel(struct partition *p);
+
+#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
+
+
+/* Group big globals data and allocate it in one go */
+struct bsd_globals {
+/* We access this through a uint64_t * when checksumming */
+/* hopefully xmalloc gives us required alignment */
+       char disklabelbuffer[BSD_BBSIZE];
+       struct xbsd_disklabel xbsd_dlabel;
+};
+
+static struct bsd_globals *bsd_globals_ptr;
+
+#define disklabelbuffer (bsd_globals_ptr->disklabelbuffer)
+#define xbsd_dlabel     (bsd_globals_ptr->xbsd_dlabel)
+
+
+/* Code */
+
+#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) == 0)
+               return 0;
+       return 1;
+}
+
+static int
+bsd_trydev(const char * dev)
+{
+       if (xbsd_readlabel(NULL) == 0)
+               return -1;
+       printf("\nBSD label for device: %s\n", dev);
+       xbsd_print_disklabel(0);
+       return 0;
+}
+
+static void
+bsd_menu(void)
+{
+       puts("Command Action");
+       puts("d\tdelete a BSD partition");
+       puts("e\tedit drive data");
+       puts("i\tinstall bootstrap");
+       puts("l\tlist known filesystem types");
+       puts("n\tadd a new BSD partition");
+       puts("p\tprint BSD partition table");
+       puts("q\tquit without saving changes");
+       puts("r\treturn to main menu");
+       puts("s\tshow complete disklabel");
+       puts("t\tchange a partition's filesystem id");
+       puts("u\tchange units (cylinders/sectors)");
+       puts("w\twrite disklabel to disk");
+#if !defined(__alpha__)
+       puts("x\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) {
+                               printf("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) == 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) == 0)
+               if (xbsd_create_disklabel() == 0)
+                       exit(EXIT_SUCCESS);
+
+#endif
+
+       while (1) {
+               bb_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':
+                       if (ENABLE_FEATURE_CLEAN_UP)
+                               close_dev_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:
+                       bsd_menu();
+                       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) {
+               static const int d_masks[] = { BSD_D_REMOVABLE, BSD_D_ECC, BSD_D_BADSECT };
+
+#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 < ARRAY_SIZE(xbsd_dktypenames)-1)
+                       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: ");
+               print_flags_separated(d_masks, "removable\0""ecc\0""badsect\0", lp->d_flags, " ");
+               bb_putchar('\n');
+               /* 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 < ARRAY_SIZE(xbsd_fstypes)-1)
+                               printf("%8.8s", xbsd_fstypes[pp->p_fstype]);
+                       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;
+                       }
+                       bb_putchar('\n');
+               }
+       }
+}
+
+static void
+xbsd_write_disklabel(void)
+{
+#if defined(__alpha__)
+       printf("Writing disklabel to %s\n", disk_device);
+       xbsd_writelabel(NULL);
+#else
+       printf("Writing disklabel to %s\n",
+               partname(disk_device, xbsd_part_index + 1, 0));
+       xbsd_writelabel(xbsd_part);
+#endif
+       reread_partition_table(0);      /* no exit yet */
+}
+
+static int
+xbsd_create_disklabel(void)
+{
+       char c;
+
+#if defined(__alpha__)
+       printf("%s contains no disklabel\n", disk_device);
+#else
+       printf("%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
+#else
+                               xbsd_part
+#endif
+                       ) == 1) {
+                               xbsd_print_disklabel(1);
+                               return 1;
+                       }
+                       return 0;
+               }
+               if (c == 'n')
+                       return 0;
+       }
+}
+
+static int
+edit_int(int def, const char *mesg)
+{
+       mesg = xasprintf("%s (%d): ", mesg, def);
+       do {
+               if (!read_line(mesg))
+                       goto ret;
+       } while (!isdigit(*line_ptr));
+       def = atoi(line_ptr);
+ ret:
+       free((char*)mesg);
+       return def;
+}
+
+static void
+xbsd_edit_disklabel(void)
+{
+       struct xbsd_disklabel *d;
+
+       d = &xbsd_dlabel;
+
+#if defined(__alpha__) || defined(__ia64__)
+       d->d_secsize    = edit_int(d->d_secsize     , "bytes/sector");
+       d->d_nsectors   = edit_int(d->d_nsectors    , "sectors/track");
+       d->d_ntracks    = edit_int(d->d_ntracks     , "tracks/cylinder");
+       d->d_ncylinders = edit_int(d->d_ncylinders  , "cylinders");
+#endif
+
+       /* d->d_secpercyl can be != d->d_nsectors * d->d_ntracks */
+       while (1) {
+               d->d_secpercyl = edit_int(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        = edit_int(d->d_rpm       , "rpm");
+       d->d_interleave = edit_int(d->d_interleave, "interleave");
+       d->d_trackskew  = edit_int(d->d_trackskew , "trackskew");
+       d->d_cylskew    = edit_int(d->d_cylskew   , "cylinderskew");
+       d->d_headswitch = edit_int(d->d_headswitch, "headswitch");
+       d->d_trkseek    = edit_int(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;
+
+       fdb = open_or_warn(path, O_RDONLY);
+       if (fdb < 0) {
+               return 0;
+       }
+       if (full_read(fdb, ptr, size) < 0) {
+               bb_simple_perror_msg(path);
+               close(fdb);
+               return 0;
+       }
+       printf(" ... %s\n", path);
+       close(fdb);
+       return 1;
+}
+
+static void
+sync_disks(void)
+{
+       printf("Syncing disks\n");
+       sync();
+       /* sleep(4); What? */
+}
+
+static void
+xbsd_write_bootstrap(void)
+{
+       char path[MAXPATHLEN];
+       const char *bootdir = BSD_LINUX_BOOTDIR;
+       const 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";
+
+       snprintf(path, sizeof(path), "Bootstrap: %sboot -> boot%s (%s): ",
+               dkbasename, dkbasename, dkbasename);
+       if (read_line(path)) {
+               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) {
+                       printf("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
+
+       seek_sector(sector);
+       xwrite(dev_fd, disklabelbuffer, BSD_BBSIZE);
+
+#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[sizeof("Partition (a-%c): ") + 16];
+       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) {
+                       printf("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) {
+               printf("This partition already exists\n");
+               return 0;
+       }
+
+       return 1;
+}
+
+static void
+xbsd_list_types(void)
+{
+       list_types(xbsd_fstypes);
+}
+
+static uint16_t
+xbsd_dkcksum(struct xbsd_disklabel *lp)
+{
+       uint16_t *start, *end;
+       uint16_t sum = 0;
+
+       start = (uint16_t *) lp;
+       end = (uint16_t *) &lp->d_partitions[lp->d_npartitions];
+       while (start < end)
+               sum ^= *start++;
+       return sum;
+}
+
+static int
+xbsd_initlabel(struct partition *p)
+{
+       struct xbsd_disklabel *d = &xbsd_dlabel;
+       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 = g_sectors;            /* sectors/track */
+       d->d_ntracks = g_heads;               /* tracks/cylinder (heads) */
+       d->d_ncylinders = g_cylinders;
+       d->d_secpercyl  = g_sectors * g_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 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 whole disk */
+
+       pp->p_offset = 0;
+       pp->p_size   = d->d_secperunit;
+       pp->p_fstype = BSD_FS_UNUSED;
+#else
+       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;
+
+       if (!bsd_globals_ptr)
+               bsd_globals_ptr = xzalloc(sizeof(*bsd_globals_ptr));
+
+       d = &xbsd_dlabel;
+
+       /* p is used only to get the starting sector */
+#if !defined(__alpha__)
+       sector = (p ? get_start_sect(p) : 0);
+#else
+       sector = 0;
+#endif
+
+       seek_sector(sector);
+       if (BSD_BBSIZE != full_read(dev_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)
+               printf("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 = &xbsd_dlabel;
+       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);
+       seek_sector(0);
+       xwrite(dev_fd, disklabelbuffer, BSD_BBSIZE);
+#else
+       seek_sector(sector);
+       lseek(dev_fd, BSD_LABELOFFSET, SEEK_CUR);
+       xwrite(dev_fd, d, sizeof(*d));
+#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, g_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__)
+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__ */
+
+/* Undefine 'global' tricks */
+#undef disklabelbuffer
+#undef xbsd_dlabel
+
+#endif /* OSF_LABEL */
diff --git a/util-linux/fdisk_sgi.c b/util-linux/fdisk_sgi.c
new file mode 100644 (file)
index 0000000..51cf30c
--- /dev/null
@@ -0,0 +1,886 @@
+/*
+ * Copyright (C) Andreas Neuper, Sep 1998.
+ *
+ * Licensed under GPLv2, see file LICENSE in this tarball for details.
+ */
+
+#if ENABLE_FEATURE_SGI_LABEL
+
+#define SGI_DEBUG 0
+
+#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
+
+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 ? fdisk_swap16(x) : (uint16_t)(x))
+#define SGI_SSWAP32(x) (sgi_other_endian ? fdisk_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 smallint sgi_other_endian; /* bool */
+static smallint sgi_volumes = 1; /* max 15 */
+
+/*
+ * 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 char *const 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;
+}
+
+void BUG_bad_sgi_partition_size(void);
+
+static int
+check_sgi_label(void)
+{
+       if (sizeof(sgi_partition) > 512) {
+               /* According to MIPS Computer Systems, Inc the label
+                * must not contain more than 512 bytes */
+               BUG_bad_sgi_partition_size();
+       }
+
+       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))) {
+               printf("Detected sgi disklabel with wrong checksum\n");
+       }
+       update_units();
+       current_label_type = LABEL_SGI;
+       g_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, g_heads, g_sectors, g_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, g_heads, g_sectors, g_cylinders,
+                       str_units(PLURAL), units_per_sector );
+       }
+
+       w = strlen(disk_device);
+       wd = sizeof("Device") - 1;
+       if (w < wd)
+       w = wd;
+
+       printf("----- partitions -----\n"
+               "Pt# %*s  Info     Start       End   Sectors  Id  System\n",
+               w + 2, "Device");
+       for (i = 0; i < g_partitions; i++) {
+               if (sgi_get_num_sectors(i) || SGI_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 g_heads * g_sectors * g_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;
+       }
+       if (strlen(aFile) > 16) {
+               printf("\nName of Bootfile too long (>16 bytes)\n");
+               return 0;
+       }
+       if (aFile[0] != '/') {
+               printf("\nBootfile must have a fully qualified pathname\n");
+               return 0;
+       }
+       if (strncmp(aFile, (char*)sgilabel->boot_file, 16)) {
+               printf("\nBe aware, that the bootfile is not checked for existence.\n"
+                        "\tSGI'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);
+
+       write_sector(0, sgilabel);
+       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);
+               write_sector(infostartblock, info);
+               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 (SGI_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 (SGI_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 (SGI_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 (SGI_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("Partitions %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-%8u\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 (SGI_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-%8u\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") != 0)
+                       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("Partition overlap detected\n");
+}
+
+static void
+sgi_set_entire(void)
+{
+       int n;
+
+       for (n = 10; n < g_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 < g_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 (g_heads * g_sectors * 5 < sgi_get_lastblock())
+                       sgi_set_partition(n, 0, g_heads * g_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(msg_part_already_defined, 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)
+                       break;
+               printf("You will get a partition overlap on the disk. "
+                               "Fix it first!\n");
+       }
+       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);
+}
+
+#if ENABLE_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 */
+
+       printf(msg_building_new_label, "SGI disklabel");
+
+       sgi_other_endian = BB_LITTLE_ENDIAN;
+       res = ioctl(dev_fd, BLKGETSIZE, &longsectors);
+       if (!ioctl(dev_fd, HDIO_GETGEO, &geometry)) {
+               g_heads = geometry.heads;
+               g_sectors = geometry.sectors;
+               if (res == 0) {
+                       /* the get device size ioctl was successful */
+                       g_cylinders = longsectors / (g_heads * g_sectors);
+                       g_cylinders /= sec_fac;
+               } else {
+                       /* otherwise print error and use truncated version */
+                       g_cylinders = geometry.cylinders;
+                       printf(
+"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, g_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 (SGI_DEBUG)
+                                       printf("ID=%02x\tSTART=%d\tLENGTH=%d\n",
+                               old[i].sysid, old[i].start, old[i].nsect);
+                       }
+               }
+       }
+
+       memset(MBRbuffer, 0, sizeof(MBRbuffer));
+       /* fields with '//' are already zeroed out by memset above */
+
+       sgilabel->magic = SGI_SSWAP32(SGI_LABEL_MAGIC);
+       //sgilabel->boot_part = SGI_SSWAP16(0);
+       sgilabel->swap_part = SGI_SSWAP16(1);
+
+       //memset(sgilabel->boot_file, 0, 16);
+       strcpy((char*)sgilabel->boot_file, "/unix"); /* sizeof(sgilabel->boot_file) == 16 > 6 */
+
+       //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);
+       /* tracks/cylinder (heads) */
+       sgilabel->devparam.ntrks                    = SGI_SSWAP16(geometry.heads);
+       //sgilabel->devparam.cmd_tag_queue_depth      = (0);
+       //sgilabel->devparam.unused0                  = (0);
+       //sgilabel->devparam.unused1                  = SGI_SSWAP16(0);
+       /* sectors/track */
+       sgilabel->devparam.nsect                    = SGI_SSWAP16(geometry.sectors);
+       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;
+       g_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 /* FEATURE_FDISK_ADVANCED */
+
+/* _____________________________________________________________
+ */
+
+static sgiinfo *
+fill_sgiinfo(void)
+{
+       sgiinfo *info = xzalloc(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 (file)
index 0000000..9cdf869
--- /dev/null
@@ -0,0 +1,728 @@
+/*
+ * fdisk_sun.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
+ *
+ * Licensed under GPLv2, see file LICENSE in this tarball for details.
+ */
+
+#if ENABLE_FEATURE_SUN_LABEL
+
+#define SUNOS_SWAP 3
+#define SUN_WHOLE_DISK 5
+
+#define SUN_LABEL_MAGIC          0xDABE
+#define SUN_LABEL_MAGIC_SWAPPED  0xBEDA
+#define SUN_SSWAP16(x) (sun_other_endian ? fdisk_swap16(x) : (uint16_t)(x))
+#define SUN_SSWAP32(x) (sun_other_endian ? fdisk_swap32(x) : (uint32_t)(x))
+
+/* Copied from linux/major.h */
+#define FLOPPY_MAJOR    2
+
+#define SCSI_IOCTL_GET_IDLUN 0x5382
+
+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(dev_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 char *const 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 / (g_heads * g_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) {
+               printf("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 {
+               g_heads = SUN_SSWAP16(sunlabel->ntrks);
+               g_cylinders = SUN_SSWAP16(sunlabel->ncyl);
+               g_sectors = SUN_SSWAP16(sunlabel->nsect);
+       }
+       update_units();
+       current_label_type = LABEL_SUN;
+       g_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(dev_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_for_read("/proc/scsi/scsi");
+       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 < ARRAY_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 ndiv;
+       unsigned char c;
+       const struct sun_predefined_drives *p = NULL;
+
+       printf(msg_building_new_label, "sun disklabel");
+
+       sun_other_endian = BB_LITTLE_ENDIAN;
+       memset(MBRbuffer, 0, sizeof(MBRbuffer));
+       sunlabel->magic = SUN_SSWAP16(SUN_LABEL_MAGIC);
+       if (!floppy) {
+               unsigned i;
+               puts("Drive type\n"
+                "   ?   auto configure\n"
+                "   0   custom (with hardware detected defaults)");
+               for (i = 0; i < ARRAY_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 == '0') {
+                               break;
+                       }
+                       if (c >= 'a' && c < 'a' + ARRAY_SIZE(sun_drives)) {
+                               p = sun_drives + c - 'a';
+                               break;
+                       }
+                       if (c >= 'A' && c < 'A' + ARRAY_SIZE(sun_drives)) {
+                               p = sun_drives + c - 'A';
+                               break;
+                       }
+                       if (c == '?' && scsi_disk) {
+                               p = sun_autoconfigure_scsi();
+                               if (p)
+                                       break;
+                               printf("Autoconfigure failed\n");
+                       }
+               }
+       }
+       if (!p || floppy) {
+               if (!ioctl(dev_fd, HDIO_GETGEO, &geometry)) {
+                       g_heads = geometry.heads;
+                       g_sectors = geometry.sectors;
+                       g_cylinders = geometry.cylinders;
+               } else {
+                       g_heads = 0;
+                       g_sectors = 0;
+                       g_cylinders = 0;
+               }
+               if (floppy) {
+                       sunlabel->nacyl = 0;
+                       sunlabel->pcylcount = SUN_SSWAP16(g_cylinders);
+                       sunlabel->rspeed = SUN_SSWAP16(300);
+                       sunlabel->ilfact = SUN_SSWAP16(1);
+                       sunlabel->sparecyl = 0;
+               } else {
+                       g_heads = read_int(1, g_heads, 1024, 0, "Heads");
+                       g_sectors = read_int(1, g_sectors, 1024, 0, "Sectors/track");
+               if (g_cylinders)
+                       g_cylinders = read_int(1, g_cylinders - 2, 65535, 0, "Cylinders");
+               else
+                       g_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, g_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, g_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);
+               g_cylinders = p->ncyl;
+               g_heads = p->ntrks;
+               g_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"),
+               g_cylinders, SUN_SSWAP16(sunlabel->nacyl), g_heads, g_sectors);
+
+       sunlabel->ntrks = SUN_SSWAP16(g_heads);
+       sunlabel->nsect = SUN_SSWAP16(g_sectors);
+       sunlabel->ncyl = SUN_SSWAP16(g_cylinders);
+       if (floppy)
+               set_sun_partition(0, 0, g_cylinders * g_heads * g_sectors, LINUX_NATIVE);
+       else {
+               if (g_cylinders * g_heads * g_sectors >= 150 * 2048) {
+                       ndiv = g_cylinders - (50 * 2048 / (g_heads * g_sectors)); /* 50M swap */
+               } else
+                       ndiv = g_cylinders * 2 / 3;
+               set_sun_partition(0, 0, ndiv * g_heads * g_sectors, LINUX_NATIVE);
+               set_sun_partition(1, ndiv * g_heads * g_sectors, g_cylinders * g_heads * g_sectors, LINUX_SWAP);
+               sunlabel->infos[1].flags |= 0x01; /* Not mountable */
+       }
+       set_sun_partition(2, 0, g_cylinders * g_heads * g_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 = g_cylinders * g_heads * g_sectors;
+       for (i = 0; i < g_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) * g_heads * g_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] % (g_heads * g_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, 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 = g_cylinders * g_heads * g_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(msg_part_already_defined, 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.\n"
+                               "Delete/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 + g_heads * g_sectors - 1) / (g_heads * g_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 < g_partitions; i++)
+                       if (lens[i] && starts[i] <= first && starts[i] + lens[i] > first)
+                               break;
+               if (i < g_partitions && !whole_disk) {
+                       if (n == 2 && !first) {
+                               whole_disk = 1;
+                               break;
+                       }
+                       printf("Sector %d is already allocated\n", first);
+               } else
+                       break;
+       }
+       stop = g_cylinders * g_heads * g_sectors;
+       stop2 = stop;
+       for (i = 0; i < g_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,\n"
+"but your value %d %s covers some other partition.\n"
+"Your entry has been changed 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)) == g_heads * g_sectors * g_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, g_heads, g_sectors, SUN_SSWAP16(sunlabel->rspeed),
+                       g_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, g_heads, g_sectors, g_cylinders,
+                       str_units(PLURAL), units_per_sector);
+
+       printf("%*s Flag    Start       End    Blocks   Id  System\n",
+               w + 1, "Device");
+       for (i = 0; i < g_partitions; i++) {
+               if (sunlabel->partitions[i].num_sectors) {
+                       uint32_t start = SUN_SSWAP32(sunlabel->partitions[i].start_cylinder) * g_heads * g_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 */
+               }
+       }
+}
+
+#if ENABLE_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), g_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 /* 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;
+       write_sector(0, sunlabel);
+}
+#endif /* SUN_LABEL */
diff --git a/util-linux/findfs.c b/util-linux/findfs.c
new file mode 100644 (file)
index 0000000..5b64399
--- /dev/null
@@ -0,0 +1,38 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Support functions for mounting devices by label/uuid
+ *
+ * Copyright (C) 2006 by Jason Schoon <floydpink@gmail.com>
+ * Some portions cribbed from e2fsprogs, util-linux, dosfstools
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.h"
+#include "volume_id.h"
+
+int findfs_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int findfs_main(int argc, char **argv)
+{
+       char *tmp = NULL;
+
+       if (argc != 2)
+               bb_show_usage();
+
+       if (!strncmp(argv[1], "LABEL=", 6))
+               tmp = get_devname_from_label(argv[1] + 6);
+       else if (!strncmp(argv[1], "UUID=", 5))
+               tmp = get_devname_from_uuid(argv[1] + 5);
+       else if (!strncmp(argv[1], "/dev/", 5)) {
+               /* Just pass a device name right through.  This might aid in some scripts
+               being able to call this unconditionally */
+               tmp = argv[1];
+       } else
+               bb_show_usage();
+
+       if (tmp) {
+               puts(tmp);
+               return 0;
+       }
+       return 1;
+}
diff --git a/util-linux/freeramdisk.c b/util-linux/freeramdisk.c
new file mode 100644 (file)
index 0000000..bde6afc
--- /dev/null
@@ -0,0 +1,33 @@
+/* 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 "libbb.h"
+
+/* From <linux/fd.h> */
+#define FDFLUSH  _IO(2,0x4b)
+
+int freeramdisk_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int freeramdisk_main(int argc, char **argv)
+{
+       int fd;
+
+       if (argc != 2) bb_show_usage();
+
+       fd = xopen(argv[1], O_RDWR);
+
+       // Act like freeramdisk, fdflush, or both depending on configuration.
+       ioctl_or_perror_and_die(fd, (ENABLE_FREERAMDISK && applet_name[1]=='r')
+                       || !ENABLE_FDFLUSH ? BLKFLSBUF : FDFLUSH, NULL, "%s", argv[1]);
+
+       if (ENABLE_FEATURE_CLEAN_UP) close(fd);
+
+       return EXIT_SUCCESS;
+}
diff --git a/util-linux/fsck_minix.c b/util-linux/fsck_minix.c
new file mode 100644 (file)
index 0000000..78a7c82
--- /dev/null
@@ -0,0 +1,1309 @@
+/* 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
+ *                           superblock 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 Mickiewicz <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 superblock 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 <mntent.h>
+#include "libbb.h"
+#include "minix.h"
+
+#ifndef BLKGETSIZE
+#define BLKGETSIZE _IO(0x12,96)    /* return device size */
+#endif
+
+struct BUG_bad_inode_size {
+       char BUG_bad_inode1_size[(INODE_SIZE1 * MINIX1_INODES_PER_BLOCK != BLOCK_SIZE) ? -1 : 1];
+#if ENABLE_FEATURE_MINIX2
+       char BUG_bad_inode2_size[(INODE_SIZE2 * MINIX2_INODES_PER_BLOCK != BLOCK_SIZE) ? -1 : 1];
+#endif
+};
+
+enum {
+#ifdef UNUSED
+       MINIX1_LINK_MAX = 250,
+       MINIX2_LINK_MAX = 65530,
+       MINIX_I_MAP_SLOTS = 8,
+       MINIX_Z_MAP_SLOTS = 64,
+       MINIX_V1 = 0x0001,      /* original minix fs */
+       MINIX_V2 = 0x0002,      /* minix V2 fs */
+#endif
+       MINIX_NAME_MAX = 255,         /* # chars in a file name */
+};
+
+
+#if !ENABLE_FEATURE_MINIX2
+enum { version2 = 0 };
+#endif
+
+enum { MAX_DEPTH = 32 };
+
+enum { dev_fd = 3 };
+
+struct globals {
+#if ENABLE_FEATURE_MINIX2
+       smallint version2;
+#endif
+       smallint changed;  /* is filesystem modified? */
+       smallint errors_uncorrected;  /* flag if some error was not corrected */
+       smallint termios_set;
+       smallint dirsize;
+       smallint namelen;
+       const char *device_name;
+       int directory, regular, blockdev, chardev, links, symlinks, total;
+       char *inode_buffer;
+
+       char *inode_map;
+       char *zone_map;
+
+       unsigned char *inode_count;
+       unsigned char *zone_count;
+
+       /* File-name data */
+       int name_depth;
+       char *name_component[MAX_DEPTH+1];
+
+       /* Bigger stuff */
+       struct termios sv_termios;
+       char superblock_buffer[BLOCK_SIZE];
+       char add_zone_ind_blk[BLOCK_SIZE];
+       char add_zone_dind_blk[BLOCK_SIZE];
+       USE_FEATURE_MINIX2(char add_zone_tind_blk[BLOCK_SIZE];)
+       char check_file_blk[BLOCK_SIZE];
+
+       /* File-name data */
+       char current_name[MAX_DEPTH * MINIX_NAME_MAX];
+};
+
+#define G (*ptr_to_globals)
+#if ENABLE_FEATURE_MINIX2
+#define version2           (G.version2           )
+#endif
+#define changed            (G.changed            )
+#define errors_uncorrected (G.errors_uncorrected )
+#define termios_set        (G.termios_set        )
+#define dirsize            (G.dirsize            )
+#define namelen            (G.namelen            )
+#define device_name        (G.device_name        )
+#define directory          (G.directory          )
+#define regular            (G.regular            )
+#define blockdev           (G.blockdev           )
+#define chardev            (G.chardev            )
+#define links              (G.links              )
+#define symlinks           (G.symlinks           )
+#define total              (G.total              )
+#define inode_buffer       (G.inode_buffer       )
+#define inode_map          (G.inode_map          )
+#define zone_map           (G.zone_map           )
+#define inode_count        (G.inode_count        )
+#define zone_count         (G.zone_count         )
+#define name_depth         (G.name_depth         )
+#define name_component     (G.name_component     )
+#define sv_termios         (G.sv_termios         )
+#define superblock_buffer  (G.superblock_buffer )
+#define add_zone_ind_blk   (G.add_zone_ind_blk   )
+#define add_zone_dind_blk  (G.add_zone_dind_blk  )
+#define add_zone_tind_blk  (G.add_zone_tind_blk  )
+#define check_file_blk     (G.check_file_blk     )
+#define current_name       (G.current_name       )
+#define INIT_G() do { \
+       SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
+       dirsize = 16; \
+       namelen = 14; \
+       current_name[0] = '/'; \
+       /*current_name[1] = '\0';*/ \
+       name_component[0] = &current_name[0]; \
+} while (0)
+
+
+#define OPTION_STR "larvsmf"
+enum {
+       OPT_l = (1 << 0),
+       OPT_a = (1 << 1),
+       OPT_r = (1 << 2),
+       OPT_v = (1 << 3),
+       OPT_s = (1 << 4),
+       OPT_w = (1 << 5),
+       OPT_f = (1 << 6),
+};
+#define OPT_list      (option_mask32 & OPT_l)
+#define OPT_automatic (option_mask32 & OPT_a)
+#define OPT_repair    (option_mask32 & OPT_r)
+#define OPT_verbose   (option_mask32 & OPT_v)
+#define OPT_show      (option_mask32 & OPT_s)
+#define OPT_warn_mode (option_mask32 & OPT_w)
+#define OPT_force     (option_mask32 & OPT_f)
+/* non-automatic repairs requested? */
+#define OPT_manual    ((option_mask32 & (OPT_a|OPT_r)) == OPT_r)
+
+
+#define Inode1 (((struct minix1_inode *) inode_buffer)-1)
+#define Inode2 (((struct minix2_inode *) inode_buffer)-1)
+
+#define Super (*(struct minix_superblock *)(superblock_buffer))
+
+#if ENABLE_FEATURE_MINIX2
+# define ZONES    ((unsigned)(version2 ? Super.s_zones : Super.s_nzones))
+#else
+# define ZONES    ((unsigned)(Super.s_nzones))
+#endif
+#define INODES    ((unsigned)Super.s_ninodes)
+#define IMAPS     ((unsigned)Super.s_imap_blocks)
+#define ZMAPS     ((unsigned)Super.s_zmap_blocks)
+#define FIRSTZONE ((unsigned)Super.s_firstdatazone)
+#define ZONESIZE  ((unsigned)Super.s_log_zone_size)
+#define MAXSIZE   ((unsigned)Super.s_max_size)
+#define MAGIC     (Super.s_magic)
+
+/* gcc likes this more (code is smaller) than macro variant */
+static ALWAYS_INLINE unsigned div_roundup(unsigned size, unsigned n)
+{
+       return (size + n-1) / n;
+}
+
+#if !ENABLE_FEATURE_MINIX2
+#define INODE_BLOCKS            div_roundup(INODES, MINIX1_INODES_PER_BLOCK)
+#else
+#define INODE_BLOCKS            div_roundup(INODES, \
+                                (version2 ? MINIX2_INODES_PER_BLOCK : MINIX1_INODES_PER_BLOCK))
+#endif
+
+#define INODE_BUFFER_SIZE       (INODE_BLOCKS * BLOCK_SIZE)
+#define NORM_FIRSTZONE          (2 + IMAPS + ZMAPS + INODE_BLOCKS)
+
+/* Before you ask "where they come from?": */
+/* setbit/clrbit are supplied by sys/param.h */
+
+static int minix_bit(const char *a, unsigned i)
+{
+       return (a[i >> 3] & (1<<(i & 7)));
+}
+
+static void minix_setbit(char *a, unsigned i)
+{
+       setbit(a, i);
+       changed = 1;
+}
+static void minix_clrbit(char *a, unsigned i)
+{
+       clrbit(a, i);
+       changed = 1;
+}
+
+/* Note: do not assume 0/1, it is 0/nonzero */
+#define zone_in_use(x)  (minix_bit(zone_map,(x)-FIRSTZONE+1))
+#define inode_in_use(x) (minix_bit(inode_map,(x)))
+
+#define mark_inode(x)   (minix_setbit(inode_map,(x)))
+#define unmark_inode(x) (minix_clrbit(inode_map,(x)))
+
+#define mark_zone(x)    (minix_setbit(zone_map,(x)-FIRSTZONE+1))
+#define unmark_zone(x)  (minix_clrbit(zone_map,(x)-FIRSTZONE+1))
+
+
+static void recursive_check(unsigned ino);
+#if ENABLE_FEATURE_MINIX2
+static void recursive_check2(unsigned ino);
+#endif
+
+static void die(const char *str) NORETURN;
+static void die(const char *str)
+{
+       if (termios_set)
+               tcsetattr_stdin_TCSANOW(&sv_termios);
+       bb_error_msg_and_die("%s", str);
+}
+
+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 (!OPT_repair) {
+               bb_putchar('\n');
+               errors_uncorrected = 1;
+               return 0;
+       }
+       if (OPT_automatic) {
+               bb_putchar('\n');
+               if (!def)
+                       errors_uncorrected = 1;
+               return def;
+       }
+       printf(def ? "%s (y/n)? " : "%s (n/y)? ", string);
+       for (;;) {
+               fflush(stdout);
+               c = getchar();
+               if (c == 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;
+//XXX:FIXME use find_mount_point()
+       f = setmntent(MOUNTED, "r");
+       if (f == 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;
+       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(EXIT_SUCCESS);
+       }
+}
+
+/*
+ * 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, smallint *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, smallint *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 nr, void *addr)
+{
+       if (!nr) {
+               memset(addr, 0, BLOCK_SIZE);
+               return;
+       }
+       xlseek(dev_fd, BLOCK_SIZE * nr, SEEK_SET);
+       if (BLOCK_SIZE != full_read(dev_fd, addr, BLOCK_SIZE)) {
+               printf("%s: bad block %u in file '%s'\n",
+                               bb_msg_read_error, nr, current_name);
+               errors_uncorrected = 1;
+               memset(addr, 0, BLOCK_SIZE);
+       }
+}
+
+/*
+ * write_block writes block nr to disk.
+ */
+static void write_block(unsigned nr, void *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;
+       }
+       xlseek(dev_fd, BLOCK_SIZE * nr, SEEK_SET);
+       if (BLOCK_SIZE != full_write(dev_fd, addr, BLOCK_SIZE)) {
+               printf("%s: bad block %u in file '%s'\n",
+                               bb_msg_write_error, nr, 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 minix1_inode *inode, unsigned blknr)
+{
+       uint16_t ind[BLOCK_SIZE >> 1];
+       int block, result;
+       smallint blk_chg;
+
+       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);
+               goto common;
+       }
+       blknr -= 512;
+       block = check_zone_nr(inode->i_zone + 8, &changed);
+       read_block(block, ind); /* double indirect */
+       blk_chg = 0;
+       result = check_zone_nr(&ind[blknr / 512], &blk_chg);
+       if (blk_chg)
+               write_block(block, ind);
+       block = result;
+ common:
+       read_block(block, ind);
+       blk_chg = 0;
+       result = check_zone_nr(&ind[blknr % 512], &blk_chg);
+       if (blk_chg)
+               write_block(block, ind);
+       return result;
+}
+
+#if ENABLE_FEATURE_MINIX2
+static int map_block2(struct minix2_inode *inode, unsigned blknr)
+{
+       uint32_t ind[BLOCK_SIZE >> 2];
+       int block, result;
+       smallint blk_chg;
+
+       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);
+               goto common2;
+       }
+       blknr -= 256;
+       if (blknr < 256 * 256) {
+               block = check_zone_nr2(inode->i_zone + 8, &changed);
+               goto common1;
+       }
+       blknr -= 256 * 256;
+       block = check_zone_nr2(inode->i_zone + 9, &changed);
+       read_block(block, ind); /* triple indirect */
+       blk_chg = 0;
+       result = check_zone_nr2(&ind[blknr / (256 * 256)], &blk_chg);
+       if (blk_chg)
+               write_block(block, ind);
+       block = result;
+ common1:
+       read_block(block, ind); /* double indirect */
+       blk_chg = 0;
+       result = check_zone_nr2(&ind[(blknr / 256) % 256], &blk_chg);
+       if (blk_chg)
+               write_block(block, ind);
+       block = result;
+ common2:
+       read_block(block, ind);
+       blk_chg = 0;
+       result = check_zone_nr2(&ind[blknr % 256], &blk_chg);
+       if (blk_chg)
+               write_block(block, ind);
+       return result;
+}
+#endif
+
+static void write_superblock(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 | MINIX_ERROR_FS;
+       if (!errors_uncorrected)
+               Super.s_state &= ~MINIX_ERROR_FS;
+
+       xlseek(dev_fd, BLOCK_SIZE, SEEK_SET);
+       if (BLOCK_SIZE != full_write(dev_fd, superblock_buffer, BLOCK_SIZE))
+               die("cannot write superblock");
+}
+
+static void write_tables(void)
+{
+       write_superblock();
+
+       if (IMAPS * BLOCK_SIZE != write(dev_fd, inode_map, IMAPS * BLOCK_SIZE))
+               die("cannot write inode map");
+       if (ZMAPS * BLOCK_SIZE != write(dev_fd, zone_map, ZMAPS * BLOCK_SIZE))
+               die("cannot write zone map");
+       if (INODE_BUFFER_SIZE != write(dev_fd, inode_buffer, INODE_BUFFER_SIZE))
+               die("cannot write inodes");
+}
+
+static void get_dirsize(void)
+{
+       int block;
+       char blk[BLOCK_SIZE];
+       int size;
+
+#if ENABLE_FEATURE_MINIX2
+       if (version2)
+               block = Inode2[MINIX_ROOT_INO].i_zone[0];
+       else
+#endif
+               block = Inode1[MINIX_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)
+{
+       xlseek(dev_fd, BLOCK_SIZE, SEEK_SET);
+       if (BLOCK_SIZE != full_read(dev_fd, superblock_buffer, BLOCK_SIZE))
+               die("cannot read superblock");
+       /* already initialized to:
+       namelen = 14;
+       dirsize = 16;
+       version2 = 0;
+       */
+       if (MAGIC == MINIX1_SUPER_MAGIC) {
+       } else if (MAGIC == MINIX1_SUPER_MAGIC2) {
+               namelen = 30;
+               dirsize = 32;
+#if ENABLE_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 superblock");
+       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 superblock");
+       if (ZMAPS * BLOCK_SIZE * 8 < ZONES - FIRSTZONE + 1)
+               die("bad s_zmap_blocks field in superblock");
+}
+
+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(dev_fd, inode_map, IMAPS * BLOCK_SIZE))
+               die("cannot read inode map");
+       if (ZMAPS * BLOCK_SIZE != read(dev_fd, zone_map, ZMAPS * BLOCK_SIZE))
+               die("cannot read zone map");
+       if (INODE_BUFFER_SIZE != read(dev_fd, inode_buffer, INODE_BUFFER_SIZE))
+               die("cannot read inodes");
+       if (NORM_FIRSTZONE != FIRSTZONE) {
+               printf("warning: firstzone!=norm_firstzone\n");
+               errors_uncorrected = 1;
+       }
+       get_dirsize();
+       if (OPT_show) {
+               printf("%u inodes\n"
+                       "%u blocks\n"
+                       "Firstdatazone=%u (%u)\n"
+                       "Zonesize=%u\n"
+                       "Maxsize=%u\n"
+                       "Filesystem state=%u\n"
+                       "namelen=%u\n\n",
+                       INODES,
+                       ZONES,
+                       FIRSTZONE, NORM_FIRSTZONE,
+                       BLOCK_SIZE << ZONESIZE,
+                       MAXSIZE,
+                       Super.s_state,
+                       namelen);
+       }
+}
+
+static void get_inode_common(unsigned nr, uint16_t i_mode)
+{
+       total++;
+       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 (OPT_repair) {
+                               if (ask("Mark as 'in use'", 1))
+                                       mark_inode(nr);
+                               else
+                                       errors_uncorrected = 1;
+                       }
+               }
+               if (S_ISDIR(i_mode))
+                       directory++;
+               else if (S_ISREG(i_mode))
+                       regular++;
+               else if (S_ISCHR(i_mode))
+                       chardev++;
+               else if (S_ISBLK(i_mode))
+                       blockdev++;
+               else if (S_ISLNK(i_mode))
+                       symlinks++;
+               else if (S_ISSOCK(i_mode));
+               else if (S_ISFIFO(i_mode));
+               else {
+                       printf("%s has mode %05o\n", current_name, i_mode);
+               }
+       } else
+               links++;
+       if (!++inode_count[nr]) {
+               printf("Warning: inode count too big\n");
+               inode_count[nr]--;
+               errors_uncorrected = 1;
+       }
+}
+
+static struct minix1_inode *get_inode(unsigned nr)
+{
+       struct minix1_inode *inode;
+
+       if (!nr || nr > INODES)
+               return NULL;
+       inode = Inode1 + nr;
+       get_inode_common(nr, inode->i_mode);
+       return inode;
+}
+
+#if ENABLE_FEATURE_MINIX2
+static struct minix2_inode *get_inode2(unsigned nr)
+{
+       struct minix2_inode *inode;
+
+       if (!nr || nr > INODES)
+               return NULL;
+       inode = Inode2 + nr;
+       get_inode_common(nr, inode->i_mode);
+       return inode;
+}
+#endif
+
+static void check_root(void)
+{
+       struct minix1_inode *inode = Inode1 + MINIX_ROOT_INO;
+
+       if (!inode || !S_ISDIR(inode->i_mode))
+               die("root inode isn't a directory");
+}
+
+#if ENABLE_FEATURE_MINIX2
+static void check_root2(void)
+{
+       struct minix2_inode *inode = Inode2 + MINIX_ROOT_INO;
+
+       if (!inode || !S_ISDIR(inode->i_mode))
+               die("root inode isn't a directory");
+}
+#else
+void check_root2(void);
+#endif
+
+static int add_zone_common(int block, smallint *corrected)
+{
+       if (!block)
+               return 0;
+       if (zone_count[block]) {
+               printf("Already used block is reused in file '%s'. ",
+                               current_name);
+               if (ask("Clear", 1)) {
+                       block = 0;
+                       *corrected = 1;
+                       return -1; /* "please zero out *znr" */
+               }
+       }
+       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;
+}
+
+static int add_zone(uint16_t *znr, smallint *corrected)
+{
+       int block;
+
+       block = check_zone_nr(znr, corrected);
+       block = add_zone_common(block, corrected);
+       if (block == -1) {
+               *znr = 0;
+               block = 0;
+       }
+       return block;
+}
+
+#if ENABLE_FEATURE_MINIX2
+static int add_zone2(uint32_t *znr, smallint *corrected)
+{
+       int block;
+
+       block = check_zone_nr2(znr, corrected);
+       block = add_zone_common(block, corrected);
+       if (block == -1) {
+               *znr = 0;
+               block = 0;
+       }
+       return block;
+}
+#endif
+
+static void add_zone_ind(uint16_t *znr, smallint *corrected)
+{
+       int i;
+       int block;
+       smallint chg_blk = 0;
+
+       block = add_zone(znr, corrected);
+       if (!block)
+               return;
+       read_block(block, add_zone_ind_blk);
+       for (i = 0; i < (BLOCK_SIZE >> 1); i++)
+               add_zone(i + (uint16_t *) add_zone_ind_blk, &chg_blk);
+       if (chg_blk)
+               write_block(block, add_zone_ind_blk);
+}
+
+#if ENABLE_FEATURE_MINIX2
+static void add_zone_ind2(uint32_t *znr, smallint *corrected)
+{
+       int i;
+       int block;
+       smallint chg_blk = 0;
+
+       block = add_zone2(znr, corrected);
+       if (!block)
+               return;
+       read_block(block, add_zone_ind_blk);
+       for (i = 0; i < BLOCK_SIZE >> 2; i++)
+               add_zone2(i + (uint32_t *) add_zone_ind_blk, &chg_blk);
+       if (chg_blk)
+               write_block(block, add_zone_ind_blk);
+}
+#endif
+
+static void add_zone_dind(uint16_t *znr, smallint *corrected)
+{
+       int i;
+       int block;
+       smallint chg_blk = 0;
+
+       block = add_zone(znr, corrected);
+       if (!block)
+               return;
+       read_block(block, add_zone_dind_blk);
+       for (i = 0; i < (BLOCK_SIZE >> 1); i++)
+               add_zone_ind(i + (uint16_t *) add_zone_dind_blk, &chg_blk);
+       if (chg_blk)
+               write_block(block, add_zone_dind_blk);
+}
+
+#if ENABLE_FEATURE_MINIX2
+static void add_zone_dind2(uint32_t *znr, smallint *corrected)
+{
+       int i;
+       int block;
+       smallint chg_blk = 0;
+
+       block = add_zone2(znr, corrected);
+       if (!block)
+               return;
+       read_block(block, add_zone_dind_blk);
+       for (i = 0; i < BLOCK_SIZE >> 2; i++)
+               add_zone_ind2(i + (uint32_t *) add_zone_dind_blk, &chg_blk);
+       if (chg_blk)
+               write_block(block, add_zone_dind_blk);
+}
+
+static void add_zone_tind2(uint32_t *znr, smallint *corrected)
+{
+       int i;
+       int block;
+       smallint chg_blk = 0;
+
+       block = add_zone2(znr, corrected);
+       if (!block)
+               return;
+       read_block(block, add_zone_tind_blk);
+       for (i = 0; i < BLOCK_SIZE >> 2; i++)
+               add_zone_dind2(i + (uint32_t *) add_zone_tind_blk, &chg_blk);
+       if (chg_blk)
+               write_block(block, add_zone_tind_blk);
+}
+#endif
+
+static void check_zones(unsigned i)
+{
+       struct minix1_inode *inode;
+
+       if (!i || i > INODES)
+               return;
+       if (inode_count[i] > 1)         /* have we counted this file already? */
+               return;
+       inode = Inode1 + 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);
+}
+
+#if ENABLE_FEATURE_MINIX2
+static void check_zones2(unsigned 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 minix1_inode *dir, unsigned offset)
+{
+       struct minix1_inode *inode;
+       int ino;
+       char *name;
+       int block;
+
+       block = map_block(dir, offset / BLOCK_SIZE);
+       read_block(block, check_file_blk);
+       name = check_file_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, check_file_blk);
+               }
+               ino = 0;
+       }
+       push_filename(name);
+       inode = get_inode(ino);
+       pop_filename();
+       if (!offset) {
+               if (inode && LONE_CHAR(name, '.'))
+                       return;
+               printf("%s: bad directory: '.' isn't first\n", current_name);
+               errors_uncorrected = 1;
+       }
+       if (offset == dirsize) {
+               if (inode && strcmp("..", name) == 0)
+                       return;
+               printf("%s: bad directory: '..' isn't second\n", current_name);
+               errors_uncorrected = 1;
+       }
+       if (!inode)
+               return;
+       push_filename(name);
+       if (OPT_list) {
+               if (OPT_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();
+}
+
+#if ENABLE_FEATURE_MINIX2
+static void check_file2(struct minix2_inode *dir, unsigned offset)
+{
+       struct minix2_inode *inode;
+       int ino;
+       char *name;
+       int block;
+
+       block = map_block2(dir, offset / BLOCK_SIZE);
+       read_block(block, check_file_blk);
+       name = check_file_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, check_file_blk);
+               }
+               ino = 0;
+       }
+       push_filename(name);
+       inode = get_inode2(ino);
+       pop_filename();
+       if (!offset) {
+               if (inode && LONE_CHAR(name, '.'))
+                       return;
+               printf("%s: bad directory: '.' isn't first\n", current_name);
+               errors_uncorrected = 1;
+       }
+       if (offset == dirsize) {
+               if (inode && strcmp("..", name) == 0)
+                       return;
+               printf("%s: bad directory: '..' isn't second\n", current_name);
+               errors_uncorrected = 1;
+       }
+       if (!inode)
+               return;
+       push_filename(name);
+       if (OPT_list) {
+               if (OPT_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();
+}
+#endif
+
+static void recursive_check(unsigned ino)
+{
+       struct minix1_inode *dir;
+       unsigned offset;
+
+       dir = Inode1 + 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);
+}
+
+#if ENABLE_FEATURE_MINIX2
+static void recursive_check2(unsigned ino)
+{
+       struct minix2_inode *dir;
+       unsigned 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[BLOCK_SIZE];
+
+       xlseek(dev_fd, BLOCK_SIZE * i, SEEK_SET);
+       return (BLOCK_SIZE != full_read(dev_fd, buffer, BLOCK_SIZE));
+}
+
+static void check_counts(void)
+{
+       int i;
+
+       for (i = 1; i <= INODES; i++) {
+               if (OPT_warn_mode && Inode1[i].i_mode && !inode_in_use(i)) {
+                       printf("Inode %d has non-zero mode. ", i);
+                       if (ask("Clear", 1)) {
+                               Inode1[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 (Inode1[i].i_nlinks != inode_count[i]) {
+                       printf("Inode %d (mode=%07o), i_nlinks=%d, counted=%d. ",
+                               i, Inode1[i].i_mode, Inode1[i].i_nlinks,
+                               inode_count[i]);
+                       if (ask("Set i_nlinks to count", 1)) {
+                               Inode1[i].i_nlinks = inode_count[i];
+                               changed = 1;
+                       }
+               }
+       }
+       for (i = FIRSTZONE; i < ZONES; i++) {
+               if ((zone_in_use(i) != 0) == 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]);
+       }
+}
+
+#if ENABLE_FEATURE_MINIX2
+static void check_counts2(void)
+{
+       int i;
+
+       for (i = 1; i <= INODES; i++) {
+               if (OPT_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) != 0) == 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(MINIX_ROOT_INO);
+       recursive_check(MINIX_ROOT_INO);
+       check_counts();
+}
+
+#if ENABLE_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(MINIX_ROOT_INO);
+       recursive_check2(MINIX_ROOT_INO);
+       check_counts2();
+}
+#else
+void check2(void);
+#endif
+
+int fsck_minix_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int fsck_minix_main(int argc UNUSED_PARAM, char **argv)
+{
+       struct termios tmp;
+       int retcode = 0;
+
+       xfunc_error_retval = 8;
+
+       INIT_G();
+
+       opt_complementary = "=1:ar"; /* one argument; -a assumes -r */
+       getopt32(argv, OPTION_STR);
+       argv += optind;
+       device_name = argv[0];
+
+       check_mount();  /* trying to check a mounted filesystem? */
+       if (OPT_manual) {
+               if (!isatty(0) || !isatty(1))
+                       die("need terminal for interactive repairs");
+       }
+       xmove_fd(xopen(device_name, OPT_repair ? O_RDWR : O_RDONLY), dev_fd);
+
+       /*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, bb_banner);
+
+       if (!(Super.s_state & MINIX_ERROR_FS)
+        && (Super.s_state & MINIX_VALID_FS) && !OPT_force
+       ) {
+               if (OPT_repair)
+                       printf("%s is clean, check is skipped\n", device_name);
+               return 0;
+       } else if (OPT_force)
+               printf("Forcing filesystem check on %s\n", device_name);
+       else if (OPT_repair)
+               printf("Filesystem on %s is dirty, needs checking\n",
+                          device_name);
+
+       read_tables();
+
+       if (OPT_manual) {
+               tcgetattr(0, &sv_termios);
+               tmp = sv_termios;
+               tmp.c_lflag &= ~(ICANON | ECHO);
+               tcsetattr_stdin_TCSANOW(&tmp);
+               termios_set = 1;
+       }
+
+       if (version2) {
+               check_root2();
+               check2();
+       } else {
+               check_root();
+               check();
+       }
+
+       if (OPT_verbose) {
+               int i, free_cnt;
+
+               for (i = 1, free_cnt = 0; i <= INODES; i++)
+                       if (!inode_in_use(i))
+                               free_cnt++;
+               printf("\n%6u inodes used (%u%%)\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("%6u zones used (%u%%)\n\n"
+                          "%6u regular files\n"
+                          "%6u directories\n"
+                          "%6u character device files\n"
+                          "%6u block device files\n"
+                          "%6u links\n"
+                          "%6u symbolic links\n"
+                          "------\n"
+                          "%6u 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 (OPT_repair)
+               write_superblock();
+
+       if (OPT_manual)
+               tcsetattr_stdin_TCSANOW(&sv_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 (file)
index 0000000..fd67287
--- /dev/null
@@ -0,0 +1,354 @@
+/* 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 Mickiewicz
+ *     <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 <getopt.h>
+#include "libbb.h"
+
+/* NON_OPT is the code that is returned when a non-option is found in '+'
+   mode */
+enum {
+       NON_OPT = 1,
+#if ENABLE_GETOPT_LONG
+/* LONG_OPT is the code that is returned when a long option is found. */
+       LONG_OPT = 2
+#endif
+};
+
+/* For finding activated option flags. Must match getopt32 call! */
+enum {
+       OPT_o   = 0x1,  // -o
+       OPT_n   = 0x2,  // -n
+       OPT_q   = 0x4,  // -q
+       OPT_Q   = 0x8,  // -Q
+       OPT_s   = 0x10, // -s
+       OPT_T   = 0x20, // -T
+       OPT_u   = 0x40, // -u
+#if ENABLE_GETOPT_LONG
+       OPT_a   = 0x80, // -a
+       OPT_l   = 0x100, // -l
+#endif
+       SHELL_IS_TCSH = 0x8000, /* hijack this bit for other purposes */
+};
+
+/* 0 is getopt_long, 1 is getopt_long_only */
+#define alternative  (option_mask32 & OPT_a)
+
+#define quiet_errors (option_mask32 & OPT_q)
+#define quiet_output (option_mask32 & OPT_Q)
+#define quote        (!(option_mask32 & OPT_u))
+#define shell_TCSH   (option_mask32 & SHELL_IS_TCSH)
+
+/*
+ * 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.
+ */
+static const char *normalize(const char *arg)
+{
+       char *bufptr;
+#if ENABLE_FEATURE_CLEAN_UP
+       static char *BUFFER = NULL;
+       free(BUFFER);
+#else
+       char *BUFFER;
+#endif
+
+       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 (*arg) {
+               if (*arg == '\'') {
+                       /* Quote: replace it with: '\'' */
+                       *bufptr ++= '\'';
+                       *bufptr ++= '\\';
+                       *bufptr ++= '\'';
+                       *bufptr ++= '\'';
+               } else if (shell_TCSH && *arg == '!') {
+                       /* Exclamation mark: replace it with: \! */
+                       *bufptr ++= '\'';
+                       *bufptr ++= '\\';
+                       *bufptr ++= '!';
+                       *bufptr ++= '\'';
+               } else if (shell_TCSH && *arg == '\n') {
+                       /* Newline: replace it with: \n */
+                       *bufptr ++= '\\';
+                       *bufptr ++= 'n';
+               } else if (shell_TCSH && isspace(*arg)) {
+                       /* Non-newline whitespace: replace it with \<ws> */
+                       *bufptr ++= '\'';
+                       *bufptr ++= '\\';
+                       *bufptr ++= *arg;
+                       *bufptr ++= '\'';
+               } else
+                       /* Just copy */
+                       *bufptr ++= *arg;
+               arg++;
+       }
+       *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.
+ */
+#if !ENABLE_GETOPT_LONG
+#define generate_output(argv,argc,optstr,longopts) \
+       generate_output(argv,argc,optstr)
+#endif
+static 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;
+#if ENABLE_GETOPT_LONG
+       int longindex;
+#endif
+       const char *charptr;
+
+       if (quiet_errors) /* No error reporting from getopt(3) */
+               opterr = 0;
+
+       /* We used it already in main() in getopt32(),
+        * we *must* reset getopt(3): */
+#ifdef __GLIBC__
+       optind = 0;
+#else /* BSD style */
+       optind = 1;
+       /* optreset = 1; */
+#endif
+
+       while (1) {
+               opt =
+#if ENABLE_GETOPT_LONG
+                       alternative ?
+                       getopt_long_only(argc, argv, optstr, longopts, &longindex) :
+                       getopt_long(argc, argv, optstr, longopts, &longindex);
+#else
+                       getopt(argc, argv, optstr);
+#endif
+               if (opt == -1)
+                       break;
+               if (opt == '?' || opt == ':' )
+                       exit_code = 1;
+               else if (!quiet_output) {
+#if ENABLE_GETOPT_LONG
+                       if (opt == LONG_OPT) {
+                               printf(" --%s", longopts[longindex].name);
+                               if (longopts[longindex].has_arg)
+                                       printf(" %s",
+                                               normalize(optarg ? optarg : ""));
+                       } else
+#endif
+                       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++]));
+               bb_putchar('\n');
+       }
+       return exit_code;
+}
+
+#if ENABLE_GETOPT_LONG
+/*
+ * Register several long options. options is a string of long options,
+ * separated by commas or whitespace.
+ * This nukes options!
+ */
+static struct option *add_long_options(struct option *long_options, char *options)
+{
+       int long_nr = 0;
+       int arg_opt, tlen;
+       char *tokptr = strtok(options, ", \t\n");
+
+       if (long_options)
+               while (long_options[long_nr].name)
+                       long_nr++;
+
+       while (tokptr) {
+               arg_opt = no_argument;
+               tlen = strlen(tokptr);
+               if (tlen) {
+                       tlen--;
+                       if (tokptr[tlen] == ':') {
+                               arg_opt = required_argument;
+                               if (tlen && tokptr[tlen-1] == ':') {
+                                       tlen--;
+                                       arg_opt = optional_argument;
+                               }
+                               tokptr[tlen] = '\0';
+                               if (tlen == 0)
+                                       bb_error_msg_and_die("empty long option specified");
+                       }
+                       long_options = xrealloc_vector(long_options, 4, long_nr);
+                       long_options[long_nr].has_arg = arg_opt;
+                       /*long_options[long_nr].flag = NULL; - xrealloc_vector did it */
+                       long_options[long_nr].val = LONG_OPT;
+                       long_options[long_nr].name = xstrdup(tokptr);
+                       long_nr++;
+                       /*memset(&long_options[long_nr], 0, sizeof(long_options[0])); - xrealloc_vector did it */
+               }
+               tokptr = strtok(NULL, ", \t\n");
+       }
+       return long_options;
+}
+#endif
+
+static void set_shell(const char *new_shell)
+{
+       if (!strcmp(new_shell, "bash") || !strcmp(new_shell, "sh"))
+               return;
+       if (!strcmp(new_shell, "tcsh") || !strcmp(new_shell, "csh"))
+               option_mask32 |= SHELL_IS_TCSH;
+       else
+               bb_error_msg("unknown shell '%s', assuming bash", new_shell);
+}
+
+
+/* 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
+ */
+
+#if ENABLE_GETOPT_LONG
+static const char getopt_longopts[] ALIGN1 =
+       "options\0"      Required_argument "o"
+       "longoptions\0"  Required_argument "l"
+       "quiet\0"        No_argument       "q"
+       "quiet-output\0" No_argument       "Q"
+       "shell\0"        Required_argument "s"
+       "test\0"         No_argument       "T"
+       "unquoted\0"     No_argument       "u"
+       "alternative\0"  No_argument       "a"
+       "name\0"         Required_argument "n"
+       ;
+#endif
+
+int getopt_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int getopt_main(int argc, char **argv)
+{
+       char *optstr = NULL;
+       char *name = NULL;
+       unsigned opt;
+       const char *compatible;
+       char *s_arg;
+#if ENABLE_GETOPT_LONG
+       struct option *long_options = NULL;
+       llist_t *l_arg = NULL;
+#endif
+
+       compatible = getenv("GETOPT_COMPATIBLE"); /* used as yes/no flag */
+
+       if (argc == 1) {
+               if (compatible) {
+                       /* For some reason, the original getopt gave no error
+                          when there were no arguments. */
+                       printf(" --\n");
+                       return 0;
+               }
+               bb_error_msg_and_die("missing optstring argument");
+       }
+
+       if (argv[1][0] != '-' || compatible) {
+               char *s;
+
+               option_mask32 |= OPT_u; /* quoting off */
+               s = xstrdup(argv[1] + strspn(argv[1], "-+"));
+               argv[1] = argv[0];
+               return generate_output(argv+1, argc-1, s, long_options);
+       }
+
+#if !ENABLE_GETOPT_LONG
+       opt = getopt32(argv, "+o:n:qQs:Tu", &optstr, &name, &s_arg);
+#else
+       applet_long_options = getopt_longopts;
+       opt_complementary = "l::";
+       opt = getopt32(argv, "+o:n:qQs:Tual:",
+                                       &optstr, &name, &s_arg, &l_arg);
+       /* Effectuate the read options for the applet itself */
+       while (l_arg) {
+               long_options = add_long_options(long_options, llist_pop(&l_arg));
+       }
+#endif
+
+       if (opt & OPT_s) {
+               set_shell(s_arg);
+       }
+
+       if (opt & OPT_T) {
+               return 4;
+       }
+
+       /* All options controlling the applet have now been parsed */
+       if (!optstr) {
+               if (optind >= argc)
+                       bb_error_msg_and_die("missing optstring argument");
+               optstr = argv[optind++];
+       }
+
+       argv[optind-1] = name ? name : 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 (file)
index 0000000..48edd70
--- /dev/null
@@ -0,0 +1,151 @@
+/* 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 "libbb.h"
+#include "dump.h"
+
+/* This is a NOEXEC applet. Be very careful! */
+
+static void bb_dump_addfile(dumper_t *dumper, char *name)
+{
+       char *p;
+       FILE *fp;
+       char *buf;
+
+       fp = xfopen_for_read(name);
+       while ((buf = xmalloc_fgetline(fp)) != NULL) {
+               p = skip_whitespace(buf);
+               if (*p && (*p != '#')) {
+                       bb_dump_add(dumper, 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[] ALIGN1 = "\"%07.7_Ax\n\"";
+
+static const char hexdump_opts[] ALIGN1 = "bcdoxCe:f:n:s:v" USE_FEATURE_HEXDUMP_REVERSE("R");
+
+static const struct suffix_mult suffixes[] = {
+       { "b", 512 },
+       { "k", 1024 },
+       { "m", 1024*1024 },
+       { }
+};
+
+int hexdump_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int hexdump_main(int argc, char **argv)
+{
+       dumper_t *dumper = alloc_dumper();
+       const char *p;
+       int ch;
+#if ENABLE_FEATURE_HEXDUMP_REVERSE
+       FILE *fp;
+       smallint rdump = 0;
+#endif
+
+       if (ENABLE_HD && !applet_name[2]) { /* we are "hd" */
+               ch = 'C';
+               goto hd_applet;
+       }
+
+       /* We cannot use getopt32: in hexdump options are cumulative.
+        * E.g. "hexdump -C -C file" should dump each line twice */
+       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(dumper, add_first);
+                       bb_dump_add(dumper, add_strings[(int)(p - hexdump_opts)]);
+               }
+               /* Save a little bit of space below by omitting the 'else's. */
+               if (ch == 'C') {
+ hd_applet:
+                       bb_dump_add(dumper, "\"%08.8_Ax\n\"");
+                       bb_dump_add(dumper, "\"%08.8_ax  \" 8/1 \"%02x \" \"  \" 8/1 \"%02x \" ");
+                       bb_dump_add(dumper, "\"  |\" 16/1 \"%_p\" \"|\\n\"");
+               }
+               if (ch == 'e') {
+                       bb_dump_add(dumper, optarg);
+               } /* else */
+               if (ch == 'f') {
+                       bb_dump_addfile(dumper, optarg);
+               } /* else */
+               if (ch == 'n') {
+                       dumper->dump_length = xatoi_u(optarg);
+               } /* else */
+               if (ch == 's') {
+                       dumper->dump_skip = xatoul_range_sfx(optarg, 0, LONG_MAX, suffixes);
+               } /* else */
+               if (ch == 'v') {
+                       dumper->dump_vflag = ALL;
+               }
+#if ENABLE_FEATURE_HEXDUMP_REVERSE
+               if (ch == 'R') {
+                       rdump = 1;
+               }
+#endif
+       }
+
+       if (!dumper->fshead) {
+               bb_dump_add(dumper, add_first);
+               bb_dump_add(dumper, "\"%07.7_ax \" 8/2 \"%04x \" \"\\n\"");
+       }
+
+       argv += optind;
+
+#if !ENABLE_FEATURE_HEXDUMP_REVERSE
+       return bb_dump_dump(dumper, argv);
+#else
+       if (!rdump) {
+               return bb_dump_dump(dumper, argv);
+       }
+
+       /* -R: reverse of 'hexdump -Cv' */
+       fp = stdin;
+       if (!*argv) {
+               argv--;
+               goto jump_in;
+       }
+
+       do {
+               char *buf;
+               fp = xfopen_for_read(*argv);
+ jump_in:
+               while ((buf = xmalloc_fgetline(fp)) != NULL) {
+                       p = buf;
+                       while (1) {
+                               /* skip address or previous byte */
+                               while (isxdigit(*p)) p++;
+                               while (*p == ' ') p++;
+                               /* '|' char will break the line */
+                               if (!isxdigit(*p) || sscanf(p, "%x ", &ch) != 1)
+                                       break;
+                               putchar(ch);
+                       }
+                       free(buf);
+               }
+               fclose(fp);
+       } while (*++argv);
+
+       fflush_stdout_and_exit(EXIT_SUCCESS);
+#endif
+}
diff --git a/util-linux/hwclock.c b/util-linux/hwclock.c
new file mode 100644 (file)
index 0000000..3d28364
--- /dev/null
@@ -0,0 +1,127 @@
+/* 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/utsname.h>
+#include "libbb.h"
+#include "rtc_.h"
+
+#if ENABLE_FEATURE_HWCLOCK_LONG_OPTIONS
+# ifndef _GNU_SOURCE
+#  define _GNU_SOURCE
+# endif
+#endif
+
+static const char *rtcname;
+
+static time_t read_rtc(int utc)
+{
+       time_t ret;
+       int fd;
+
+       fd = rtc_xopen(&rtcname, O_RDONLY);
+       ret = rtc_read_time(fd, utc);
+       close(fd);
+
+       return ret;
+}
+
+static void write_rtc(time_t t, int utc)
+{
+       struct tm tm;
+       int rtc = rtc_xopen(&rtcname, O_WRONLY);
+
+       tm = *(utc ? gmtime(&t) : localtime(&t));
+       tm.tm_isdst = 0;
+
+       xioctl(rtc, RTC_SET_TIME, &tm);
+
+       close(rtc);
+}
+
+static void show_clock(int utc)
+{
+       //struct tm *ptm;
+       time_t t;
+       char *cp;
+
+       t = read_rtc(utc);
+       //ptm = localtime(&t);  /* Sets 'tzname[]' */
+
+       cp = ctime(&t);
+       if (cp[0])
+               cp[strlen(cp) - 1] = '\0';
+
+       //printf("%s  %.6f seconds %s\n", cp, 0.0, utc ? "" : (ptm->tm_isdst ? tzname[1] : tzname[0]));
+       printf("%s  0.000000 seconds\n", cp);
+}
+
+static void to_sys_clock(int utc)
+{
+       struct timeval tv;
+       const struct timezone tz = { timezone/60 - 60*daylight, 0 };
+
+       tv.tv_sec = read_rtc(utc);
+       tv.tv_usec = 0;
+       if (settimeofday(&tv, &tz))
+               bb_perror_msg_and_die("settimeofday() failed");
+}
+
+static void from_sys_clock(int utc)
+{
+       struct timeval tv;
+
+       gettimeofday(&tv, NULL);
+       //if (gettimeofday(&tv, NULL))
+       //      bb_perror_msg_and_die("gettimeofday() failed");
+       write_rtc(tv.tv_sec, 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
+#define HWCLOCK_OPT_RTCFILE     0x20
+
+int hwclock_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int hwclock_main(int argc UNUSED_PARAM, char **argv)
+{
+       unsigned opt;
+       int utc;
+
+#if ENABLE_FEATURE_HWCLOCK_LONG_OPTIONS
+       static const char hwclock_longopts[] ALIGN1 =
+               "localtime\0" No_argument "l"
+               "utc\0"       No_argument "u"
+               "show\0"      No_argument "r"
+               "hctosys\0"   No_argument "s"
+               "systohc\0"   No_argument "w"
+               "file\0"      Required_argument "f"
+               ;
+       applet_long_options = hwclock_longopts;
+#endif
+       opt_complementary = "r--ws:w--rs:s--wr:l--u:u--l";
+       opt = getopt32(argv, "lurswf:", &rtcname);
+
+       /* 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 = rtc_adjtime_is_utc();
+
+       if (opt & HWCLOCK_OPT_HCTOSYS)
+               to_sys_clock(utc);
+       else if (opt & HWCLOCK_OPT_SYSTOHC)
+               from_sys_clock(utc);
+       else
+               /* default HWCLOCK_OPT_SHOW */
+               show_clock(utc);
+
+       return 0;
+}
diff --git a/util-linux/ipcrm.c b/util-linux/ipcrm.c
new file mode 100644 (file)
index 0000000..5dcda85
--- /dev/null
@@ -0,0 +1,220 @@
+/* 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 "libbb.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 *array;
+       struct seminfo *__buf;
+};
+#endif
+
+#define IPCRM_LEGACY 1
+
+
+#if IPCRM_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 /* IPCRM_LEGACY */
+
+
+int ipcrm_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+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;
+#if IPCRM_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(EXIT_FAILURE);
+                       printf("resource(s) deleted\n");
+                       return 0;
+               }
+       }
+#endif /* IPCRM_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) {
+                               const char *errmsg;
+
+                               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, "key", 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) {
+                       const 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 (file)
index 0000000..9201257
--- /dev/null
@@ -0,0 +1,621 @@
+/* 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.
+ */
+
+/* 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>
+
+#include "libbb.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 *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);
+
+       pw = getpwuid(ipcp->cuid);
+       if (pw) printf(" %-10s", pw->pw_name);
+       else    printf(" %-10d", ipcp->cuid);
+       gr = getgrgid(ipcp->cgid);
+       if (gr) printf(" %-10s", gr->gr_name);
+       else    printf(" %-10d", ipcp->cgid);
+
+       pw = getpwuid(ipcp->uid);
+       if (pw) printf(" %-10s", pw->pw_name);
+       else    printf(" %-10d", ipcp->uid);
+       gr = getgrgid(ipcp->gid);
+       if (gr) 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);
+       }
+       bb_putchar('\n');
+}
+
+int ipcs_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int ipcs_main(int argc UNUSED_PARAM, 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(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(EXIT_SUCCESS);
+               }
+               if (flags & flag_sem) {
+                       print_sem(id);
+                       fflush_stdout_and_exit(EXIT_SUCCESS);
+               }
+               if (flags & flag_msg) {
+                       print_msg(id);
+                       fflush_stdout_and_exit(EXIT_SUCCESS);
+               }
+               bb_show_usage();
+       }
+
+       if (!(flags & (flag_shm | flag_msg | flag_sem)))
+               flags |= flag_msg | flag_shm | flag_sem;
+       bb_putchar('\n');
+
+       if (flags & flag_shm) {
+               do_shm();
+               bb_putchar('\n');
+       }
+       if (flags & flag_sem) {
+               do_sem();
+               bb_putchar('\n');
+       }
+       if (flags & flag_msg) {
+               do_msg();
+               bb_putchar('\n');
+       }
+       fflush_stdout_and_exit(EXIT_SUCCESS);
+}
diff --git a/util-linux/losetup.c b/util-linux/losetup.c
new file mode 100644 (file)
index 0000000..e224a4d
--- /dev/null
@@ -0,0 +1,79 @@
+/* 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 "libbb.h"
+
+int losetup_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int losetup_main(int argc, char **argv)
+{
+       char dev[] = LOOP_NAME"0";
+       unsigned opt;
+       char *opt_o;
+       char *s;
+       unsigned long long offset = 0;
+
+       /* max 2 args, all opts are mutually exclusive */
+       opt_complementary = "?2:d--of:o--df:f-do";
+       opt = getopt32(argv, "do:f", &opt_o);
+       argc -= optind;
+       argv += optind;
+
+       if (opt == 0x2) // -o
+               offset = xatoull(opt_o);
+
+       if (opt == 0x4 && argc) // -f does not take any argument
+               bb_show_usage();
+
+       if (opt == 0x1) { // -d
+               /* detach takes exactly one argument */
+               if (argc != 1)
+                       bb_show_usage();
+               if (del_loop(argv[0]))
+                       bb_simple_perror_msg_and_die(argv[0]);
+               return EXIT_SUCCESS;
+       }
+
+       if (argc == 2) {
+               /* -o or no option */
+               if (set_loop(&argv[0], argv[1], offset) < 0)
+                       bb_simple_perror_msg_and_die(argv[0]);
+               return EXIT_SUCCESS;
+       }
+
+       if (argc == 1) {
+               /* -o or no option */
+               s = query_loop(argv[0]);
+               if (!s)
+                       bb_simple_perror_msg_and_die(argv[0]);
+               printf("%s: %s\n", argv[0], s);
+               if (ENABLE_FEATURE_CLEAN_UP)
+                       free(s);
+               return EXIT_SUCCESS;
+       }
+
+       /* -o, -f or no option */
+       while (1) {
+               s = query_loop(dev);
+               if (!s) {
+                       if (opt == 0x4) {
+                               puts(dev);
+                               return EXIT_SUCCESS;
+                       }
+               } else {
+                       if (opt != 0x4)
+                               printf("%s: %s\n", dev, s);
+                       if (ENABLE_FEATURE_CLEAN_UP)
+                               free(s);
+               }
+
+               if (++dev[sizeof(dev) - 2] > '9')
+                       break;
+       }
+       return EXIT_SUCCESS;
+}
diff --git a/util-linux/mdev.c b/util-linux/mdev.c
new file mode 100644 (file)
index 0000000..3c4540c
--- /dev/null
@@ -0,0 +1,544 @@
+/* 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 "libbb.h"
+#include "xregex.h"
+
+struct globals {
+       int root_major, root_minor;
+       char *subsystem;
+};
+#define G (*(struct globals*)&bb_common_bufsiz1)
+#define root_major (G.root_major)
+#define root_minor (G.root_minor)
+#define subsystem (G.subsystem)
+
+/* Prevent infinite loops in /sys symlinks */
+#define MAX_SYSFS_DEPTH 3
+
+/* We use additional 64+ bytes in make_device() */
+#define SCRATCH_SIZE 80
+
+#if ENABLE_FEATURE_MDEV_RENAME
+/* Builds an alias path.
+ * This function potentionally reallocates the alias parameter.
+ */
+static char *build_alias(char *alias, const char *device_name)
+{
+       char *dest;
+
+       /* ">bar/": rename to bar/device_name */
+       /* ">bar[/]baz": rename to bar[/]baz */
+       dest = strrchr(alias, '/');
+       if (dest) { /* ">bar/[baz]" ? */
+               *dest = '\0'; /* mkdir bar */
+               bb_make_directory(alias, 0755, FILEUTILS_RECUR);
+               *dest = '/';
+               if (dest[1] == '\0') { /* ">bar/" => ">bar/device_name" */
+                       dest = alias;
+                       alias = concat_path_file(alias, device_name);
+                       free(dest);
+               }
+       }
+
+       return alias;
+}
+#endif
+
+/* mknod in /dev based on a path like "/sys/block/hda/hda1" */
+/* NB: "mdev -s" may call us many times, do not leak memory/fds! */
+static void make_device(char *path, int delete)
+{
+#if ENABLE_FEATURE_MDEV_CONF
+       parser_t *parser;
+#endif
+       const char *device_name;
+       int major, minor, type, len;
+       int mode;
+       char *dev_maj_min = path + strlen(path);
+
+       /* Force the configuration file settings exactly. */
+       umask(0);
+
+       /* 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.
+        */
+       major = -1;
+       if (!delete) {
+               strcpy(dev_maj_min, "/dev");
+               len = open_read_close(path, dev_maj_min + 1, 64);
+               *dev_maj_min++ = '\0';
+               if (len < 1) {
+                       if (!ENABLE_FEATURE_MDEV_EXEC)
+                               return;
+                       /* no "dev" file, so just try to run script */
+                       *dev_maj_min = '\0';
+               } else if (sscanf(dev_maj_min, "%u:%u", &major, &minor) != 2) {
+                       major = -1;
+               }
+       }
+
+       /* Determine device name, type, major and minor */
+       device_name = bb_basename(path);
+       /* http://kernel.org/doc/pending/hotplug.txt says that only
+        * "/sys/block/..." is for block devices. "/sys/bus" etc is not.
+        * But since 2.6.25 block devices are also in /sys/class/block.
+        * We use strstr("/block/") to forestall future surprises. */
+       type = S_IFCHR;
+       if (strstr(path, "/block/"))
+               type = S_IFBLK;
+
+#if !ENABLE_FEATURE_MDEV_CONF
+       mode = 0660;
+#else
+       /* If we have config file, look up user settings */
+       parser = config_open2("/etc/mdev.conf", fopen_for_read);
+       while (1) {
+               regmatch_t off[1 + 9*ENABLE_FEATURE_MDEV_RENAME_REGEXP];
+               int keep_matching;
+               char *val;
+               struct bb_uidgid_t ugid;
+               char *tokens[4];
+# if ENABLE_FEATURE_MDEV_EXEC
+               char *command = NULL;
+# endif
+# if ENABLE_FEATURE_MDEV_RENAME
+               char *alias = NULL;
+               char aliaslink = aliaslink; /* for compiler */
+# endif
+               /* Defaults in case we won't match any line */
+               ugid.uid = ugid.gid = 0;
+               keep_matching = 0;
+               mode = 0660;
+
+               if (!config_read(parser, tokens, 4, 3, "# \t", PARSE_NORMAL)) {
+                       /* End of file, create dev node with default params */
+                       goto line_matches;
+               }
+
+               val = tokens[0];
+               keep_matching = ('-' == val[0]);
+               val += keep_matching; /* swallow leading dash */
+
+               /* Fields: regex uid:gid mode [alias] [cmd] */
+
+               /* 1st field: @<numeric maj,min>... */
+               if (val[0] == '@') {
+                       /* @major,minor[-last] */
+                       /* (useful when name is ambiguous:
+                        * "/sys/class/usb/lp0" and
+                        * "/sys/class/printer/lp0") */
+                       int cmaj, cmin0, cmin1, sc;
+                       if (major < 0)
+                               continue; /* no dev, no match */
+                       sc = sscanf(val, "@%u,%u-%u", &cmaj, &cmin0, &cmin1);
+                       if (sc < 1 || major != cmaj
+                        || (sc == 2 && minor != cmin0)
+                        || (sc == 3 && (minor < cmin0 || minor > cmin1))
+                       ) {
+                               continue; /* this line doesn't match */
+                       }
+               } else { /* ... or regex to match device name */
+                       regex_t match;
+                       int result;
+                       const char *dev_name_or_subsystem = device_name;
+                       if ('/' == val[0] && subsystem) {
+                               dev_name_or_subsystem = subsystem;
+                               val++;
+                       }
+
+                       /* Is this it? */
+                       xregcomp(&match, val, REG_EXTENDED);
+                       result = regexec(&match, dev_name_or_subsystem, ARRAY_SIZE(off), off, 0);
+                       regfree(&match);
+
+                       //bb_error_msg("matches:");
+                       //for (int i = 0; i < ARRAY_SIZE(off); i++) {
+                       //      if (off[i].rm_so < 0) continue;
+                       //      bb_error_msg("match %d: '%.*s'\n", i,
+                       //              (int)(off[i].rm_eo - off[i].rm_so),
+                       //              device_name + off[i].rm_so);
+                       //}
+
+                       /* If not this device, skip rest of line */
+                       /* (regexec returns whole pattern as "range" 0) */
+                       if (result || off[0].rm_so
+                        || ((int)off[0].rm_eo != (int)strlen(dev_name_or_subsystem))
+                       ) {
+                               continue; /* this line doesn't match */
+                       }
+               }
+
+               /* This line matches: stop parsing the file after parsing
+                * the rest of fields unless keep_matching == 1 */
+
+               /* 2nd field: uid:gid - device ownership */
+               if (get_uidgid(&ugid, tokens[1], 1) == 0)
+                       bb_error_msg("unknown user/group %s", tokens[1]);
+
+               /* 3rd field: mode - device permissions */
+               mode = strtoul(tokens[2], NULL, 8);
+
+               val = tokens[3];
+               /* 4th field (opt): >|=alias */
+# if ENABLE_FEATURE_MDEV_RENAME
+               if (!val)
+                       goto line_matches;
+               aliaslink = val[0];
+               if (aliaslink == '>' || aliaslink == '=') {
+                       char *a, *s, *st;
+#  if ENABLE_FEATURE_MDEV_RENAME_REGEXP
+                       char *p;
+                       unsigned i, n;
+#  endif
+                       a = val;
+                       s = strchrnul(val, ' ');
+                       st = strchrnul(val, '\t');
+                       if (st < s)
+                               s = st;
+                       val = (s[0] && s[1]) ? s+1 : NULL;
+                       s[0] = '\0';
+
+#  if ENABLE_FEATURE_MDEV_RENAME_REGEXP
+                       /* substitute %1..9 with off[1..9], if any */
+                       n = 0;
+                       s = a;
+                       while (*s)
+                               if (*s++ == '%')
+                                       n++;
+
+                       p = alias = xzalloc(strlen(a) + n * strlen(device_name));
+                       s = a + 1;
+                       while (*s) {
+                               *p = *s;
+                               if ('%' == *s) {
+                                       i = (s[1] - '0');
+                                       if (i <= 9 && off[i].rm_so >= 0) {
+                                               n = off[i].rm_eo - off[i].rm_so;
+                                               strncpy(p, device_name + off[i].rm_so, n);
+                                               p += n - 1;
+                                               s++;
+                                       }
+                               }
+                               p++;
+                               s++;
+                       }
+#  else
+                       alias = xstrdup(a + 1);
+#  endif
+               }
+# endif /* ENABLE_FEATURE_MDEV_RENAME */
+
+# if ENABLE_FEATURE_MDEV_EXEC
+               /* The rest (opt): @|$|*command */
+               if (!val)
+                       goto line_matches;
+               {
+                       const char *s = "@$*";
+                       const char *s2 = strchr(s, val[0]);
+
+                       if (!s2)
+                               bb_error_msg_and_die("bad line %u", parser->lineno);
+
+                       /* Correlate the position in the "@$*" with the delete
+                        * step so that we get the proper behavior:
+                        * @cmd: run on create
+                        * $cmd: run on delete
+                        * *cmd: run on both
+                        */
+                       if ((s2 - s + 1) /*1/2/3*/ & /*1/2*/ (1 + delete)) {
+                               command = xstrdup(val + 1);
+                       }
+               }
+# endif
+               /* End of field parsing */
+ line_matches:
+#endif /* ENABLE_FEATURE_MDEV_CONF */
+
+               /* "Execute" the line we found */
+
+               if (!delete && major >= 0) {
+                       if (ENABLE_FEATURE_MDEV_RENAME)
+                               unlink(device_name);
+                       if (mknod(device_name, mode | type, makedev(major, minor)) && errno != EEXIST)
+                               bb_perror_msg_and_die("mknod %s", device_name);
+                       if (major == root_major && minor == root_minor)
+                               symlink(device_name, "root");
+#if ENABLE_FEATURE_MDEV_CONF
+                       chown(device_name, ugid.uid, ugid.gid);
+# if ENABLE_FEATURE_MDEV_RENAME
+                       if (alias) {
+                               alias = build_alias(alias, device_name);
+                               /* move the device, and optionally
+                                * make a symlink to moved device node */
+                               if (rename(device_name, alias) == 0 && aliaslink == '>')
+                                       symlink(alias, device_name);
+                               free(alias);
+                       }
+# endif
+#endif
+               }
+#if ENABLE_FEATURE_MDEV_EXEC
+               if (command) {
+                       /* setenv will leak memory, use putenv/unsetenv/free */
+                       char *s = xasprintf("%s=%s", "MDEV", device_name);
+                       char *s1 = xasprintf("%s=%s", "SUBSYSTEM", subsystem);
+                       putenv(s);
+                       putenv(s1);
+                       if (system(command) == -1)
+                               bb_perror_msg_and_die("can't run '%s'", command);
+                       unsetenv("SUBSYSTEM");
+                       free(s1);
+                       unsetenv("MDEV");
+                       free(s);
+                       free(command);
+               }
+#endif
+               if (delete) {
+                       unlink(device_name);
+                       /* At creation time, device might have been moved
+                        * and a symlink might have been created. Undo that. */
+#if ENABLE_FEATURE_MDEV_RENAME
+                       if (alias) {
+                               alias = build_alias(alias, device_name);
+                               unlink(alias);
+                               free(alias);
+                       }
+#endif
+               }
+
+#if ENABLE_FEATURE_MDEV_CONF
+               /* We found matching line.
+                * Stop unless it was prefixed with '-' */
+               if (!keep_matching)
+                       break;
+       } /* end of "while line is read from /etc/mdev.conf" */
+
+       config_close(parser);
+#endif /* ENABLE_FEATURE_MDEV_CONF */
+}
+
+/* File callback for /sys/ traversal */
+static int FAST_FUNC fileAction(const char *fileName,
+               struct stat *statbuf UNUSED_PARAM,
+               void *userData,
+               int depth UNUSED_PARAM)
+{
+       size_t len = strlen(fileName) - 4; /* can't underflow */
+       char *scratch = userData;
+
+       /* len check is for paranoid reasons */
+       if (strcmp(fileName + len, "/dev") != 0 || len >= PATH_MAX)
+               return FALSE;
+
+       strcpy(scratch, fileName);
+       scratch[len] = '\0';
+       make_device(scratch, 0);
+
+       return TRUE;
+}
+
+/* Directory callback for /sys/ traversal */
+static int FAST_FUNC dirAction(const char *fileName UNUSED_PARAM,
+               struct stat *statbuf UNUSED_PARAM,
+               void *userData UNUSED_PARAM,
+               int depth)
+{
+       /* Extract device subsystem -- the name of the directory
+        * under /sys/class/ */
+       if (1 == depth) {
+               free(subsystem);
+               subsystem = strrchr(fileName, '/');
+               if (subsystem)
+                       subsystem = xstrdup(subsystem + 1);
+       }
+
+       return (depth >= MAX_SYSFS_DEPTH ? SKIP : TRUE);
+}
+
+/* For the full gory details, see linux/Documentation/firmware_class/README
+ *
+ * Firmware loading works like this:
+ * - kernel sets FIRMWARE env var
+ * - userspace checks /lib/firmware/$FIRMWARE
+ * - userspace waits for /sys/$DEVPATH/loading to appear
+ * - userspace writes "1" to /sys/$DEVPATH/loading
+ * - userspace copies /lib/firmware/$FIRMWARE into /sys/$DEVPATH/data
+ * - userspace writes "0" (worked) or "-1" (failed) to /sys/$DEVPATH/loading
+ * - kernel loads firmware into device
+ */
+static void load_firmware(const char *const firmware, const char *const sysfs_path)
+{
+       int cnt;
+       int firmware_fd, loading_fd, data_fd;
+
+       /* check for /lib/firmware/$FIRMWARE */
+       xchdir("/lib/firmware");
+       firmware_fd = xopen(firmware, O_RDONLY);
+
+       /* in case we goto out ... */
+       data_fd = -1;
+
+       /* check for /sys/$DEVPATH/loading ... give 30 seconds to appear */
+       xchdir(sysfs_path);
+       for (cnt = 0; cnt < 30; ++cnt) {
+               loading_fd = open("loading", O_WRONLY);
+               if (loading_fd != -1)
+                       goto loading;
+               sleep(1);
+       }
+       goto out;
+
+ loading:
+       /* tell kernel we're loading by "echo 1 > /sys/$DEVPATH/loading" */
+       if (full_write(loading_fd, "1", 1) != 1)
+               goto out;
+
+       /* load firmware into /sys/$DEVPATH/data */
+       data_fd = open("data", O_WRONLY);
+       if (data_fd == -1)
+               goto out;
+       cnt = bb_copyfd_eof(firmware_fd, data_fd);
+
+       /* tell kernel result by "echo [0|-1] > /sys/$DEVPATH/loading" */
+       if (cnt > 0)
+               full_write(loading_fd, "0", 1);
+       else
+               full_write(loading_fd, "-1", 2);
+
+ out:
+       if (ENABLE_FEATURE_CLEAN_UP) {
+               close(firmware_fd);
+               close(loading_fd);
+               close(data_fd);
+       }
+}
+
+int mdev_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int mdev_main(int argc UNUSED_PARAM, char **argv)
+{
+       RESERVE_CONFIG_BUFFER(temp, PATH_MAX + SCRATCH_SIZE);
+
+       /* We can be called as hotplug helper */
+       /* Kernel cannot provide suitable stdio fds for us, do it ourself */
+#if 1
+       bb_sanitize_stdio();
+#else
+       /* Debug code */
+       /* Replace LOGFILE by other file or device name if you need */
+#define LOGFILE "/dev/console"
+       /* Just making sure fd 0 is not closed,
+        * we don't really intend to read from it */
+       xmove_fd(xopen("/", O_RDONLY), STDIN_FILENO);
+       xmove_fd(xopen(LOGFILE, O_WRONLY|O_APPEND), STDOUT_FILENO);
+       xmove_fd(xopen(LOGFILE, O_WRONLY|O_APPEND), STDERR_FILENO);
+#endif
+
+       xchdir("/dev");
+
+       if (argv[1] && strcmp(argv[1], "-s") == 0) {
+               /* Scan:
+                * mdev -s
+                */
+               struct stat st;
+
+               xstat("/", &st);
+               root_major = major(st.st_dev);
+               root_minor = minor(st.st_dev);
+
+               /* ACTION_FOLLOWLINKS is needed since in newer kernels
+                * /sys/block/loop* (for example) are symlinks to dirs,
+                * not real directories.
+                * (kernel's CONFIG_SYSFS_DEPRECATED makes them real dirs,
+                * but we can't enforce that on users)
+                */
+               if (access("/sys/class/block", F_OK) != 0) {
+                       /* Scan obsolete /sys/block only if /sys/class/block
+                        * doesn't exist. Otherwise we'll have dupes.
+                        * Also, do not complain if it doesn't exist.
+                        * Some people configure kernel to have no blockdevs.
+                        */
+                       recursive_action("/sys/block",
+                               ACTION_RECURSE | ACTION_FOLLOWLINKS | ACTION_QUIET,
+                               fileAction, dirAction, temp, 0);
+               }
+               recursive_action("/sys/class",
+                       ACTION_RECURSE | ACTION_FOLLOWLINKS,
+                       fileAction, dirAction, temp, 0);
+       } else {
+               char *fw;
+               char *seq;
+               char *action;
+               char *env_path;
+
+               /* Hotplug:
+                * env ACTION=... DEVPATH=... SUBSYSTEM=... [SEQNUM=...] mdev
+                * ACTION can be "add" or "remove"
+                * DEVPATH is like "/block/sda" or "/class/input/mice"
+                */
+               action = getenv("ACTION");
+               env_path = getenv("DEVPATH");
+               subsystem = getenv("SUBSYSTEM");
+               if (!action || !env_path /*|| !subsystem*/)
+                       bb_show_usage();
+               fw = getenv("FIRMWARE");
+
+               /* If it exists, does /dev/mdev.seq match $SEQNUM?
+                * If it does not match, earlier mdev is running
+                * in parallel, and we need to wait */
+               seq = getenv("SEQNUM");
+               if (seq) {
+                       int timeout = 2000 / 32; /* 2000 msec */
+                       do {
+                               int seqlen;
+                               char seqbuf[sizeof(int)*3 + 2];
+
+                               seqlen = open_read_close("mdev.seq", seqbuf, sizeof(seqbuf-1));
+                               if (seqlen < 0) {
+                                       seq = NULL;
+                                       break;
+                               }
+                               seqbuf[seqlen] = '\0';
+                               if (seqbuf[0] == '\n' /* seed file? */
+                                || strcmp(seq, seqbuf) == 0 /* correct idx? */
+                               ) {
+                                       break;
+                               }
+                               usleep(32*1000);
+                       } while (--timeout);
+               }
+
+               snprintf(temp, PATH_MAX, "/sys%s", env_path);
+               if (strcmp(action, "remove") == 0) {
+                       /* Ignoring "remove firmware". It was reported
+                        * to happen and to cause erroneous deletion
+                        * of device nodes. */
+                       if (!fw)
+                               make_device(temp, 1);
+               }
+               else if (strcmp(action, "add") == 0) {
+                       make_device(temp, 0);
+                       if (ENABLE_FEATURE_MDEV_LOAD_FIRMWARE) {
+                               if (fw)
+                                       load_firmware(fw, temp);
+                       }
+               }
+
+               if (seq) {
+                       xopen_xwrite_close("mdev.seq", utoa(xatou(seq) + 1));
+               }
+       }
+
+       if (ENABLE_FEATURE_CLEAN_UP)
+               RELEASE_CONFIG_BUFFER(temp);
+
+       return EXIT_SUCCESS;
+}
diff --git a/util-linux/minix.h b/util-linux/minix.h
new file mode 100644 (file)
index 0000000..3e2b989
--- /dev/null
@@ -0,0 +1,98 @@
+/*
+ * 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 superblock data on disk
+ */
+struct minix_superblock {
+       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 #defined */
+#undef BLOCK_SIZE
+
+enum {
+       BLOCK_SIZE              = 1024,
+       BITS_PER_BLOCK          = BLOCK_SIZE << 3,
+
+       MINIX_ROOT_INO          = 1,
+       MINIX_BAD_INO           = 2,
+
+       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),
+};
+
+/*
+Basic test script for regressions in mkfs/fsck.
+Copies current dir into image (typically bbox build tree).
+
+#!/bin/sh
+tmpdir=/tmp/minixtest-$$
+tmpimg=/tmp/minix-img-$$
+
+mkdir $tmpdir
+dd if=/dev/zero of=$tmpimg bs=1M count=20 || exit
+./busybox mkfs.minix $tmpimg || exit
+mount -o loop $tmpimg $tmpdir || exit
+cp -a "$PWD" $tmpdir
+umount $tmpdir || exit
+./busybox fsck.minix -vfm $tmpimg || exit
+echo "Continue?"
+read junk
+./busybox fsck.minix -vfml $tmpimg || exit
+rmdir $tmpdir
+rm $tmpimg
+
+*/
diff --git a/util-linux/mkfs_minix.c b/util-linux/mkfs_minix.c
new file mode 100644 (file)
index 0000000..18512a3
--- /dev/null
@@ -0,0 +1,731 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * mkfs.c - make a linux (minix) file-system.
+ *
+ * (C) 1991 Linus Torvalds.
+ *
+ * Licensed under GPLv2, see file LICENSE in this tarball for details.
+ */
+
+/*
+ * 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 "libbb.h"
+#include <mntent.h>
+
+#include "minix.h"
+
+/* Store the very same times/uids/gids for image consistency */
+#if 1
+# define CUR_TIME 0
+# define GETUID 0
+# define GETGID 0
+#else
+/* Was using this. Is it useful? NB: this will break testsuite */
+# define CUR_TIME time(NULL)
+# define GETUID getuid()
+# define GETGID getgid()
+#endif
+
+enum {
+       MAX_GOOD_BLOCKS         = 512,
+       TEST_BUFFER_BLOCKS      = 16,
+};
+
+#if !ENABLE_FEATURE_MINIX2
+enum { version2 = 0 };
+#endif
+
+enum { dev_fd = 3 };
+
+struct globals {
+#if ENABLE_FEATURE_MINIX2
+       smallint version2;
+#define version2 G.version2
+#endif
+       char *device_name;
+       uint32_t total_blocks;
+       int badblocks;
+       int namelen;
+       int dirsize;
+       int magic;
+       char *inode_buffer;
+       char *inode_map;
+       char *zone_map;
+       int used_good_blocks;
+       unsigned long req_nr_inodes;
+       unsigned currently_testing;
+
+       char root_block[BLOCK_SIZE];
+       char superblock_buffer[BLOCK_SIZE];
+       char boot_block_buffer[512];
+       unsigned short good_blocks_table[MAX_GOOD_BLOCKS];
+       /* check_blocks(): buffer[] was the biggest static in entire bbox */
+       char check_blocks_buffer[BLOCK_SIZE * TEST_BUFFER_BLOCKS];
+
+       unsigned short ind_block1[BLOCK_SIZE >> 1];
+       unsigned short dind_block1[BLOCK_SIZE >> 1];
+       unsigned long ind_block2[BLOCK_SIZE >> 2];
+       unsigned long dind_block2[BLOCK_SIZE >> 2];
+};
+#define G (*ptr_to_globals)
+#define INIT_G() do { \
+       SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
+} while (0)
+
+static ALWAYS_INLINE unsigned div_roundup(unsigned size, unsigned n)
+{
+       return (size + n-1) / n;
+}
+
+#define INODE_BUF1              (((struct minix1_inode*)G.inode_buffer) - 1)
+#define INODE_BUF2              (((struct minix2_inode*)G.inode_buffer) - 1)
+
+#define SB                      (*(struct minix_superblock*)G.superblock_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)
+
+/* Before you ask "where they come from?": */
+/* setbit/clrbit are supplied by sys/param.h */
+
+static int minix_bit(const char* a, unsigned i)
+{
+       return a[i >> 3] & (1<<(i & 7));
+}
+
+static void minix_setbit(char *a, unsigned i)
+{
+       setbit(a, i);
+}
+static void minix_clrbit(char *a, unsigned i)
+{
+       clrbit(a, i);
+}
+
+/* Note: do not assume 0/1, it is 0/nonzero */
+#define zone_in_use(x)  minix_bit(G.zone_map,(x)-SB_FIRSTZONE+1)
+/*#define inode_in_use(x) minix_bit(G.inode_map,(x))*/
+
+#define mark_inode(x)   minix_setbit(G.inode_map,(x))
+#define unmark_inode(x) minix_clrbit(G.inode_map,(x))
+#define mark_zone(x)    minix_setbit(G.zone_map,(x)-SB_FIRSTZONE+1)
+#define unmark_zone(x)  minix_clrbit(G.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 superblock 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, G.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, G.superblock_buffer, BLOCK_SIZE);
+
+       msg_eol = "cannot write inode map";
+       xwrite(dev_fd, G.inode_map, SB_IMAPS * BLOCK_SIZE);
+
+       msg_eol = "cannot write zone map";
+       xwrite(dev_fd, G.zone_map, SB_ZMAPS * BLOCK_SIZE);
+
+       msg_eol = "cannot write inodes";
+       xwrite(dev_fd, G.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 (G.used_good_blocks + 1 >= MAX_GOOD_BLOCKS)
+               bb_error_msg_and_die("too many bad blocks");
+       if (G.used_good_blocks)
+               blk = G.good_blocks_table[G.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");
+       G.good_blocks_table[G.used_good_blocks] = blk;
+       G.used_good_blocks++;
+       return blk;
+}
+
+static void mark_good_blocks(void)
+{
+       int blk;
+
+       for (blk = 0; blk < G.used_good_blocks; blk++)
+               mark_zone(G.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;
+       /* moved to globals to reduce stack usage
+       unsigned short ind_block[BLOCK_SIZE >> 1];
+       unsigned short dind_block[BLOCK_SIZE >> 1];
+       */
+#define ind_block (G.ind_block1)
+#define dind_block (G.dind_block1)
+
+#define NEXT_BAD (zone = next(zone))
+
+       if (!G.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 = G.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);
+#undef ind_block
+#undef 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;
+       /* moved to globals to reduce stack usage
+       unsigned long ind_block[BLOCK_SIZE >> 2];
+       unsigned long dind_block[BLOCK_SIZE >> 2];
+       */
+#define ind_block (G.ind_block2)
+#define dind_block (G.dind_block2)
+
+       if (!G.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 = G.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);
+#undef ind_block
+#undef dind_block
+}
+#else
+void make_bad_inode2(void);
+#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 (G.badblocks)
+               inode->i_size = 3 * G.dirsize;
+       else {
+               G.root_block[2 * G.dirsize] = '\0';
+               G.root_block[2 * G.dirsize + 1] = '\0';
+               inode->i_size = 2 * G.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], G.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 (G.badblocks)
+               inode->i_size = 3 * G.dirsize;
+       else {
+               G.root_block[2 * G.dirsize] = '\0';
+               G.root_block[2 * G.dirsize + 1] = '\0';
+               inode->i_size = 2 * G.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], G.root_block);
+}
+#else
+void make_root_inode2(void);
+#endif
+
+/*
+ * Perform a test of a block; return the number of
+ * blocks readable.
+ */
+static size_t do_check(char *buffer, size_t try, unsigned current_block)
+{
+       ssize_t 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;
+       try = ((size_t)got) / BLOCK_SIZE;
+
+       if (got & (BLOCK_SIZE - 1))
+               fprintf(stderr, "Short read at block %u\n", (unsigned)(current_block + try));
+       return try;
+}
+
+static void alarm_intr(int alnum UNUSED_PARAM)
+{
+       if (G.currently_testing >= SB_ZONES)
+               return;
+       signal(SIGALRM, alarm_intr);
+       alarm(5);
+       if (!G.currently_testing)
+               return;
+       printf("%d ...", G.currently_testing);
+       fflush(stdout);
+}
+
+static void check_blocks(void)
+{
+       size_t try, got;
+
+       G.currently_testing = 0;
+       signal(SIGALRM, alarm_intr);
+       alarm(5);
+       while (G.currently_testing < SB_ZONES) {
+               msg_eol = "seek failed in check_blocks";
+               xlseek(dev_fd, G.currently_testing * BLOCK_SIZE, SEEK_SET);
+               msg_eol = "\n";
+               try = TEST_BUFFER_BLOCKS;
+               if (G.currently_testing + try > SB_ZONES)
+                       try = SB_ZONES - G.currently_testing;
+               got = do_check(G.check_blocks_buffer, try, G.currently_testing);
+               G.currently_testing += got;
+               if (got == try)
+                       continue;
+               if (G.currently_testing < SB_FIRSTZONE)
+                       bb_error_msg_and_die("bad blocks before data-area: cannot make fs");
+               mark_zone(G.currently_testing);
+               G.badblocks++;
+               G.currently_testing++;
+       }
+       alarm(0);
+       printf("%d bad block(s)\n", G.badblocks);
+}
+
+static void get_list_blocks(char *filename)
+{
+       FILE *listfile;
+       unsigned long blockno;
+
+       listfile = xfopen_for_read(filename);
+       while (!feof(listfile)) {
+               fscanf(listfile, "%ld\n", &blockno);
+               mark_zone(blockno);
+               G.badblocks++;
+       }
+       printf("%d bad block(s)\n", G.badblocks);
+}
+
+static void setup_tables(void)
+{
+       unsigned long inodes;
+       unsigned norm_firstzone;
+       unsigned sb_zmaps;
+       unsigned i;
+
+       /* memset(G.superblock_buffer, 0, BLOCK_SIZE); */
+       /* memset(G.boot_block_buffer, 0, 512); */
+       SB_MAGIC = G.magic;
+       SB_ZONE_SIZE = 0;
+       SB_MAXSIZE = version2 ? 0x7fffffff : (7 + 512 + 512 * 512) * 1024;
+       if (version2)
+               SB.s_zones = G.total_blocks;
+       else
+               SB.s_nzones = G.total_blocks;
+
+       /* some magic nrs: 1 inode / 3 blocks */
+       if (G.req_nr_inodes == 0)
+               inodes = G.total_blocks / 3;
+       else
+               inodes = G.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(G.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;
+       G.inode_map = xmalloc(SB_IMAPS * BLOCK_SIZE);
+       G.zone_map = xmalloc(SB_ZMAPS * BLOCK_SIZE);
+       memset(G.inode_map, 0xff, SB_IMAPS * BLOCK_SIZE);
+       memset(G.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);
+       G.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);
+}
+
+int mkfs_minix_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int mkfs_minix_main(int argc UNUSED_PARAM, char **argv)
+{
+       unsigned opt;
+       char *tmp;
+       struct stat statbuf;
+       char *str_i;
+       char *listfile = NULL;
+
+       INIT_G();
+/* default (changed to 30, per Linus's suggestion, Sun Nov 21 08:05:07 1993) */
+       G.namelen = 30;
+       G.dirsize = 32;
+       G.magic = MINIX1_SUPER_MAGIC2;
+
+       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_complementary = "n+"; /* -n N */
+       opt = getopt32(argv, "ci:l:n:v", &str_i, &listfile, &G.namelen);
+       argv += optind;
+       //if (opt & 1) -c
+       if (opt & 2) G.req_nr_inodes = xatoul(str_i); // -i
+       //if (opt & 4) -l
+       if (opt & 8) { // -n
+               if (G.namelen == 14) G.magic = MINIX1_SUPER_MAGIC;
+               else if (G.namelen == 30) G.magic = MINIX1_SUPER_MAGIC2;
+               else bb_show_usage();
+               G.dirsize = G.namelen + 2;
+       }
+       if (opt & 0x10) { // -v
+#if ENABLE_FEATURE_MINIX2
+               version2 = 1;
+#else
+               bb_error_msg_and_die("not compiled with minix v2 support");
+#endif
+       }
+
+       G.device_name = *argv++;
+       if (!G.device_name)
+               bb_show_usage();
+       if (*argv)
+               G.total_blocks = xatou32(*argv);
+       else
+               G.total_blocks = get_size(G.device_name) / 1024;
+
+       if (G.total_blocks < 10)
+               bb_error_msg_and_die("must have at least 10 blocks");
+
+       if (version2) {
+               G.magic = MINIX2_SUPER_MAGIC2;
+               if (G.namelen == 14)
+                       G.magic = MINIX2_SUPER_MAGIC;
+       } else if (G.total_blocks > 65535)
+               G.total_blocks = 65535;
+
+       /* Check if it is mounted */
+       if (find_mount_point(G.device_name))
+               bb_error_msg_and_die("can't format mounted filesystem");
+
+       xmove_fd(xopen(G.device_name, O_RDWR), dev_fd);
+       if (fstat(dev_fd, &statbuf) < 0)
+               bb_error_msg_and_die("cannot stat %s", G.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'", G.device_name);
+#endif
+
+       tmp = G.root_block;
+       *(short *) tmp = 1;
+       strcpy(tmp + 2, ".");
+       tmp += G.dirsize;
+       *(short *) tmp = 1;
+       strcpy(tmp + 2, "..");
+       tmp += G.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/mkfs_vfat.c b/util-linux/mkfs_vfat.c
new file mode 100644 (file)
index 0000000..aa6ae92
--- /dev/null
@@ -0,0 +1,614 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * mkfs_vfat: utility to create FAT32 filesystem
+ * inspired by dosfstools
+ *
+ * Busybox'ed (2009) by Vladimir Dronnikov <dronnikov@gmail.com>
+ *
+ * Licensed under GPLv2, see file LICENSE in this tarball for details.
+ */
+#include "libbb.h"
+#include "volume_id/volume_id_internal.h"
+
+#include <linux/hdreg.h> /* HDIO_GETGEO */
+#include <linux/fd.h>    /* FDGETPRM */
+//#include <linux/msdos_fs.h>
+
+#define SECTOR_SIZE             512
+
+#define SECTORS_PER_BLOCK      (BLOCK_SIZE / SECTOR_SIZE)
+
+// M$ says the high 4 bits of a FAT32 FAT entry are reserved
+#define EOF_FAT32       0x0FFFFFF8
+#define BAD_FAT32       0x0FFFFFF7
+#define MAX_CLUST_32    0x0FFFFFF0
+
+#define ATTR_VOLUME     8
+
+#define        NUM_FATS        2
+
+/* FAT32 filesystem looks like this:
+ * sector -nn...-1: "hidden" sectors, all sectors before this partition
+ * (-h hidden-sectors sets it. Useful only for boot loaders,
+ *  they need to know _disk_ offset in order to be able to correctly
+ *  address sectors relative to start of disk)
+ * sector 0: boot sector
+ * sector 1: info sector
+ * sector 2: set aside for boot code which didn't fit into sector 0
+ * ...(zero-filled sectors)...
+ * sector B: backup copy of sector 0 [B set by -b backup-boot-sector]
+ * sector B+1: backup copy of sector 1
+ * sector B+2: backup copy of sector 2
+ * ...(zero-filled sectors)...
+ * sector R: FAT#1 [R set by -R reserved-sectors]
+ * ...(FAT#1)...
+ * sector R+fat_size: FAT#2
+ * ...(FAT#2)...
+ * sector R+fat_size*2: cluster #2
+ * ...(cluster #2)...
+ * sector R+fat_size*2+clust_size: cluster #3
+ * ...(the rest is filled by clusters till the end)...
+ */
+
+enum {
+// Perhaps this should remain constant
+       info_sector_number = 1,
+// TODO: make these cmdline options
+// dont forget sanity check: backup_boot_sector + 3 <= reserved_sect
+       backup_boot_sector = 3,
+       reserved_sect      = 6,
+};
+
+// how many blocks we try to read while testing
+#define TEST_BUFFER_BLOCKS      16
+
+struct msdos_dir_entry {
+       char     name[11];       /* 000 name and extension */
+       uint8_t  attr;           /* 00b attribute bits */
+       uint8_t  lcase;          /* 00c case for base and extension */
+       uint8_t  ctime_cs;       /* 00d creation time, centiseconds (0-199) */
+       uint16_t ctime;          /* 00e creation time */
+       uint16_t cdate;          /* 010 creation date */
+       uint16_t adate;          /* 012 last access date */
+       uint16_t starthi;        /* 014 high 16 bits of cluster in FAT32 */
+       uint16_t time;           /* 016 time */
+       uint16_t date;           /* 018 date */
+       uint16_t start;          /* 01a first cluster */
+       uint32_t size;           /* 01c file size in bytes */
+} __attribute__ ((packed));
+
+/* Example of boot sector's beginning:
+0000  eb 58 90 4d 53 57 49 4e  34 2e 31 00 02 08 26 00  |...MSWIN4.1...&.|
+0010  02 00 00 00 00 f8 00 00  3f 00 ff 00 3f 00 00 00  |........?...?...|
+0020  54 9b d0 00 0d 34 00 00  00 00 00 00 02 00 00 00  |T....4..........|
+0030  01 00 06 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
+0040  80 00 29 71 df 51 e0 4e  4f 20 4e 41 4d 45 20 20  |..)q.Q.NO NAME  |
+0050  20 20 46 41 54 33 32 20  20 20 33 c9 8e d1 bc f4  |  FAT32   3.....|
+*/
+struct msdos_volume_info { /* (offsets are relative to start of boot sector) */
+       uint8_t  drive_number;    /* 040 BIOS drive number */
+       uint8_t  reserved;        /* 041 unused */
+       uint8_t  ext_boot_sign;   /* 042 0x29 if fields below exist (DOS 3.3+) */
+       uint32_t volume_id32;     /* 043 volume ID number */
+       char     volume_label[11];/* 047 volume label */
+       char     fs_type[8];      /* 052 typically "FATnn" */
+} __attribute__ ((packed));       /* 05a end. Total size 26 (0x1a) bytes */
+
+struct msdos_boot_sector {
+       char     boot_jump[3];       /* 000 short or near jump instruction */
+       char     system_id[8];       /* 003 name - can be used to special case partition manager volumes */
+       uint16_t bytes_per_sect;     /* 00b bytes per logical sector */
+       uint8_t  sect_per_clust;     /* 00d sectors/cluster */
+       uint16_t reserved_sect;      /* 00e reserved sectors (sector offset of 1st FAT relative to volume start) */
+       uint8_t  fats;               /* 010 number of FATs */
+       uint16_t dir_entries;        /* 011 root directory entries */
+       uint16_t volume_size_sect;   /* 013 volume size in sectors */
+       uint8_t  media_byte;         /* 015 media code */
+       uint16_t sect_per_fat;       /* 016 sectors/FAT */
+       uint16_t sect_per_track;     /* 018 sectors per track */
+       uint16_t heads;              /* 01a number of heads */
+       uint32_t hidden;             /* 01c hidden sectors (sector offset of volume within physical disk) */
+       uint32_t fat32_volume_size_sect; /* 020 volume size in sectors (if volume_size_sect == 0) */
+       uint32_t fat32_sect_per_fat; /* 024 sectors/FAT */
+       uint16_t fat32_flags;        /* 028 bit 8: fat mirroring, low 4: active fat */
+       uint8_t  fat32_version[2];   /* 02a major, minor filesystem version (I see 0,0) */
+       uint32_t fat32_root_cluster; /* 02c first cluster in root directory */
+       uint16_t fat32_info_sector;  /* 030 filesystem info sector (usually 1) */
+       uint16_t fat32_backup_boot;  /* 032 backup boot sector (usually 6) */
+       uint32_t reserved2[3];       /* 034 unused */
+       struct msdos_volume_info vi; /* 040 */
+       char     boot_code[0x200 - 0x5a - 2]; /* 05a */
+#define BOOT_SIGN 0xAA55
+       uint16_t boot_sign;          /* 1fe */
+} __attribute__ ((packed));
+
+#define FAT_FSINFO_SIG1 0x41615252
+#define FAT_FSINFO_SIG2 0x61417272
+struct fat32_fsinfo {
+       uint32_t signature1;         /* 0x52,0x52,0x41,0x61, "RRaA" */
+       uint32_t reserved1[128 - 8];
+       uint32_t signature2;         /* 0x72,0x72,0x61,0x41, "rrAa" */
+       uint32_t free_clusters;      /* free cluster count.  -1 if unknown */
+       uint32_t next_cluster;       /* most recently allocated cluster */
+       uint32_t reserved2[3];
+       uint16_t reserved3;          /* 1fc */
+       uint16_t boot_sign;          /* 1fe */
+} __attribute__ ((packed));
+
+struct bug_check {
+       char BUG1[sizeof(struct msdos_dir_entry  ) == 0x20 ? 1 : -1];
+       char BUG2[sizeof(struct msdos_volume_info) == 0x1a ? 1 : -1];
+       char BUG3[sizeof(struct msdos_boot_sector) == 0x200 ? 1 : -1];
+       char BUG4[sizeof(struct fat32_fsinfo     ) == 0x200 ? 1 : -1];
+};
+
+static const char boot_code[] ALIGN1 =
+       "\x0e"          /* 05a:         push  cs */
+       "\x1f"          /* 05b:         pop   ds */
+       "\xbe\x77\x7c"  /*  write_msg:  mov   si, offset message_txt */
+       "\xac"          /* 05f:         lodsb */
+       "\x22\xc0"      /* 060:         and   al, al */
+       "\x74\x0b"      /* 062:         jz    key_press */
+       "\x56"          /* 064:         push  si */
+       "\xb4\x0e"      /* 065:         mov   ah, 0eh */
+       "\xbb\x07\x00"  /* 067:         mov   bx, 0007h */
+       "\xcd\x10"      /* 06a:         int   10h */
+       "\x5e"          /* 06c:         pop   si */
+       "\xeb\xf0"      /* 06d:         jmp   write_msg */
+       "\x32\xe4"      /*  key_press:  xor   ah, ah */
+       "\xcd\x16"      /* 071:         int   16h */
+       "\xcd\x19"      /* 073:         int   19h */
+       "\xeb\xfe"      /*  foo:        jmp   foo */
+       /* 077: message_txt: */
+       "This is not a bootable disk\r\n";
+
+
+#define MARK_CLUSTER(cluster, value) \
+       ((uint32_t *)fat)[cluster] = cpu_to_le32(value)
+
+void BUG_unsupported_field_size(void);
+#define STORE_LE(field, value) \
+do { \
+       if (sizeof(field) == 4) \
+               field = cpu_to_le32(value); \
+       else if (sizeof(field) == 2) \
+               field = cpu_to_le16(value); \
+       else if (sizeof(field) == 1) \
+               field = (value); \
+       else \
+               BUG_unsupported_field_size(); \
+} while (0)
+
+/* compat:
+ * mkdosfs 2.11 (12 Mar 2005)
+ * Usage: mkdosfs [-A] [-c] [-C] [-v] [-I] [-l bad-block-file]
+ *        [-b backup-boot-sector]
+ *        [-m boot-msg-file] [-n volume-name] [-i volume-id]
+ *        [-s sectors-per-cluster] [-S logical-sector-size]
+ *        [-f number-of-FATs]
+ *        [-h hidden-sectors] [-F fat-size] [-r root-dir-entries]
+ *        [-R reserved-sectors]
+ *        /dev/name [blocks]
+ */
+int mkfs_vfat_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int mkfs_vfat_main(int argc UNUSED_PARAM, char **argv)
+{
+       struct stat st;
+       const char *volume_label = "";
+       char *buf;
+       char *device_name;
+       uoff_t volume_size_bytes;
+       uoff_t volume_size_sect;
+       uint32_t total_clust;
+       uint32_t volume_id;
+       int dev;
+       unsigned bytes_per_sect;
+       unsigned sect_per_fat;
+       unsigned opts;
+       uint16_t sect_per_track;
+       uint8_t media_byte;
+       uint8_t sect_per_clust;
+       uint8_t heads;
+       enum {
+               OPT_A = 1 << 0,  // [IGNORED] atari format
+               OPT_b = 1 << 1,  // [IGNORED] location of backup boot sector
+               OPT_c = 1 << 2,  // [IGNORED] check filesystem
+               OPT_C = 1 << 3,  // [IGNORED] create a new file
+               OPT_f = 1 << 4,  // [IGNORED] number of FATs
+               OPT_F = 1 << 5,  // [IGNORED, implied 32] choose FAT size
+               OPT_h = 1 << 6,  // [IGNORED] number of hidden sectors
+               OPT_I = 1 << 7,  // [IGNORED] don't bark at entire disk devices
+               OPT_i = 1 << 8,  // [IGNORED] volume ID
+               OPT_l = 1 << 9,  // [IGNORED] bad block filename
+               OPT_m = 1 << 10, // [IGNORED] message file
+               OPT_n = 1 << 11, // volume label
+               OPT_r = 1 << 12, // [IGNORED] root directory entries
+               OPT_R = 1 << 13, // [IGNORED] number of reserved sectors
+               OPT_s = 1 << 14, // [IGNORED] sectors per cluster
+               OPT_S = 1 << 15, // [IGNORED] sector size
+               OPT_v = 1 << 16, // verbose
+       };
+
+       opt_complementary = "-1";//:b+:f+:F+:h+:r+:R+:s+:S+:vv:c--l:l--c";
+       opts = getopt32(argv, "Ab:cCf:F:h:Ii:l:m:n:r:R:s:S:v",
+               NULL, NULL, NULL, NULL, NULL,
+               NULL, NULL, &volume_label, NULL, NULL, NULL, NULL);
+       argv += optind;
+
+       // cache device name
+       device_name = argv[0];
+       // default volume ID = creation time
+       volume_id = time(NULL);
+
+       dev = xopen(device_name, O_EXCL | O_RDWR);
+       if (fstat(dev, &st) < 0)
+               bb_simple_perror_msg_and_die(device_name);
+
+       //
+       // Get image size and sector size
+       //
+       bytes_per_sect = SECTOR_SIZE;
+       volume_size_bytes = st.st_size;
+       if (!S_ISBLK(st.st_mode)) {
+               if (!S_ISREG(st.st_mode)) {
+                       if (!argv[1])
+                               bb_error_msg_and_die("image size must be specified");
+               }
+               // not a block device, skip bad sectors check
+               opts &= ~OPT_c;
+       } else {
+               int min_bytes_per_sect;
+
+               // more portable than BLKGETSIZE[64]
+               volume_size_bytes = xlseek(dev, 0, SEEK_END);
+               xlseek(dev, 0, SEEK_SET);
+#if 0
+               unsigned device_num;
+               // for true block devices we do check sanity
+               device_num = st.st_rdev & 0xff3f;
+               // do we allow to format the whole disk device?
+               if (!(opts & OPT_I) && (
+                       device_num == 0x0300 || // hda, hdb
+                       (device_num & 0xff0f) == 0x0800 || // sd
+                       device_num == 0x0d00 || // xd
+                       device_num == 0x1600 )  // hdc, hdd
+               )
+                       bb_error_msg_and_die("will not try to make filesystem on full-disk device (use -I if wanted)");
+               // can't work on mounted filesystems
+               if (find_mount_point(device_name))
+                       bb_error_msg_and_die("can't format mounted filesystem");
+#endif
+               // get true sector size
+               // (parameter must be int*, not long* or size_t*)
+               xioctl(dev, BLKSSZGET, &min_bytes_per_sect);
+               if (min_bytes_per_sect > SECTOR_SIZE) {
+                       bytes_per_sect = min_bytes_per_sect;
+                       bb_error_msg("for this device sector size is %u", min_bytes_per_sect);
+               }
+       }
+       if (argv[1]) {
+               volume_size_bytes = XATOOFF(argv[1]);
+               if (volume_size_bytes >= MAXINT(off_t) / 1024)
+                       bb_error_msg_and_die("image size is too big");
+               volume_size_bytes *= 1024;
+       }
+       volume_size_sect = volume_size_bytes / bytes_per_sect;
+
+       //
+       // Find out or guess media parameters
+       //
+       media_byte = 0xf8;
+       heads = 255;
+       sect_per_track = 63;
+       sect_per_clust = 1;
+       {
+               struct hd_geometry geometry;
+               // size (in sectors), sect (per track), head
+               struct floppy_struct param;
+
+               // N.B. whether to use HDIO_GETGEO or HDIO_REQ?
+               if (ioctl(dev, HDIO_GETGEO, &geometry) == 0
+                && geometry.sectors
+                && geometry.heads
+               ) {
+                       // hard drive
+                       sect_per_track = geometry.sectors;
+                       heads = geometry.heads;
+
+ set_cluster_size:
+                       /* For FAT32, try to do the same as M$'s format command
+                        * (see http://www.win.tue.nl/~aeb/linux/fs/fat/fatgen103.pdf p. 20):
+                        * fs size <= 260M: 0.5k clusters
+                        * fs size <=   8G: 4k clusters
+                        * fs size <=  16G: 8k clusters
+                        * fs size >   16G: 16k clusters
+                        */
+                       sect_per_clust = 1;
+                       if (volume_size_bytes >= 260*1024*1024) {
+                               sect_per_clust = 8;
+                               /* fight gcc: */
+                               /* "error: integer overflow in expression" */
+                               /* "error: right shift count >= width of type" */
+                               if (sizeof(off_t) > 4) {
+                                       unsigned t = (volume_size_bytes >> 31 >> 1);
+                                       if (t >= 8/4)
+                                               sect_per_clust = 16;
+                                       if (t >= 16/4)
+                                               sect_per_clust = 32;
+                               }
+                       }
+               } else {
+                       // floppy, loop, or regular file
+                       int not_floppy = ioctl(dev, FDGETPRM, &param);
+                       if (not_floppy == 0) {
+                               // floppy disk
+                               sect_per_track = param.sect;
+                               heads = param.head;
+                               volume_size_sect = param.size;
+                               volume_size_bytes = param.size * SECTOR_SIZE;
+                       }
+                       // setup the media descriptor byte
+                       switch (volume_size_sect) {
+                       case 2*360:     // 5.25", 2, 9, 40 - 360K
+                               media_byte = 0xfd;
+                               break;
+                       case 2*720:     // 3.5", 2, 9, 80 - 720K
+                       case 2*1200:    // 5.25", 2, 15, 80 - 1200K
+                               media_byte = 0xf9;
+                               break;
+                       default:        // anything else
+                               if (not_floppy)
+                                       goto set_cluster_size;
+                       case 2*1440:    // 3.5", 2, 18, 80 - 1440K
+                       case 2*2880:    // 3.5", 2, 36, 80 - 2880K
+                               media_byte = 0xf0;
+                               break;
+                       }
+                       // not floppy, but size matches floppy exactly.
+                       // perhaps it is a floppy image.
+                       // we already set media_byte as if it is a floppy,
+                       // now set sect_per_track and heads.
+                       heads = 2;
+                       sect_per_track = (unsigned)volume_size_sect / 160;
+                       if (sect_per_track < 9)
+                               sect_per_track = 9;
+               }
+       }
+
+       //
+       // Calculate number of clusters, sectors/cluster, sectors/FAT
+       // (an initial guess for sect_per_clust should already be set)
+       //
+       // "mkdosfs -v -F 32 image5k 5" is the minimum:
+       // 2 sectors for FATs and 2 data sectors
+       if ((off_t)(volume_size_sect - reserved_sect) < 4)
+               bb_error_msg_and_die("the image is too small for FAT32");
+       sect_per_fat = 1;
+       while (1) {
+               while (1) {
+                       int spf_adj;
+                       off_t tcl = (volume_size_sect - reserved_sect - NUM_FATS * sect_per_fat) / sect_per_clust;
+                       // tcl may be > MAX_CLUST_32 here, but it may be
+                       // because sect_per_fat is underestimated,
+                       // and with increased sect_per_fat it still may become
+                       // <= MAX_CLUST_32. Therefore, we do not check
+                       // against MAX_CLUST_32, but against a bigger const:
+                       if (tcl > 0x7fffffff)
+                               goto next;
+                       total_clust = tcl; // fits in uint32_t
+                       spf_adj = ((total_clust + 2) * 4 + bytes_per_sect - 1) / bytes_per_sect - sect_per_fat;
+#if 0
+                       bb_error_msg("sect_per_clust:%u sect_per_fat:%u total_clust:%u",
+                                       sect_per_clust, sect_per_fat, (int)tcl);
+                       bb_error_msg("adjust to sect_per_fat:%d", spf_adj);
+#endif
+                       if (spf_adj <= 0) {
+                               // do not need to adjust sect_per_fat.
+                               // so, was total_clust too big after all?
+                               if (total_clust <= MAX_CLUST_32)
+                                       goto found_total_clust; // no
+                               // yes, total_clust is _a bit_ too big
+                               goto next;
+                       }
+                       // adjust sect_per_fat, go back and recalc total_clust
+                       // (note: just "sect_per_fat += spf_adj" isn't ok)
+                       sect_per_fat += ((unsigned)spf_adj / 2) | 1;
+               }
+ next:
+               if (sect_per_clust == 128)
+                       bb_error_msg_and_die("can't make FAT32 with >128 sectors/cluster");
+               sect_per_clust *= 2;
+               sect_per_fat = (sect_per_fat / 2) | 1;
+       }
+ found_total_clust:
+
+       //
+       // Print info
+       //
+       if (opts & OPT_v) {
+               fprintf(stderr,
+                       "Device '%s':\n"
+                       "heads:%u, sectors/track:%u, bytes/sector:%u\n"
+                       "media descriptor:%02x\n"
+                       "total sectors:%"OFF_FMT"u, clusters:%u, sectors/cluster:%u\n"
+                       "FATs:2, sectors/FAT:%u\n"
+                       "volumeID:%08x, label:'%s'\n",
+                       device_name,
+                       heads, sect_per_track, bytes_per_sect,
+                       (int)media_byte,
+                       volume_size_sect, (int)total_clust, (int)sect_per_clust,
+                       sect_per_fat,
+                       (int)volume_id, volume_label
+               );
+       }
+
+       //
+       // Write filesystem image sequentially (no seeking)
+       //
+       {
+               // (a | b) is poor man's max(a, b)
+               unsigned bufsize = reserved_sect;
+               //bufsize |= sect_per_fat; // can be quite large
+               bufsize |= 2; // use this instead
+               bufsize |= sect_per_clust;
+               buf = xzalloc(bufsize * bytes_per_sect);
+       }
+
+       { // boot and fsinfo sectors, and their copies
+               struct msdos_boot_sector *boot_blk = (void*)buf;
+               struct fat32_fsinfo *info = (void*)(buf + bytes_per_sect);
+
+               strcpy(boot_blk->boot_jump, "\xeb\x58\x90" "mkdosfs"); // system_id[8] included :)
+               STORE_LE(boot_blk->bytes_per_sect, bytes_per_sect);
+               STORE_LE(boot_blk->sect_per_clust, sect_per_clust);
+               STORE_LE(boot_blk->reserved_sect, reserved_sect);
+               STORE_LE(boot_blk->fats, 2);
+               //STORE_LE(boot_blk->dir_entries, 0); // for FAT32, stays 0
+               if (volume_size_sect <= 0xffff)
+                       STORE_LE(boot_blk->volume_size_sect, volume_size_sect);
+               STORE_LE(boot_blk->media_byte, media_byte);
+               // wrong: this would make Linux think that it's fat12/16:
+               //if (sect_per_fat <= 0xffff)
+               //      STORE_LE(boot_blk->sect_per_fat, sect_per_fat);
+               // works:
+               //STORE_LE(boot_blk->sect_per_fat, 0);
+               STORE_LE(boot_blk->sect_per_track, sect_per_track);
+               STORE_LE(boot_blk->heads, heads);
+               //STORE_LE(boot_blk->hidden, 0);
+               STORE_LE(boot_blk->fat32_volume_size_sect, volume_size_sect);
+               STORE_LE(boot_blk->fat32_sect_per_fat, sect_per_fat);
+               //STORE_LE(boot_blk->fat32_flags, 0);
+               //STORE_LE(boot_blk->fat32_version[2], 0,0);
+               STORE_LE(boot_blk->fat32_root_cluster, 2);
+               STORE_LE(boot_blk->fat32_info_sector, info_sector_number);
+               STORE_LE(boot_blk->fat32_backup_boot, backup_boot_sector);
+               //STORE_LE(boot_blk->reserved2[3], 0,0,0);
+               STORE_LE(boot_blk->vi.ext_boot_sign, 0x29);
+               STORE_LE(boot_blk->vi.volume_id32, volume_id);
+               strncpy(boot_blk->vi.fs_type, "FAT32   ", sizeof(boot_blk->vi.fs_type));
+               strncpy(boot_blk->vi.volume_label, volume_label, sizeof(boot_blk->vi.volume_label));
+               memcpy(boot_blk->boot_code, boot_code, sizeof(boot_code));
+               STORE_LE(boot_blk->boot_sign, BOOT_SIGN);
+
+               STORE_LE(info->signature1, FAT_FSINFO_SIG1);
+               STORE_LE(info->signature2, FAT_FSINFO_SIG2);
+               // we've allocated cluster 2 for the root dir
+               STORE_LE(info->free_clusters, (total_clust - 1));
+               STORE_LE(info->next_cluster, 2);
+               STORE_LE(info->boot_sign, BOOT_SIGN);
+
+               // 1st copy
+               xwrite(dev, buf, bytes_per_sect * backup_boot_sector);
+               // 2nd copy and possibly zero sectors
+               xwrite(dev, buf, bytes_per_sect * (reserved_sect - backup_boot_sector));
+       }
+
+       { // file allocation tables
+               unsigned i,j;
+               unsigned char *fat = (void*)buf;
+
+               memset(buf, 0, bytes_per_sect * 2);
+               // initial FAT entries
+               MARK_CLUSTER(0, 0x0fffff00 | media_byte);
+               MARK_CLUSTER(1, 0xffffffff);
+               // mark cluster 2 as EOF (used for root dir)
+               MARK_CLUSTER(2, EOF_FAT32);
+               for (i = 0; i < NUM_FATS; i++) {
+                       xwrite(dev, buf, bytes_per_sect);
+                       for (j = 1; j < sect_per_fat; j++)
+                               xwrite(dev, buf + bytes_per_sect, bytes_per_sect);
+               }
+       }
+
+       // root directory
+       // empty directory is just a set of zero bytes
+       memset(buf, 0, sect_per_clust * bytes_per_sect);
+       if (volume_label[0]) {
+               // create dir entry for volume_label
+               struct msdos_dir_entry *de;
+#if 0
+               struct tm tm;
+               uint16_t t, d;
+#endif
+               de = (void*)buf;
+               strncpy(de->name, volume_label, sizeof(de->name));
+               STORE_LE(de->attr, ATTR_VOLUME);
+#if 0
+               localtime_r(&create_time, &tm);
+               t = (tm.tm_sec >> 1) + (tm.tm_min << 5) + (tm.tm_hour << 11);
+               d = tm.tm_mday + ((tm.tm_mon+1) << 5) + ((tm.tm_year-80) << 9);
+               STORE_LE(de->time, t);
+               STORE_LE(de->date, d);
+               //STORE_LE(de->ctime_cs, 0);
+               de->ctime = de->time;
+               de->cdate = de->date;
+               de->adate = de->date;
+#endif
+       }
+       xwrite(dev, buf, sect_per_clust * bytes_per_sect);
+
+#if 0
+       if (opts & OPT_c) {
+               uoff_t volume_size_blocks;
+               unsigned start_data_sector;
+               unsigned start_data_block;
+               unsigned badblocks = 0;
+               int try, got;
+               off_t currently_testing;
+               char *blkbuf = xmalloc(BLOCK_SIZE * TEST_BUFFER_BLOCKS);
+
+               volume_size_blocks = (volume_size_bytes >> BLOCK_SIZE_BITS);
+               // N.B. the two following vars are in hard sectors, i.e. SECTOR_SIZE byte sectors!
+               start_data_sector = (reserved_sect + NUM_FATS * sect_per_fat) * (bytes_per_sect / SECTOR_SIZE);
+               start_data_block = (start_data_sector + SECTORS_PER_BLOCK - 1) / SECTORS_PER_BLOCK;
+
+               bb_info_msg("Searching for bad blocks ");
+               currently_testing = 0;
+               try = TEST_BUFFER_BLOCKS;
+               while (currently_testing < volume_size_blocks) {
+                       if (currently_testing + try > volume_size_blocks)
+                               try = volume_size_blocks - currently_testing;
+                       // perform a test on a block. return the number of blocks
+                       // that could be read successfully.
+                       // seek to the correct location
+                       xlseek(dev, currently_testing * BLOCK_SIZE, SEEK_SET);
+                       // try reading
+                       got = read(dev, blkbuf, try * BLOCK_SIZE);
+                       if (got < 0)
+                               got = 0;
+                       if (got & (BLOCK_SIZE - 1))
+                               bb_error_msg("Unexpected values in do_check: probably bugs");
+                       got /= BLOCK_SIZE;
+                       currently_testing += got;
+                       if (got == try) {
+                               try = TEST_BUFFER_BLOCKS;
+                               continue;
+                       }
+                       try = 1;
+                       if (currently_testing < start_data_block)
+                               bb_error_msg_and_die("bad blocks before data-area: cannot make fs");
+
+                       // mark all of the sectors in the block as bad
+                       for (i = 0; i < SECTORS_PER_BLOCK; i++) {
+                               int cluster = (currently_testing * SECTORS_PER_BLOCK + i - start_data_sector) / (int) (sect_per_clust) / (bytes_per_sect / SECTOR_SIZE);
+                               if (cluster < 0)
+                                       bb_error_msg_and_die("Invalid cluster number in mark_sector: probably bug!");
+                               MARK_CLUSTER(cluster, BAD_FAT32);
+                       }
+                       badblocks++;
+                       currently_testing++;
+               }
+               free(blkbuf);
+               if (badblocks)
+                       bb_info_msg("%d bad block(s)", badblocks);
+       }
+#endif
+
+       // cleanup
+       if (ENABLE_FEATURE_CLEAN_UP) {
+               free(buf);
+               close(dev);
+       }
+
+       return 0;
+}
diff --git a/util-linux/mkswap.c b/util-linux/mkswap.c
new file mode 100644 (file)
index 0000000..11c411b
--- /dev/null
@@ -0,0 +1,129 @@
+/* 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 "libbb.h"
+
+#if ENABLE_SELINUX
+static void mkswap_selinux_setcontext(int fd, const char *path)
+{
+       struct stat stbuf;
+
+       if (!is_selinux_enabled())
+               return;
+
+       if (fstat(fd, &stbuf) < 0)
+               bb_perror_msg_and_die("fstat failed");
+       if (S_ISREG(stbuf.st_mode)) {
+               security_context_t newcon;
+               security_context_t oldcon = NULL;
+               context_t context;
+
+               if (fgetfilecon(fd, &oldcon) < 0) {
+                       if (errno != ENODATA)
+                               goto error;
+                       if (matchpathcon(path, stbuf.st_mode, &oldcon) < 0)
+                               goto error;
+               }
+               context = context_new(oldcon);
+               if (!context || context_type_set(context, "swapfile_t"))
+                       goto error;
+               newcon = context_str(context);
+               if (!newcon)
+                       goto error;
+               /* fsetfilecon_raw is hidden */
+               if (strcmp(oldcon, newcon) != 0 && fsetfilecon(fd, newcon) < 0)
+                       goto error;
+               if (ENABLE_FEATURE_CLEAN_UP) {
+                       context_free(context);
+                       freecon(oldcon);
+               }
+       }
+       return;
+ error:
+       bb_perror_msg_and_die("SELinux relabeling failed");
+}
+#else
+#define mkswap_selinux_setcontext(fd, path) ((void)0)
+#endif
+
+#if 0 /* from Linux 2.6.23 */
+/*
+ * Magic header for a swap area. The first part of the union is
+ * what the swap magic looks like for the old (limited to 128MB)
+ * swap area format, the second part of the union adds - in the
+ * old reserved area - some extra information. Note that the first
+ * kilobyte is reserved for boot loader or disk label stuff...
+ */
+union swap_header {
+       struct {
+               char reserved[PAGE_SIZE - 10];
+               char magic[10];                 /* SWAP-SPACE or SWAPSPACE2 */
+       } magic;
+       struct {
+               char            bootbits[1024]; /* Space for disklabel etc. */
+               __u32           version;        /* second kbyte, word 0 */
+               __u32           last_page;      /* 1 */
+               __u32           nr_badpages;    /* 2 */
+               unsigned char   sws_uuid[16];   /* 3,4,5,6 */
+               unsigned char   sws_volume[16]; /* 7,8,9,10  */
+               __u32           padding[117];   /* 11..127 */
+               __u32           badpages[1];    /* 128, total 129 32-bit words */
+       } info;
+};
+#endif
+
+#define NWORDS 129
+#define hdr ((uint32_t*)(&bb_common_bufsiz1))
+
+struct BUG_bufsiz1_is_too_small {
+       char BUG_bufsiz1_is_too_small[COMMON_BUFSIZE < (NWORDS * 4) ? -1 : 1];
+};
+
+/* Stored without terminating NUL */
+static const char SWAPSPACE2[sizeof("SWAPSPACE2")-1] ALIGN1 = "SWAPSPACE2";
+
+int mkswap_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int mkswap_main(int argc, char **argv)
+{
+       int fd, pagesize;
+       off_t len;
+
+       // 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);
+       /* fdlength was reported to be unreliable - use seek */
+       len = xlseek(fd, 0, SEEK_END);
+#if ENABLE_SELINUX
+       xlseek(fd, 0, SEEK_SET);
+#endif
+       pagesize = getpagesize();
+       printf("Setting up swapspace version 1, size = %"OFF_FMT"u bytes\n",
+                       len - pagesize);
+       mkswap_selinux_setcontext(fd, argv[1]);
+
+       // Make a header. hdr is zero-filled so far...
+       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, NWORDS * 4);
+       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 (file)
index 0000000..b0f20c4
--- /dev/null
@@ -0,0 +1,201 @@
+/* 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 "libbb.h"
+
+#if ENABLE_FEATURE_USE_TERMIOS
+
+struct globals {
+       int cin_fileno;
+       struct termios initial_settings;
+       struct termios new_settings;
+};
+#define G (*(struct globals*)bb_common_bufsiz1)
+#define INIT_G() ((void)0)
+#define initial_settings (G.initial_settings)
+#define new_settings     (G.new_settings    )
+#define cin_fileno       (G.cin_fileno      )
+
+#define setTermSettings(fd, argp) tcsetattr(fd, TCSANOW, argp)
+#define getTermSettings(fd, argp) tcgetattr(fd, argp)
+
+static void gotsig(int sig UNUSED_PARAM)
+{
+       bb_putchar('\n');
+       setTermSettings(cin_fileno, &initial_settings);
+       exit(EXIT_FAILURE);
+}
+
+#else /* !FEATURE_USE_TERMIOS */
+#define INIT_G() ((void)0)
+#define setTermSettings(fd, argp) ((void)0)
+#endif /* FEATURE_USE_TERMIOS */
+
+#define CONVERTED_TAB_SIZE 8
+
+int more_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int more_main(int argc UNUSED_PARAM, char **argv)
+{
+       int c = c; /* for gcc */
+       int lines;
+       int input = 0;
+       int spaces = 0;
+       int please_display_more_prompt;
+       struct stat st;
+       FILE *file;
+       FILE *cin;
+       int len;
+       unsigned terminal_width;
+       unsigned terminal_height;
+
+       INIT_G();
+
+       argv++;
+       /* Another popular pager, most, detects when stdout
+        * is not a tty and turns into cat. This makes sense. */
+       if (!isatty(STDOUT_FILENO))
+               return bb_cat(argv);
+       cin = fopen_for_read(CURRENT_TTY);
+       if (!cin)
+               return bb_cat(argv);
+
+#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);
+       bb_signals(0
+               + (1 << SIGINT)
+               + (1 << SIGQUIT)
+               + (1 << SIGTERM)
+               , gotsig);
+#endif
+
+       do {
+               file = stdin;
+               if (*argv) {
+                       file = fopen_or_warn(*argv, "r");
+                       if (!file)
+                               continue;
+               }
+               st.st_size = 0;
+               fstat(fileno(file), &st);
+
+               please_display_more_prompt = 0;
+               /* never returns w, h <= 1 */
+               get_terminal_width_height(fileno(cin), &terminal_width, &terminal_height);
+               terminal_height -= 1;
+
+               len = 0;
+               lines = 0;
+               while (spaces || (c = getc(file)) != EOF) {
+                       int wrap;
+                       if (spaces)
+                               spaces--;
+ loop_top:
+                       if (input != 'r' && please_display_more_prompt) {
+                               len = printf("--More-- ");
+                               if (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.
+                                */
+                               for (;;) {
+                                       input = getc(cin);
+                                       input = tolower(input);
+#if !ENABLE_FEATURE_USE_TERMIOS
+                                       printf("\033[A"); /* up cursor */
+#endif
+                                       /* Erase the last message */
+                                       printf("\r%*s\r", len, "");
+
+                                       /* Due to various multibyte escape
+                                        * sequences, it's not ok to accept
+                                        * any input as a command to scroll
+                                        * the screen. We only allow known
+                                        * commands, else we show help msg. */
+                                       if (input == ' ' || input == '\n' || input == 'q' || input == 'r')
+                                               break;
+                                       len = printf("(Enter:next line Space:next page Q:quit R:show the rest)");
+                               }
+                               len = 0;
+                               lines = 0;
+                               please_display_more_prompt = 0;
+
+                               if (input == 'q')
+                                       goto end;
+
+                               /* The user may have resized the terminal.
+                                * Re-read the dimensions. */
+#if ENABLE_FEATURE_USE_TERMIOS
+                               get_terminal_width_height(cin_fileno, &terminal_width, &terminal_height);
+                               terminal_height -= 1;
+#endif
+                       }
+
+                       /* Crudely convert tabs into spaces, which are
+                        * a bajillion times easier to deal with. */
+                       if (c == '\t') {
+                               spaces = CONVERTED_TAB_SIZE - 1;
+                               c = ' ';
+                       }
+
+                       /*
+                        * 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.
+                        */
+                       wrap = (++len > terminal_width);
+                       if (c == '\n' || wrap) {
+                               /* Then outputting this character
+                                * will move us to a new line. */
+                               if (++lines >= terminal_height || input == '\n')
+                                       please_display_more_prompt = 1;
+                               len = 0;
+                       }
+                       if (c != '\n' && wrap) {
+                               /* Then outputting this will also put a character on
+                                * the beginning of that new line. Thus we first want to
+                                * display the prompt (if any), so we skip the putchar()
+                                * and go back to the top of the loop, without reading
+                                * a new character. */
+                               goto loop_top;
+                       }
+                       /* My small mind cannot fathom backspaces and UTF-8 */
+                       putchar(c);
+               }
+               fclose(file);
+               fflush(stdout);
+       } while (*argv && *++argv);
+ end:
+       setTermSettings(cin_fileno, &initial_settings);
+       return 0;
+}
diff --git a/util-linux/mount.c b/util-linux/mount.c
new file mode 100644 (file)
index 0000000..694057b
--- /dev/null
@@ -0,0 +1,2000 @@
+/* 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 <mntent.h>
+#include <syslog.h>
+#include "libbb.h"
+
+#if ENABLE_FEATURE_MOUNT_LABEL
+#include "volume_id.h"
+#endif
+
+// Needed for nfs support only
+#include <sys/utsname.h>
+#undef TRUE
+#undef FALSE
+#include <rpc/rpc.h>
+#include <rpc/pmap_prot.h>
+#include <rpc/pmap_clnt.h>
+
+#ifndef MS_SILENT
+#define MS_SILENT      (1 << 15)
+#endif
+// Grab more as needed from util-linux's mount/mount_constants.h
+#ifndef MS_DIRSYNC
+#define MS_DIRSYNC      128     // Directory modifications are synchronous
+#endif
+
+
+#if defined(__dietlibc__)
+// 16.12.2006, Sampo Kellomaki (sampo@iki.fi)
+// dietlibc-0.30 does not have implementation of getmntent_r()
+static struct mntent *getmntent_r(FILE* stream, struct mntent* result,
+               char* buffer UNUSED_PARAM, int bufsize UNUSED_PARAM)
+{
+       struct mntent* ment = getmntent(stream);
+       return memcpy(result, ment, sizeof(*ment));
+}
+#endif
+
+
+// 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),
+};
+
+
+#define OPTION_STR "o:t:rwanfvsiO:"
+enum {
+       OPT_o = (1 << 0),
+       OPT_t = (1 << 1),
+       OPT_r = (1 << 2),
+       OPT_w = (1 << 3),
+       OPT_a = (1 << 4),
+       OPT_n = (1 << 5),
+       OPT_f = (1 << 6),
+       OPT_v = (1 << 7),
+       OPT_s = (1 << 8),
+       OPT_i = (1 << 9),
+       OPT_O = (1 << 10),
+};
+
+#if ENABLE_FEATURE_MTAB_SUPPORT
+#define useMtab (!(option_mask32 & OPT_n))
+#else
+#define useMtab 0
+#endif
+
+#if ENABLE_FEATURE_MOUNT_FAKE
+#define fakeIt (option_mask32 & OPT_f)
+#else
+#define fakeIt 0
+#endif
+
+
+// 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
+static const int32_t 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,
+               /* "sw"     */ MOUNT_SWAP,
+               /* "swap"   */ MOUNT_SWAP,
+               USE_DESKTOP(/* "user"  */ MOUNT_USERS,)
+               USE_DESKTOP(/* "users" */ MOUNT_USERS,)
+               /* "_netdev" */ 0,
+       )
+
+       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,
+               /* "dirsync"     */ MS_DIRSYNC,
+               /* "async"       */ ~MS_SYNCHRONOUS,
+               /* "atime"       */ ~MS_NOATIME,
+               /* "noatime"     */ MS_NOATIME,
+               /* "diratime"    */ ~MS_NODIRATIME,
+               /* "nodiratime"  */ MS_NODIRATIME,
+               /* "mand"        */ MS_MANDLOCK,
+               /* "nomand"      */ ~MS_MANDLOCK,
+               /* "relatime"    */ MS_RELATIME,
+               /* "norelatime"  */ ~MS_RELATIME,
+               /* "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
+};
+
+static const char mount_option_str[] =
+       USE_FEATURE_MOUNT_LOOP(
+               "loop\0"
+       )
+       USE_FEATURE_MOUNT_FSTAB(
+               "defaults\0"
+               // "quiet\0" - do not filter out, vfat wants to see it
+               "noauto\0"
+               "sw\0"
+               "swap\0"
+               USE_DESKTOP("user\0")
+               USE_DESKTOP("users\0")
+               "_netdev\0"
+       )
+       USE_FEATURE_MOUNT_FLAGS(
+               // vfs flags
+               "nosuid\0"
+               "suid\0"
+               "dev\0"
+               "nodev\0"
+               "exec\0"
+               "noexec\0"
+               "sync\0"
+               "dirsync\0"
+               "async\0"
+               "atime\0"
+               "noatime\0"
+               "diratime\0"
+               "nodiratime\0"
+               "mand\0"
+               "nomand\0"
+               "relatime\0"
+               "norelatime\0"
+               "loud\0"
+
+               // action flags
+               "bind\0"
+               "move\0"
+               "shared\0"
+               "slave\0"
+               "private\0"
+               "unbindable\0"
+               "rshared\0"
+               "rslave\0"
+               "rprivate\0"
+               "runbindable\0"
+       )
+
+       // Always understood.
+       "ro\0"        // vfs flag
+       "rw\0"        // vfs flag
+       "remount\0"   // action flag
+;
+
+
+struct globals {
+#if ENABLE_FEATURE_MOUNT_NFS
+       smalluint nfs_mount_version;
+#endif
+#if ENABLE_FEATURE_MOUNT_VERBOSE
+       unsigned verbose;
+#endif
+       llist_t *fslist;
+       char getmntent_buf[1];
+
+};
+enum { GETMNTENT_BUFSIZE = COMMON_BUFSIZE - offsetof(struct globals, getmntent_buf) };
+#define G (*(struct globals*)&bb_common_bufsiz1)
+#define nfs_mount_version (G.nfs_mount_version)
+#if ENABLE_FEATURE_MOUNT_VERBOSE
+#define verbose           (G.verbose          )
+#else
+#define verbose           0
+#endif
+#define fslist            (G.fslist           )
+#define getmntent_buf     (G.getmntent_buf    )
+
+
+#if ENABLE_FEATURE_MOUNT_VERBOSE
+static int verbose_mount(const char *source, const char *target,
+               const char *filesystemtype,
+               unsigned long mountflags, const void *data)
+{
+       int rc;
+
+       errno = 0;
+       rc = mount(source, target, filesystemtype, mountflags, data);
+       if (verbose >= 2)
+               bb_perror_msg("mount('%s','%s','%s',0x%08lx,'%s'):%d",
+                       source, target, filesystemtype,
+                       mountflags, (char*)data, rc);
+       return rc;
+}
+#else
+#define verbose_mount(...) mount(__VA_ARGS__)
+#endif
+
+#if ENABLE_FEATURE_MOUNT_LABEL
+static void resolve_mount_spec(char **fsname)
+{
+       char *tmp = NULL;
+
+       if (!strncmp(*fsname, "UUID=", 5))
+               tmp = get_devname_from_uuid(*fsname + 5);
+       else if (!strncmp(*fsname, "LABEL=", 6))
+               tmp = get_devname_from_label(*fsname + 6);
+
+       if (tmp)
+               *fsname = tmp;
+}
+#else
+#define resolve_mount_spec(fsname) ((void)0)
+#endif
+
+// Append mount options to string
+static void append_mount_options(char **oldopts, const 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 long parse_mount_options(char *options, char **unrecognized)
+{
+       long flags = MS_SILENT;
+
+       // Loop through options
+       for (;;) {
+               unsigned i;
+               char *comma = strchr(options, ',');
+               const char *option_str = mount_option_str;
+
+               if (comma) *comma = '\0';
+
+// FIXME: use hasmntopt()
+               // Find this option in mount_options
+               for (i = 0; i < ARRAY_SIZE(mount_options); i++) {
+                       if (!strcasecmp(option_str, options)) {
+                               long fl = mount_options[i];
+                               if (fl < 0) flags &= fl;
+                               else flags |= fl;
+                               break;
+                       }
+                       option_str += strlen(option_str) + 1;
+               }
+               // If unrecognized not NULL, append unrecognized mount options
+               if (unrecognized && i == ARRAY_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);
+               }
+
+               if (!comma)
+                       break;
+               // Advance to next option
+               *comma = ',';
+               options = ++comma;
+       }
+
+       return flags;
+}
+
+// Return a list of all block device backed filesystems
+static llist_t *get_block_backed_filesystems(void)
+{
+       static const char filesystems[2][sizeof("/proc/filesystems")] = {
+               "/etc/filesystems",
+               "/proc/filesystems",
+       };
+       char *fs, *buf;
+       llist_t *list = 0;
+       int i;
+       FILE *f;
+
+       for (i = 0; i < 2; i++) {
+               f = fopen_for_read(filesystems[i]);
+               if (!f) continue;
+
+               while ((buf = xmalloc_fgetline(f)) != NULL) {
+                       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;
+}
+
+#if ENABLE_FEATURE_CLEAN_UP
+static void delete_block_backed_filesystems(void)
+{
+       llist_free(fslist, free);
+}
+#else
+void delete_block_backed_filesystems(void);
+#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, long vfsflags, char *filteropts)
+{
+       int rc = 0;
+
+       if (fakeIt) {
+               if (verbose >= 2)
+                       bb_error_msg("would do mount('%s','%s','%s',0x%08lx,'%s')",
+                               mp->mnt_fsname, mp->mnt_dir, mp->mnt_type,
+                               vfsflags, filteropts);
+               goto mtab;
+       }
+
+       // Mount, with fallback to read-only if necessary.
+       for (;;) {
+               errno = 0;
+               rc = verbose_mount(mp->mnt_fsname, mp->mnt_dir, mp->mnt_type,
+                               vfsflags, filteropts);
+
+               // If mount failed, try
+               // helper program mount.<mnt_type>
+               if (ENABLE_FEATURE_MOUNT_HELPERS && rc) {
+                       char *args[6];
+                       int errno_save = errno;
+                       args[0] = xasprintf("mount.%s", mp->mnt_type);
+                       rc = 1;
+                       args[rc++] = mp->mnt_fsname;
+                       args[rc++] = mp->mnt_dir;
+                       if (filteropts) {
+                               args[rc++] = (char *)"-o";
+                               args[rc++] = filteropts;
+                       }
+                       args[rc] = NULL;
+                       rc = wait4pid(spawn(args));
+                       free(args[0]);
+                       if (!rc)
+                               break;
+                       errno = errno_save;
+               }
+
+               if (!rc || (vfsflags & MS_RDONLY) || (errno != EACCES && errno != EROFS))
+                       break;
+               if (!(vfsflags & MS_SILENT))
+                       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 (useMtab && !rc && !(vfsflags & MS_REMOUNT)) {
+               char *fsname;
+               FILE *mountTable = setmntent(bb_path_mtab_file, "a+");
+               const char *option_str = mount_option_str;
+               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] != MS_REMOUNT; i++) {
+                       if (mount_options[i] > 0 && (mount_options[i] & vfsflags))
+                               append_mount_options(&(mp->mnt_opts), option_str);
+                       option_str += strlen(option_str) + 1;
+               }
+
+               // 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 = (char*)"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 Mickiewicz <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;
+       uint8_t 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 */
+       NFS_MOUNT_NORDIRPLUS = 0x4000
+};
+
+
+/*
+ * 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 {
+       short stat;
+       short 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;
+
+       for (i = 0; nfs_errtbl[i].stat != -1; i++) {
+               if (nfs_errtbl[i].stat == status)
+                       return strerror(nfs_errtbl[i].errnum);
+       }
+       return xasprintf("unknown nfs status return value: %d", status);
+}
+
+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)
+
+/*
+ * 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)
+{
+       int kernel_version;
+
+       if (nfs_mount_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 void
+get_mountport(struct pmap *pm_mnt,
+       struct sockaddr_in *server_addr,
+       long unsigned prog,
+       long unsigned version,
+       long unsigned proto,
+       long unsigned port)
+{
+       struct pmaplist *pmap;
+
+       server_addr->sin_port = PMAPPORT;
+/* glibc 2.4 (still) has pmap_getmaps(struct sockaddr_in *).
+ * I understand it like "IPv6 for this is not 100% ready" */
+       pmap = pmap_getmaps(server_addr);
+
+       if (version > MAX_NFSPROT)
+               version = MAX_NFSPROT;
+       if (!prog)
+               prog = MOUNTPROG;
+       pm_mnt->pm_prog = prog;
+       pm_mnt->pm_vers = version;
+       pm_mnt->pm_prot = proto;
+       pm_mnt->pm_port = port;
+
+       while (pmap) {
+               if (pmap->pml_map.pm_prog != prog)
+                       goto next;
+               if (!version && pm_mnt->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 && pm_mnt->pm_prot && pmap->pml_map.pm_prot != proto) ||
+                   (port && pmap->pml_map.pm_port != port))
+                       goto next;
+               memcpy(pm_mnt, &pmap->pml_map, sizeof(*pm_mnt));
+ next:
+               pmap = pmap->pml_next;
+       }
+       if (!pm_mnt->pm_vers)
+               pm_mnt->pm_vers = MOUNTVERS;
+       if (!pm_mnt->pm_port)
+               pm_mnt->pm_port = MOUNTPORT;
+       if (!pm_mnt->pm_prot)
+               pm_mnt->pm_prot = IPPROTO_TCP;
+}
+
+#if BB_MMU
+static int daemonize(void)
+{
+       int pid = fork();
+       if (pid < 0) /* error */
+               return -errno;
+       if (pid > 0) /* parent */
+               return 0;
+       /* child */
+       close(0);
+       xopen(bb_dev_null, O_RDWR);
+       xdup2(0, 1);
+       xdup2(0, 2);
+       setsid();
+       openlog(applet_name, LOG_PID, LOG_DAEMON);
+       logmode = LOGMODE_SYSLOG;
+       return 1;
+}
+#else
+static inline int daemonize(void) { return -ENOSYS; }
+#endif
+
+/* TODO */
+static inline int we_saw_this_host_before(const char *hostname UNUSED_PARAM)
+{
+       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, long 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;
+#if BB_MMU
+       smallint bg = 0;
+#else
+       enum { bg = 0 };
+#endif
+       int retry;
+       int mountprog;
+       int mountvers;
+       int nfsprog;
+       int nfsvers;
+       int retval;
+       /* these all are one-bit really. 4.3.1 likes this combination: */
+       smallint tcp;
+       smallint soft;
+       int intr;
+       int posix;
+       int nocto;
+       int noac;
+       int nordirplus;
+       int nolock;
+
+       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 ((size_t)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;
+
+       soft = 0;
+       intr = 0;
+       posix = 0;
+       nocto = 0;
+       nolock = 0;
+       noac = 0;
+       nordirplus = 0;
+       retry = 10000;          /* 10000 minutes ~ 1 week */
+       tcp = 0;
+
+       mountprog = MOUNTPROG;
+       mountvers = 0;
+       port = 0;
+       mountport = 0;
+       nfsprog = 100003;
+       nfsvers = 0;
+
+       /* parse options */
+       if (filteropts) for (opt = strtok(filteropts, ","); opt; opt = strtok(NULL, ",")) {
+               char *opteq = strchr(opt, '=');
+               if (opteq) {
+                       int val, idx;
+                       static const char options[] ALIGN1 =
+                               /* 0 */ "rsize\0"
+                               /* 1 */ "wsize\0"
+                               /* 2 */ "timeo\0"
+                               /* 3 */ "retrans\0"
+                               /* 4 */ "acregmin\0"
+                               /* 5 */ "acregmax\0"
+                               /* 6 */ "acdirmin\0"
+                               /* 7 */ "acdirmax\0"
+                               /* 8 */ "actimeo\0"
+                               /* 9 */ "retry\0"
+                               /* 10 */ "port\0"
+                               /* 11 */ "mountport\0"
+                               /* 12 */ "mounthost\0"
+                               /* 13 */ "mountprog\0"
+                               /* 14 */ "mountvers\0"
+                               /* 15 */ "nfsprog\0"
+                               /* 16 */ "nfsvers\0"
+                               /* 17 */ "vers\0"
+                               /* 18 */ "proto\0"
+                               /* 19 */ "namlen\0"
+                               /* 20 */ "addr\0";
+
+                       *opteq++ = '\0';
+                       idx = index_in_strings(options, opt);
+                       switch (idx) {
+                       case 12: // "mounthost"
+                               mounthost = xstrndup(opteq,
+                                               strcspn(opteq, " \t\n\r,"));
+                               continue;
+                       case 18: // "proto"
+                               if (!strncmp(opteq, "tcp", 3))
+                                       tcp = 1;
+                               else if (!strncmp(opteq, "udp", 3))
+                                       tcp = 0;
+                               else
+                                       bb_error_msg("warning: unrecognized proto= option");
+                               continue;
+                       case 20: // "addr" - ignore
+                               continue;
+                       }
+
+                       val = xatoi_u(opteq);
+                       switch (idx) {
+                       case 0: // "rsize"
+                               data.rsize = val;
+                               continue;
+                       case 1: // "wsize"
+                               data.wsize = val;
+                               continue;
+                       case 2: // "timeo"
+                               data.timeo = val;
+                               continue;
+                       case 3: // "retrans"
+                               data.retrans = val;
+                               continue;
+                       case 4: // "acregmin"
+                               data.acregmin = val;
+                               continue;
+                       case 5: // "acregmax"
+                               data.acregmax = val;
+                               continue;
+                       case 6: // "acdirmin"
+                               data.acdirmin = val;
+                               continue;
+                       case 7: // "acdirmax"
+                               data.acdirmax = val;
+                               continue;
+                       case 8: // "actimeo"
+                               data.acregmin = val;
+                               data.acregmax = val;
+                               data.acdirmin = val;
+                               data.acdirmax = val;
+                               continue;
+                       case 9: // "retry"
+                               retry = val;
+                               continue;
+                       case 10: // "port"
+                               port = val;
+                               continue;
+                       case 11: // "mountport"
+                               mountport = val;
+                               continue;
+                       case 13: // "mountprog"
+                               mountprog = val;
+                               continue;
+                       case 14: // "mountvers"
+                               mountvers = val;
+                               continue;
+                       case 15: // "nfsprog"
+                               nfsprog = val;
+                               continue;
+                       case 16: // "nfsvers"
+                       case 17: // "vers"
+                               nfsvers = val;
+                               continue;
+                       case 19: // "namlen"
+                               //if (nfs_mount_version >= 2)
+                                       data.namlen = val;
+                               //else
+                               //      bb_error_msg("warning: option namlen is not supported\n");
+                               continue;
+                       default:
+                               bb_error_msg("unknown nfs mount parameter: %s=%d", opt, val);
+                               goto fail;
+                       }
+               }
+               else { /* not of the form opt=val */
+                       static const char options[] ALIGN1 =
+                               "bg\0"
+                               "fg\0"
+                               "soft\0"
+                               "hard\0"
+                               "intr\0"
+                               "posix\0"
+                               "cto\0"
+                               "ac\0"
+                               "tcp\0"
+                               "udp\0"
+                               "lock\0"
+                               "rdirplus\0";
+                       int val = 1;
+                       if (!strncmp(opt, "no", 2)) {
+                               val = 0;
+                               opt += 2;
+                       }
+                       switch (index_in_strings(options, opt)) {
+                       case 0: // "bg"
+#if BB_MMU
+                               bg = val;
+#endif
+                               break;
+                       case 1: // "fg"
+#if BB_MMU
+                               bg = !val;
+#endif
+                               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;
+                       case 11: //rdirplus
+                               nordirplus = !val;
+                               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)
+               | (nordirplus ? NFS_MOUNT_NORDIRPLUS : 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();
+               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;
+                       }
+                       if ((size_t)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;
+/* FIXME: use monotonic()? */
+               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);
+
+               get_mountport(&pm_mnt, &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 = NULL;
+               }
+               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 = NULL;
+                       close(msock);
+                       msock = -1;
+               }
+
+               /* 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);
+       msock = -1;
+
+       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 */
+/* FIXME: parent doesn't close fsock - ??! */
+                                       retval = -daemonized;
+                                       goto ret;
+                               }
+                       }
+                       sleep(delay);   /* 1, 2, 4, 8, 16, 30, ... */
+                       delay *= 2;
+                       if (delay > 30)
+                               delay = 30;
+               }
+       }
+
+       /* Perform actual mount */
+ do_mount:
+       mp->mnt_type = (char*)"nfs";
+       retval = mount_it_now(mp, vfsflags, (char*)&data);
+       goto ret;
+
+       /* Abort */
+ fail:
+       if (msock >= 0) {
+               if (mclient) {
+                       auth_destroy(mclient->cl_auth);
+                       clnt_destroy(mclient);
+               }
+               close(msock);
+       }
+       if (fsock >= 0)
+               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, long 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;
+       long 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") == 0)
+               mp->mnt_type = NULL;
+
+       // Might this be a virtual filesystem?
+       if (ENABLE_FEATURE_MOUNT_HELPERS
+        && (strchr(mp->mnt_fsname, '#'))
+       ) {
+               char *s, *p, *args[35];
+               int n = 0;
+// FIXME: does it allow execution of arbitrary commands?!
+// What args[0] can end up with?
+               for (s = p = mp->mnt_fsname; *s && n < 35-3; ++s) {
+                       if (s[0] == '#' && s[1] != '#') {
+                               *s = '\0';
+                               args[n++] = p;
+                               p = s + 1;
+                       }
+               }
+               args[n++] = p;
+               args[n++] = mp->mnt_dir;
+               args[n] = NULL;
+               rc = wait4pid(xspawn(args));
+               goto report_error;
+       }
+
+       // Might this be an CIFS filesystem?
+       if (ENABLE_FEATURE_MOUNT_CIFS
+        && (!mp->mnt_type || strcmp(mp->mnt_type, "cifs") == 0)
+        && (mp->mnt_fsname[0] == '/' || mp->mnt_fsname[0] == '\\')
+        && mp->mnt_fsname[0] == mp->mnt_fsname[1]
+       ) {
+#if 0 /* reported to break things */
+               len_and_sockaddr *lsa;
+               char *ip, *dotted;
+               char *s;
+
+               // 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';
+               lsa = host2sockaddr(mp->mnt_fsname+2, 0);
+               *s = '\\';
+               if (!lsa)
+                       goto report_error;
+
+               // Insert ip=... option into string flags.
+               dotted = xmalloc_sockaddr2dotted_noport(&lsa->u.sa);
+               ip = xasprintf("ip=%s", dotted);
+               parse_mount_options(ip, &filteropts);
+
+               // Compose new unc '\\server-ip\share'
+               // (s => slash after hostname)
+               mp->mnt_fsname = xasprintf("\\\\%s%s", dotted, s);
+#endif
+               // Lock is required [why?]
+               vfsflags |= MS_MANDLOCK;
+               mp->mnt_type = (char*)"cifs";
+               rc = mount_it_now(mp, vfsflags, filteropts);
+#if 0
+               if (ENABLE_FEATURE_CLEAN_UP) {
+                       free(mp->mnt_fsname);
+                       free(ip);
+                       free(dotted);
+                       free(lsa);
+               }
+#endif
+               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.)
+       // (We use stat, not lstat, in order to allow
+       // mount symlink_to_file_or_blkdev dir)
+       if (!stat(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 = NULL; // will receive malloced loop dev name
+                       if (set_loop(&(mp->mnt_fsname), loopFile, 0) < 0) {
+                               if (errno == EPERM || errno == EACCES)
+                                       bb_error_msg(bb_msg_perm_denied_are_you_root);
+                               else
+                                       bb_perror_msg("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);
+       else {
+               // Loop through filesystem types until mount succeeds
+               // or we run out
+
+               // 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 (errno == EBUSY && ignore_busy)
+               return 0;
+       if (rc < 0)
+               bb_perror_msg("mounting %s on %s failed", mp->mnt_fsname, mp->mnt_dir);
+       return rc;
+}
+
+/* -O support
+ * Unlike -t, -O should interpret "no" prefix differently:
+ * -t noa,b,c = -t no(a,b,c) = mount all except fs'es with types a,b, and c
+ * -O noa,b,c = -O noa,b,c = mount all with without option a,
+ * or with option b or c.
+ * But for now we do not support -O a,b,c at all (only -O a).
+ *
+ * Another difference from -t support (match_fstype) is that
+ * we need to examine the _list_ of options in fsopt, not just a string.
+ */
+static int match_opt(const char *fs_opt, const char *O_opt)
+{
+       int match = 1;
+       int len;
+
+       if (!O_opt)
+               return match;
+
+       if (O_opt[0] == 'n' && O_opt[1] == 'o') {
+               match--;
+               O_opt += 2;
+       }
+
+       len = strlen(O_opt);
+       while (1) {
+               if (strncmp(fs_opt, O_opt, len) == 0
+                && (fs_opt[len] == '\0' || fs_opt[len] == ',')
+               ) {
+                       return match;
+               }
+               fs_opt = strchr(fs_opt, ',');
+               if (!fs_opt)
+                       break;
+               fs_opt++;
+       }
+
+       return !match;
+}
+
+// Parse options, if necessary parse fstab/mtab, and call singlemount for
+// each directory to be mounted.
+static const char must_be_root[] ALIGN1 = "you must be root";
+
+int mount_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int mount_main(int argc UNUSED_PARAM, char **argv)
+{
+       char *cmdopts = xzalloc(1);
+       char *fstype = NULL;
+       char *O_optmatch = NULL;
+       char *storage_path;
+       llist_t *lst_o = NULL;
+       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 = ) sanitize_env_if_suid();
+
+       // Parse long options, like --bind and --move.  Note that -o option
+       // and --option are synonymous.  Yes, this means --remount,rw works.
+       for (i = j = 1; argv[i]; i++) {
+               if (argv[i][0] == '-' && argv[i][1] == '-')
+                       append_mount_options(&cmdopts, argv[i] + 2);
+               else
+                       argv[j++] = argv[i];
+       }
+       argv[j] = NULL;
+
+       // Parse remaining options
+       // Max 2 params; -o is a list, -v is a counter
+       opt_complementary = "?2o::" USE_FEATURE_MOUNT_VERBOSE("vv");
+       opt = getopt32(argv, OPTION_STR, &lst_o, &fstype, &O_optmatch
+                       USE_FEATURE_MOUNT_VERBOSE(, &verbose));
+       while (lst_o) append_mount_options(&cmdopts, llist_pop(&lst_o)); // -o
+       if (opt & OPT_r) append_mount_options(&cmdopts, "ro"); // -r
+       if (opt & OPT_w) append_mount_options(&cmdopts, "rw"); // -w
+       argv += optind;
+
+       // If we have no arguments, show currently mounted filesystems
+       if (!argv[0]) {
+               if (!(opt & OPT_a)) {
+                       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[0], getmntent_buf,
+                                                               GETMNTENT_BUFSIZE))
+                       {
+                               // Don't show rootfs. FIXME: why??
+                               // util-linux 2.12a happily shows rootfs...
+                               //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;
+               }
+               storage_path = NULL;
+       } else {
+               // 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 (argv[1]) {
+                       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;
+                       resolve_mount_spec(&mtpair->mnt_fsname);
+                       rc = singlemount(mtpair, 0);
+                       return rc;
+               }
+               storage_path = bb_simplify_path(argv[0]); // malloced
+       }
+
+       // Past this point, we are handling either "mount -a [opts]"
+       // or "mount [opts] single_param"
+
+       i = parse_mount_options(cmdopts, NULL); // FIXME: should be "long", not "int"
+       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))
+       ) {
+               // verbose_mount(source, target, type, flags, data)
+               rc = verbose_mount("", argv[0], "", i, "");
+               if (rc)
+                       bb_simple_perror_msg_and_die(argv[0]);
+               return rc;
+       }
+
+       // Open either fstab or mtab
+       fstabname = "/etc/fstab";
+       if (i & MS_REMOUNT) {
+               // WARNING. I am not sure this matches util-linux's
+               // behavior. It's possible util-linux does not
+               // take -o opts from mtab (takes only mount source).
+               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 *mtother = (mtcur==mtpair ? mtpair+1 : mtpair);
+
+               // Get next fstab entry
+               if (!getmntent_r(fstab, mtcur, getmntent_buf
+                                       + (mtcur==mtpair ? GETMNTENT_BUFSIZE/2 : 0),
+                               GETMNTENT_BUFSIZE/2)
+               ) { // End of fstab/mtab is reached
+                       mtcur = mtother; // the thing we found last time
+                       break;
+               }
+
+               // If we're trying to mount something specific and this isn't it,
+               // skip it.  Note we must match the exact text in fstab (ala
+               // "proc") or a full path from root
+               if (argv[0]) {
+
+                       // 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 = mtother;
+
+               // If we're mounting all
+               } else {
+                       // No, mount -a won't mount anything,
+                       // even user mounts, for mere humans
+                       if (nonroot)
+                               bb_error_msg_and_die(must_be_root);
+
+                       // Does type match? (NULL matches always)
+                       if (!match_fstype(mtcur, fstype))
+                               continue;
+
+                       // Skip noauto and swap anyway.
+                       if ((parse_mount_options(mtcur->mnt_opts, NULL) & (MOUNT_NOAUTO | MOUNT_SWAP))
+                       // swap is bogus "fstype", parse_mount_options can't check fstypes
+                        || strcasecmp(mtcur->mnt_type, "swap") == 0
+                       ) {
+                               continue;
+                       }
+
+                       // Does (at least one) option match?
+                       // (NULL matches always)
+                       if (!match_opt(mtcur->mnt_opts, O_optmatch))
+                               continue;
+
+                       resolve_mount_spec(&mtcur->mnt_fsname);
+
+                       // NFS mounts want this to be xrealloc-able
+                       mtcur->mnt_opts = xstrdup(mtcur->mnt_opts);
+
+                       // Mount this thing
+                       if (singlemount(mtcur, 1)) {
+                               // Count number of failed mounts
+                               rc++;
+                       }
+                       free(mtcur->mnt_opts);
+               }
+       }
+
+       // End of fstab/mtab is reached.
+       // Were we looking for something specific?
+       if (argv[0]) {
+               long l;
+
+               // If we didn't find anything, complain
+               if (!mtcur->mnt_fsname)
+                       bb_error_msg_and_die("can't find %s in %s",
+                               argv[0], fstabname);
+
+               // What happens when we try to "mount swap_partition"?
+               // (fstab containts "swap_partition swap swap defaults 0 0")
+               // util-linux-ng 2.13.1 does this:
+               // stat("/sbin/mount.swap", 0x7fff62a3a350) = -1 ENOENT (No such file or directory)
+               // mount("swap_partition", "swap", "swap", MS_MGC_VAL, NULL) = -1 ENOENT (No such file or directory)
+               // lstat("swap", 0x7fff62a3a640)           = -1 ENOENT (No such file or directory)
+               // write(2, "mount: mount point swap does not exist\n", 39) = 39
+               // exit_group(32)                          = ?
+#if 0
+               // In case we want to simply skip swap partitions:
+               l = parse_mount_options(mtcur->mnt_opts, NULL);
+               if ((l & MOUNT_SWAP)
+               // swap is bogus "fstype", parse_mount_options can't check fstypes
+                || strcasecmp(mtcur->mnt_type, "swap") == 0
+               ) {
+                       goto ret;
+               }
+#endif
+               if (nonroot) {
+                       // fstab must have "users" or "user"
+                       l = parse_mount_options(mtcur->mnt_opts, NULL);
+                       if (!(l & 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);
+               resolve_mount_spec(&mtpair->mnt_fsname);
+               rc = singlemount(mtcur, 0);
+               if (ENABLE_FEATURE_CLEAN_UP)
+                       free(mtcur->mnt_opts);
+       }
+
+ //ret:
+       if (ENABLE_FEATURE_CLEAN_UP)
+               endmntent(fstab);
+       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 (file)
index 0000000..28af00c
--- /dev/null
@@ -0,0 +1,27 @@
+/* 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 "libbb.h"
+
+extern int pivot_root(const char * new_root,const char * put_old);
+
+int pivot_root_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int pivot_root_main(int argc, char **argv)
+{
+       if (argc != 3)
+               bb_show_usage();
+
+       if (pivot_root(argv[1], argv[2]) < 0) {
+               /* prints "pivot_root: <strerror text>" */
+               bb_perror_nomsg_and_die();
+       }
+
+       return EXIT_SUCCESS;
+}
diff --git a/util-linux/rdate.c b/util-linux/rdate.c
new file mode 100644 (file)
index 0000000..0880edf
--- /dev/null
@@ -0,0 +1,71 @@
+/* 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 "libbb.h"
+
+enum { RFC_868_BIAS = 2208988800UL };
+
+static void socket_timeout(int sig UNUSED_PARAM)
+{
+       bb_error_msg_and_die("timeout connecting to time server");
+}
+
+static time_t askremotedate(const char *host)
+{
+       uint32_t nett;
+       int fd;
+
+       /* Add a timeout for dead or inaccessible servers */
+       alarm(10);
+       signal(SIGALRM, socket_timeout);
+
+       fd = create_and_connect_stream_or_die(host, bb_lookup_port("time", "tcp", 37));
+
+       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) MAIN_EXTERNALLY_VISIBLE;
+int rdate_main(int argc UNUSED_PARAM, char **argv)
+{
+       time_t remote_time;
+       unsigned long flags;
+
+       opt_complementary = "-1";
+       flags = getopt32(argv, "sp");
+
+       remote_time = askremotedate(argv[optind]);
+
+       if ((flags & 2) == 0) {
+               time_t current_time;
+
+               time(&current_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/rdev.c b/util-linux/rdev.c
new file mode 100644 (file)
index 0000000..33abd39
--- /dev/null
@@ -0,0 +1,24 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * rdev - print device node associated with a filesystem
+ *
+ * Copyright (c) 2008 Nuovation System Designs, LLC
+ *   Grant Erickson <gerickson@nuovations.com>
+ *
+ * Licensed under GPLv2, see file LICENSE in this tarball for details.
+ *
+ */
+
+#include "libbb.h"
+
+int rdev_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int rdev_main(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
+{
+       char const * const root_device = find_block_device("/");
+
+       if (root_device != NULL) {
+               printf("%s /\n", root_device);
+               return EXIT_SUCCESS;
+       }
+       return EXIT_FAILURE;
+}
diff --git a/util-linux/readprofile.c b/util-linux/readprofile.c
new file mode 100644 (file)
index 0000000..1f5ba2e
--- /dev/null
@@ -0,0 +1,247 @@
+/* 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 Mickiewicz <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 "libbb.h"
+#include <sys/utsname.h>
+
+#define S_LEN 128
+
+/* These are the defaults */
+static const char defaultmap[] ALIGN1 = "/boot/System.map";
+static const char defaultpro[] ALIGN1 = "/proc/profile";
+
+int readprofile_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int readprofile_main(int argc UNUSED_PARAM, char **argv)
+{
+       FILE *map;
+       const char *mapFile, *proFile;
+       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 maplineno = 1;
+       int header_printed;
+       int multiplier = 0;
+       unsigned opt;
+       enum {
+               OPT_M = (1 << 0),
+               OPT_m = (1 << 1),
+               OPT_p = (1 << 2),
+               OPT_n = (1 << 3),
+               OPT_a = (1 << 4),
+               OPT_b = (1 << 5),
+               OPT_s = (1 << 6),
+               OPT_i = (1 << 7),
+               OPT_r = (1 << 8),
+               OPT_v = (1 << 9),
+       };
+#define optMult    (opt & OPT_M)
+#define optNative  (opt & OPT_n)
+#define optAll     (opt & OPT_a)
+#define optBins    (opt & OPT_b)
+#define optSub     (opt & OPT_s)
+#define optInfo    (opt & OPT_i)
+#define optReset   (opt & OPT_r)
+#define optVerbose (opt & OPT_v)
+
+#define next (current^1)
+
+       proFile = defaultpro;
+       mapFile = defaultmap;
+
+       opt_complementary = "M+"; /* -M N */
+       opt = getopt32(argv, "M:m:p:nabsirv", &multiplier, &mapFile, &proFile);
+
+       if (opt & (OPT_M|OPT_r)) { /* mult or reset, or both */
+               int fd, to_write;
+
+               /*
+                * When writing the multiplier, if the length of the write is
+                * not sizeof(int), the multiplier is not changed
+                */
+               to_write = sizeof(int);
+               if (!optMult)
+                       to_write = 1;   /* sth different from sizeof(int) */
+
+               fd = xopen(defaultpro, O_WRONLY);
+               xwrite(fd, &multiplier, to_write);
+               close(fd);
+               return EXIT_SUCCESS;
+       }
+
+       /*
+        * Use an fd for the profiling buffer, to skip stdio overhead
+        */
+       len = MAXINT(ssize_t);
+       buf = xmalloc_xopen_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_for_read(mapFile);
+
+       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/rtcwake.c b/util-linux/rtcwake.c
new file mode 100644 (file)
index 0000000..9a73ba2
--- /dev/null
@@ -0,0 +1,201 @@
+/*
+ * rtcwake -- enter a system sleep state until specified wakeup time.
+ *
+ * This version was taken from util-linux and scrubbed down for busybox.
+ *
+ * Licensed under GPLv2, see file LICENSE in this tarball for details.
+ *
+ * This uses cross-platform Linux interfaces to enter a system sleep state,
+ * and leave it no later than a specified time.  It uses any RTC framework
+ * driver that supports standard driver model wakeup flags.
+ *
+ * This is normally used like the old "apmsleep" utility, to wake from a
+ * suspend state like ACPI S1 (standby) or S3 (suspend-to-RAM).  Most
+ * platforms can implement those without analogues of BIOS, APM, or ACPI.
+ *
+ * On some systems, this can also be used like "nvram-wakeup", waking
+ * from states like ACPI S4 (suspend to disk).  Not all systems have
+ * persistent media that are appropriate for such suspend modes.
+ *
+ * The best way to set the system's RTC is so that it holds the current
+ * time in UTC.  Use the "-l" flag to tell this program that the system
+ * RTC uses a local timezone instead (maybe you dual-boot MS-Windows).
+ * That flag should not be needed on systems with adjtime support.
+ */
+
+#include "libbb.h"
+#include "rtc_.h"
+
+#define SYS_RTC_PATH   "/sys/class/rtc/%s/device/power/wakeup"
+#define SYS_POWER_PATH "/sys/power/state"
+#define DEFAULT_MODE   "standby"
+
+static time_t rtc_time;
+
+static bool may_wakeup(const char *rtcname)
+{
+       ssize_t ret;
+       char buf[128];
+
+       /* strip the '/dev/' from the rtcname here */
+       if (!strncmp(rtcname, "/dev/", 5))
+               rtcname += 5;
+
+       snprintf(buf, sizeof(buf), SYS_RTC_PATH, rtcname);
+       ret = open_read_close(buf, buf, sizeof(buf));
+       if (ret < 0)
+               return false;
+
+       /* wakeup events could be disabled or not supported */
+       return strncmp(buf, "enabled\n", 8) == 0;
+}
+
+static void setup_alarm(int fd, time_t *wakeup)
+{
+       struct tm *tm;
+       struct linux_rtc_wkalrm wake;
+
+       /* The wakeup time is in POSIX time (more or less UTC).
+        * Ideally RTCs use that same time; but PCs can't do that
+        * if they need to boot MS-Windows.  Messy...
+        *
+        * When running in utc mode this process's timezone is UTC,
+        * so we'll pass a UTC date to the RTC.
+        *
+        * Else mode is local so the time given to the RTC
+        * will instead use the local time zone.
+        */
+       tm = localtime(wakeup);
+
+       wake.time.tm_sec = tm->tm_sec;
+       wake.time.tm_min = tm->tm_min;
+       wake.time.tm_hour = tm->tm_hour;
+       wake.time.tm_mday = tm->tm_mday;
+       wake.time.tm_mon = tm->tm_mon;
+       wake.time.tm_year = tm->tm_year;
+       /* wday, yday, and isdst fields are unused by Linux */
+       wake.time.tm_wday = -1;
+       wake.time.tm_yday = -1;
+       wake.time.tm_isdst = -1;
+
+       /* many rtc alarms only support up to 24 hours from 'now',
+        * so use the "more than 24 hours" request only if we must
+        */
+       if ((rtc_time + (24 * 60 * 60)) > *wakeup) {
+               xioctl(fd, RTC_ALM_SET, &wake.time);
+               xioctl(fd, RTC_AIE_ON, 0);
+       } else {
+               /* avoid an extra AIE_ON call */
+               wake.enabled = 1;
+               xioctl(fd, RTC_WKALM_SET, &wake);
+       }
+}
+
+#define RTCWAKE_OPT_AUTO         0x01
+#define RTCWAKE_OPT_LOCAL        0x02
+#define RTCWAKE_OPT_UTC          0x04
+#define RTCWAKE_OPT_DEVICE       0x08
+#define RTCWAKE_OPT_SUSPEND_MODE 0x10
+#define RTCWAKE_OPT_SECONDS      0x20
+#define RTCWAKE_OPT_TIME         0x40
+
+int rtcwake_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int rtcwake_main(int argc UNUSED_PARAM, char **argv)
+{
+       unsigned opt;
+       const char *rtcname = NULL;
+       const char *suspend;
+       const char *opt_seconds;
+       const char *opt_time;
+
+       time_t sys_time;
+       time_t alarm_time = 0;
+       unsigned seconds = 0;
+       int utc = -1;
+       int fd;
+
+#if ENABLE_GETOPT_LONG
+       static const char rtcwake_longopts[] ALIGN1 =
+               "auto\0"    No_argument "a"
+               "local\0"   No_argument "l"
+               "utc\0"     No_argument "u"
+               "device\0"  Required_argument "d"
+               "mode\0"    Required_argument "m"
+               "seconds\0" Required_argument "s"
+               "time\0"    Required_argument "t"
+               ;
+       applet_long_options = rtcwake_longopts;
+#endif
+       opt = getopt32(argv, "alud:m:s:t:", &rtcname, &suspend, &opt_seconds, &opt_time);
+
+       /* this is the default
+       if (opt & RTCWAKE_OPT_AUTO)
+               utc = -1;
+       */
+       if (opt & (RTCWAKE_OPT_UTC | RTCWAKE_OPT_LOCAL))
+               utc = opt & RTCWAKE_OPT_UTC;
+       if (!(opt & RTCWAKE_OPT_SUSPEND_MODE))
+               suspend = DEFAULT_MODE;
+       if (opt & RTCWAKE_OPT_SECONDS)
+               /* alarm time, seconds-to-sleep (relative) */
+               seconds = xatoi(opt_seconds);
+       if (opt & RTCWAKE_OPT_TIME)
+               /* alarm time, time_t (absolute, seconds since 1/1 1970 UTC) */
+               alarm_time = xatoi(opt_time);
+
+       if (!alarm_time && !seconds)
+               bb_error_msg_and_die("must provide wake time");
+
+       if (utc == -1)
+               utc = rtc_adjtime_is_utc();
+
+       /* the rtcname is relative to /dev */
+       xchdir("/dev");
+
+       /* this RTC must exist and (if we'll sleep) be wakeup-enabled */
+       fd = rtc_xopen(&rtcname, O_RDONLY);
+
+       if (strcmp(suspend, "on") && !may_wakeup(rtcname))
+               bb_error_msg_and_die("%s not enabled for wakeup events", rtcname);
+
+       /* relative or absolute alarm time, normalized to time_t */
+       sys_time = time(NULL);
+       if (sys_time == (time_t)-1)
+               bb_perror_msg_and_die("read system time");
+       rtc_time = rtc_read_time(fd, utc);
+
+       if (alarm_time) {
+               if (alarm_time < sys_time)
+                       bb_error_msg_and_die("time doesn't go backward to %s", ctime(&alarm_time));
+               alarm_time += sys_time - rtc_time;
+       } else
+               alarm_time = rtc_time + seconds + 1;
+       setup_alarm(fd, &alarm_time);
+
+       sync();
+       printf("wakeup from \"%s\" at %s", suspend, ctime(&alarm_time));
+       fflush(stdout);
+       usleep(10 * 1000);
+
+       if (strcmp(suspend, "on"))
+               xopen_xwrite_close(SYS_POWER_PATH, suspend);
+       else {
+               /* "fake" suspend ... we'll do the delay ourselves */
+               unsigned long data;
+
+               do {
+                       ssize_t ret = safe_read(fd, &data, sizeof(data));
+                       if (ret < 0) {
+                               bb_perror_msg("rtc read");
+                               break;
+                       }
+               } while (!(data & RTC_AF));
+       }
+
+       xioctl(fd, RTC_AIE_OFF, 0);
+
+       if (ENABLE_FEATURE_CLEAN_UP)
+               close(fd);
+
+       return EXIT_SUCCESS;
+}
diff --git a/util-linux/script.c b/util-linux/script.c
new file mode 100644 (file)
index 0000000..a9f24b1
--- /dev/null
@@ -0,0 +1,186 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * script implementation for busybox
+ *
+ * pascal.bellard@ads-lu.com
+ *
+ * Based on code from util-linux v 2.12r
+ * Copyright (c) 1980
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * Licensed under GPLv2 or later, see file License in this tarball for details.
+ */
+
+#include "libbb.h"
+
+static smallint fd_count = 2;
+
+static void handle_sigchld(int sig UNUSED_PARAM)
+{
+       fd_count = 0;
+}
+
+int script_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int script_main(int argc UNUSED_PARAM, char **argv)
+{
+       int opt;
+       int mode;
+       int child_pid;
+       int attr_ok; /* NB: 0: ok */
+       int winsz_ok;
+       int pty;
+       char pty_line[GETPTY_BUFSIZE];
+       struct termios tt, rtt;
+       struct winsize win;
+       const char *fname = "typescript";
+       const char *shell;
+       char shell_opt[] = "-i";
+       char *shell_arg = NULL;
+
+#if ENABLE_GETOPT_LONG
+       static const char getopt_longopts[] ALIGN1 =
+               "append\0"  No_argument       "a"
+               "command\0" Required_argument "c"
+               "flush\0"   No_argument       "f"
+               "quiet\0"   No_argument       "q"
+               ;
+
+       applet_long_options = getopt_longopts;
+#endif
+       opt_complementary = "?1"; /* max one arg */
+       opt = getopt32(argv, "ac:fq", &shell_arg);
+       //argc -= optind;
+       argv += optind;
+       if (argv[0]) {
+               fname = argv[0];
+       }
+       mode = O_CREAT|O_TRUNC|O_WRONLY;
+       if (opt & 1) {
+               mode = O_CREAT|O_APPEND|O_WRONLY;
+       }
+       if (opt & 2) {
+               shell_opt[1] = 'c';
+       }
+       if (!(opt & 8)) { /* not -q */
+               printf("Script started, file is %s\n", fname);
+       }
+       shell = getenv("SHELL");
+       if (shell == NULL) {
+               shell = DEFAULT_SHELL;
+       }
+
+       pty = xgetpty(pty_line);
+
+       /* get current stdin's tty params */
+       attr_ok = tcgetattr(0, &tt);
+       winsz_ok = ioctl(0, TIOCGWINSZ, (char *)&win);
+
+       rtt = tt;
+       cfmakeraw(&rtt);
+       rtt.c_lflag &= ~ECHO;
+       tcsetattr(0, TCSAFLUSH, &rtt);
+
+       /* "script" from util-linux exits when child exits,
+        * we wouldn't wait for EOF from slave pty
+        * (output may be produced by grandchildren of child) */
+       signal(SIGCHLD, handle_sigchld);
+
+       /* TODO: SIGWINCH? pass window size changes down to slave? */
+
+       child_pid = vfork();
+       if (child_pid < 0) {
+               bb_perror_msg_and_die("vfork");
+       }
+
+       if (child_pid) {
+               /* parent */
+#define buf bb_common_bufsiz1
+               struct pollfd pfd[2];
+               int outfd, count, loop;
+
+               outfd = xopen(fname, mode);
+               pfd[0].fd = pty;
+               pfd[0].events = POLLIN;
+               pfd[1].fd = 0;
+               pfd[1].events = POLLIN;
+               ndelay_on(pty); /* this descriptor is not shared, can do this */
+               /* ndelay_on(0); - NO, stdin can be shared! Pity :( */
+
+               /* copy stdin to pty master input,
+                * copy pty master output to stdout and file */
+               /* TODO: don't use full_write's, use proper write buffering */
+               while (fd_count) {
+                       /* not safe_poll! we want SIGCHLD to EINTR poll */
+                       if (poll(pfd, fd_count, -1) < 0 && errno != EINTR) {
+                               /* If child exits too quickly, we may get EIO:
+                                * for example, try "script -c true" */
+                               break;
+                       }
+                       if (pfd[0].revents) {
+                               errno = 0;
+                               count = safe_read(pty, buf, sizeof(buf));
+                               if (count <= 0 && errno != EAGAIN) {
+                                       /* err/eof from pty: exit */
+                                       goto restore;
+                               }
+                               if (count > 0) {
+                                       full_write(STDOUT_FILENO, buf, count);
+                                       full_write(outfd, buf, count);
+                                       if (opt & 4) { /* -f */
+                                               fsync(outfd);
+                                       }
+                               }
+                       }
+                       if (pfd[1].revents) {
+                               count = safe_read(STDIN_FILENO, buf, sizeof(buf));
+                               if (count <= 0) {
+                                       /* err/eof from stdin: don't read stdin anymore */
+                                       pfd[1].revents = 0;
+                                       fd_count--;
+                               } else {
+                                       full_write(pty, buf, count);
+                               }
+                       }
+               }
+               /* If loop was exited because SIGCHLD handler set fd_count to 0,
+                * there still can be some buffered output. But not loop forever:
+                * we won't pump orphaned grandchildren's output indefinitely.
+                * Testcase: running this in script:
+                *      exec dd if=/dev/zero bs=1M count=1
+                * must have "1+0 records in, 1+0 records out" captured too.
+                * (util-linux's script doesn't do this. buggy :) */
+               loop = 999;
+               /* pty is in O_NONBLOCK mode, we exit as soon as buffer is empty */
+               while (--loop && (count = safe_read(pty, buf, sizeof(buf))) > 0) {
+                       full_write(STDOUT_FILENO, buf, count);
+                       full_write(outfd, buf, count);
+               }
+ restore:
+               if (attr_ok == 0)
+                       tcsetattr(0, TCSAFLUSH, &tt);
+               if (!(opt & 8)) /* not -q */
+                       printf("Script done, file is %s\n", fname);
+               return EXIT_SUCCESS;
+       }
+
+       /* child: make pty slave to be input, output, error; run shell */
+       close(pty); /* close pty master */
+       /* open pty slave to fd 0,1,2 */
+       close(0);
+       xopen(pty_line, O_RDWR); /* uses fd 0 */
+       xdup2(0, 1);
+       xdup2(0, 2);
+       /* copy our original stdin tty's parameters to pty */
+       if (attr_ok == 0)
+               tcsetattr(0, TCSAFLUSH, &tt);
+       if (winsz_ok == 0)
+               ioctl(0, TIOCSWINSZ, (char *)&win);
+       /* set pty as a controlling tty */
+       setsid();
+       ioctl(0, TIOCSCTTY, 0 /* 0: don't forcibly steal */);
+
+       /* Non-ignored signals revert to SIG_DFL on exec anyway */
+       /*signal(SIGCHLD, SIG_DFL);*/
+       execl(shell, shell, shell_opt, shell_arg, (char *) NULL);
+       bb_simple_perror_msg_and_die(shell);
+}
diff --git a/util-linux/setarch.c b/util-linux/setarch.c
new file mode 100644 (file)
index 0000000..8213382
--- /dev/null
@@ -0,0 +1,48 @@
+/* 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 <sys/personality.h>
+
+#include "libbb.h"
+
+int setarch_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int setarch_main(int argc UNUSED_PARAM, char **argv)
+{
+       int pers;
+
+       /* 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"
+        */
+       if (ENABLE_SETARCH && applet_name[0] == 's'
+        && argv[1] && strncpy(argv[1], "linux", 5)
+       ) {
+               applet_name = argv[1];
+               argv++;
+       }
+       if (applet_name[5] == '6') /* linux64 */
+               pers = PER_LINUX;
+       else if (applet_name[5] == '3') /* linux32 */
+               pers = PER_LINUX32;
+       else
+               bb_show_usage();
+
+       argv++;
+       if (argv[0] == NULL)
+               bb_show_usage();
+
+       /* Try to set personality */
+       if (personality(pers) >= 0) {
+               /* Try to execute the program */
+               BB_EXECVP(argv[0], argv);
+       }
+
+       bb_simple_perror_msg_and_die(argv[0]);
+}
diff --git a/util-linux/swaponoff.c b/util-linux/swaponoff.c
new file mode 100644 (file)
index 0000000..863f773
--- /dev/null
@@ -0,0 +1,102 @@
+/* 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 "libbb.h"
+#include <mntent.h>
+#include <sys/swap.h>
+
+#if ENABLE_FEATURE_SWAPON_PRI
+struct globals {
+       int flags;
+};
+#define G (*(struct globals*)&bb_common_bufsiz1)
+#define g_flags (G.flags)
+#else
+#define g_flags 0
+#endif
+
+static int swap_enable_disable(char *device)
+{
+       int status;
+       struct stat st;
+
+       xstat(device, &st);
+
+#if ENABLE_DESKTOP
+       /* test for holes */
+       if (S_ISREG(st.st_mode))
+               if (st.st_blocks * (off_t)512 < st.st_size)
+                       bb_error_msg("warning: swap file has holes");
+#endif
+
+       if (applet_name[5] == 'n')
+               status = swapon(device, g_flags);
+       else
+               status = swapoff(device);
+
+       if (status != 0) {
+               bb_simple_perror_msg(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;
+}
+
+int swap_on_off_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int swap_on_off_main(int argc UNUSED_PARAM, char **argv)
+{
+       int ret;
+
+#if !ENABLE_FEATURE_SWAPON_PRI
+       ret = getopt32(argv, "a");
+#else
+       opt_complementary = "p+";
+       ret = getopt32(argv, (applet_name[5] == 'n') ? "ap:" : "a", &g_flags);
+
+       if (ret & 2) { // -p
+               g_flags = SWAP_FLAG_PREFER |
+                       ((g_flags & SWAP_FLAG_PRIO_MASK) << SWAP_FLAG_PRIO_SHIFT);
+               ret &= 1;
+       }
+#endif
+
+       if (ret /* & 1: not needed */) // -a
+               return do_em_all();
+
+       argv += optind;
+       if (!*argv)
+               bb_show_usage();
+
+       /* ret = 0; redundant */
+       do {
+               ret += swap_enable_disable(*argv);
+       } while (*++argv);
+
+       return ret;
+}
diff --git a/util-linux/switch_root.c b/util-linux/switch_root.c
new file mode 100644 (file)
index 0000000..21cc992
--- /dev/null
@@ -0,0 +1,115 @@
+/* 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 "libbb.h"
+#include <sys/vfs.h>
+
+// Make up for header deficiencies.
+#ifndef RAMFS_MAGIC
+#define RAMFS_MAGIC ((unsigned)0x858458f6)
+#endif
+
+#ifndef TMPFS_MAGIC
+#define TMPFS_MAGIC ((unsigned)0x01021994)
+#endif
+
+#ifndef MS_MOVE
+#define MS_MOVE     8192
+#endif
+
+// Recursively delete contents of rootfs.
+static void delete_contents(const char *directory, dev_t rootdev)
+{
+       DIR *dir;
+       struct dirent *d;
+       struct stat st;
+
+       // Don't descend into other filesystems
+       if (lstat(directory, &st) || st.st_dev != rootdev)
+               return;
+
+       // Recursively delete the contents of directories.
+       if (S_ISDIR(st.st_mode)) {
+               dir = opendir(directory);
+               if (dir) {
+                       while ((d = readdir(dir))) {
+                               char *newdir = d->d_name;
+
+                               // Skip . and ..
+                               if (DOT_OR_DOTDOT(newdir))
+                                       continue;
+
+                               // Recurse to delete contents
+                               newdir = concat_path_file(directory, newdir);
+                               delete_contents(newdir, rootdev);
+                               free(newdir);
+                       }
+                       closedir(dir);
+
+                       // Directory should now be empty.  Zap it.
+                       rmdir(directory);
+               }
+
+       // It wasn't a directory.  Zap it.
+       } else unlink(directory);
+}
+
+int switch_root_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int switch_root_main(int argc UNUSED_PARAM, char **argv)
+{
+       char *newroot, *console = NULL;
+       struct stat st1, st2;
+       struct statfs stfs;
+       dev_t rootdev;
+
+       // Parse args (-c console)
+       opt_complementary = "-2"; // minimum 2 params
+       getopt32(argv, "+c:", &console); // '+': stop parsing at first non-option
+       argv += optind;
+
+       // Change to new root directory and verify it's a different fs.
+       newroot = *argv++;
+
+       xchdir(newroot);
+       if (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 filesystems.)
+       if (lstat("/init", &st1) || !S_ISREG(st1.st_mode) || statfs("/", &stfs)
+        || (((unsigned)stfs.f_type != RAMFS_MAGIC) && ((unsigned)stfs.f_type != TMPFS_MAGIC))
+        || (getpid() != 1)
+       ) {
+               bb_error_msg_and_die("not rootfs");
+       }
+
+       // Zap everything out of rootdev
+       delete_contents("/", rootdev);
+
+       // Overmount / with newdir and chroot into it.  The chdir is needed to
+       // recalculate "." and ".." links.
+       if (mount(".", "/", NULL, MS_MOVE, NULL))
+               bb_error_msg_and_die("error moving root");
+       xchroot(".");
+       xchdir("/");
+
+       // If a new console specified, redirect stdin/stdout/stderr to that.
+       if (console) {
+               close(0);
+               xopen(console, O_RDWR);
+               xdup2(0, 1);
+               xdup2(0, 2);
+       }
+
+       // Exec real init.  (This is why we must be pid 1.)
+       execv(argv[0], argv);
+       bb_perror_msg_and_die("bad init %s", argv[0]);
+}
diff --git a/util-linux/umount.c b/util-linux/umount.c
new file mode 100644 (file)
index 0000000..5b22bfa
--- /dev/null
@@ -0,0 +1,173 @@
+/* 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 <mntent.h>
+#include "libbb.h"
+
+#if defined(__dietlibc__)
+/* 16.12.2006, Sampo Kellomaki (sampo@iki.fi)
+ * dietlibc-0.30 does not have implementation of getmntent_r() */
+static struct mntent *getmntent_r(FILE* stream, struct mntent* result,
+               char* buffer UNUSED_PARAM, int bufsize UNUSED_PARAM)
+{
+       struct mntent* ment = getmntent(stream);
+       return memcpy(result, ment, sizeof(*ment));
+}
+#endif
+
+/* ignored: -v -d -t -i */
+#define OPTION_STRING           "fldnra" "vdt:i"
+#define OPT_FORCE               (1 << 0)
+#define OPT_LAZY                (1 << 1)
+#define OPT_FREELOOP            (1 << 2)
+#define OPT_NO_MTAB             (1 << 3)
+#define OPT_REMOUNT             (1 << 4)
+#define OPT_ALL                 (ENABLE_FEATURE_UMOUNT_ALL ? (1 << 5) : 0)
+
+// These constants from linux/fs.h must match OPT_FORCE and OPT_LAZY,
+// otherwise "doForce" trick below won't work!
+//#define MNT_FORCE  0x00000001 /* Attempt to forcibly umount */
+//#define MNT_DETACH 0x00000002 /* Just detach from the tree */
+
+int umount_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int umount_main(int argc UNUSED_PARAM, char **argv)
+{
+       int doForce;
+       char *const path = xmalloc(PATH_MAX + 2); /* to save stack */
+       struct mntent me;
+       FILE *fp;
+       char *fstype = NULL;
+       int status = EXIT_SUCCESS;
+       unsigned opt;
+       struct mtab_list {
+               char *dir;
+               char *device;
+               struct mtab_list *next;
+       } *mtl, *m;
+
+       opt = getopt32(argv, OPTION_STRING, &fstype);
+       //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 = NULL;
+
+       // 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("can't open '%s'", bb_path_mtab_file);
+       } else {
+               while (getmntent_r(fp, &me, path, PATH_MAX)) {
+                       /* Match fstype if passed */
+                       if (!match_fstype(&me, fstype))
+                               continue;
+                       m = xzalloc(sizeof(*m));
+                       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) && !fstype) {
+               if (!argv[0])
+                       bb_show_usage();
+               m = NULL;
+       }
+
+       // 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;
+               // Use command line argument (and look it up in mtab list)
+               else {
+                       if (!zapit)
+                               break;
+                       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 still can't umount, maybe remount read-only?
+               if (curstat) {
+                       if ((opt & OPT_REMOUNT) && errno == EBUSY && m) {
+                               // Note! Even if we succeed here, later we should not
+                               // free loop device or erase mtab entry!
+                               const char *msg = "%s busy - remounted read-only";
+                               curstat = mount(m->device, zapit, NULL, MS_REMOUNT|MS_RDONLY, NULL);
+                               if (curstat) {
+                                       msg = "can't remount %s read-only";
+                                       status = EXIT_FAILURE;
+                               }
+                               bb_error_msg(msg, m->device);
+                       } else {
+                               status = EXIT_FAILURE;
+                               bb_perror_msg("can't %sumount %s", (doForce ? "forcibly " : ""), 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_FREELOOP) && 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.
+               if (m) while ((m = m->next) != NULL)
+                       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;
+               }
+               free(path);
+       }
+
+       return status;
+}
diff --git a/util-linux/volume_id/Kbuild b/util-linux/volume_id/Kbuild
new file mode 100644 (file)
index 0000000..d78e4ad
--- /dev/null
@@ -0,0 +1,42 @@
+# 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_BLKID)                             += get_devname.o
+lib-$(CONFIG_FINDFS)                            += get_devname.o
+lib-$(CONFIG_FEATURE_MOUNT_LABEL)               += get_devname.o
+
+lib-$(CONFIG_VOLUMEID)                          += volume_id.o util.o
+lib-$(CONFIG_FEATURE_VOLUMEID_EXT)              += ext.o
+lib-$(CONFIG_FEATURE_VOLUMEID_FAT)              += fat.o
+lib-$(CONFIG_FEATURE_VOLUMEID_HFS)              += hfs.o
+### lib-$(CONFIG_FEATURE_VOLUMEID_HIGHPOINTRAID)    += highpoint.o
+### lib-$(CONFIG_FEATURE_VOLUMEID_ISWRAID)          += isw_raid.o
+### lib-$(CONFIG_FEATURE_VOLUMEID_LSIRAID)          += lsi_raid.o
+### lib-$(CONFIG_FEATURE_VOLUMEID_VIARAID)          += via_raid.o
+### lib-$(CONFIG_FEATURE_VOLUMEID_SILICONRAID)      += silicon_raid.o
+### lib-$(CONFIG_FEATURE_VOLUMEID_NVIDIARAID)       += nvidia_raid.o
+### lib-$(CONFIG_FEATURE_VOLUMEID_PROMISERAID)      += promise_raid.o
+lib-$(CONFIG_FEATURE_VOLUMEID_ISO9660)          += iso9660.o
+lib-$(CONFIG_FEATURE_VOLUMEID_JFS)              += jfs.o
+lib-$(CONFIG_FEATURE_VOLUMEID_LINUXRAID)        += linux_raid.o
+lib-$(CONFIG_FEATURE_VOLUMEID_LINUXSWAP)        += linux_swap.o
+### lib-$(CONFIG_FEATURE_VOLUMEID_LVM)              += lvm.o
+### lib-$(CONFIG_FEATURE_VOLUMEID_MAC)              += mac.o
+### lib-$(CONFIG_FEATURE_VOLUMEID_MSDOS)            += msdos.o
+lib-$(CONFIG_FEATURE_VOLUMEID_NTFS)             += ntfs.o
+lib-$(CONFIG_FEATURE_VOLUMEID_REISERFS)         += reiserfs.o
+lib-$(CONFIG_FEATURE_VOLUMEID_UDF)              += udf.o
+### lib-$(CONFIG_FEATURE_VOLUMEID_UFS)              += ufs.o
+lib-$(CONFIG_FEATURE_VOLUMEID_XFS)              += xfs.o
+lib-$(CONFIG_FEATURE_VOLUMEID_CRAMFS)           += cramfs.o
+### lib-$(CONFIG_FEATURE_VOLUMEID_HPFS)             += hpfs.o
+lib-$(CONFIG_FEATURE_VOLUMEID_ROMFS)            += romfs.o
+lib-$(CONFIG_FEATURE_VOLUMEID_SYSV)             += sysv.o
+### lib-$(CONFIG_FEATURE_VOLUMEID_MINIX)            += minix.o
+lib-$(CONFIG_FEATURE_VOLUMEID_LUKS)             += luks.o
+lib-$(CONFIG_FEATURE_VOLUMEID_OCFS2)            += ocfs2.o
diff --git a/util-linux/volume_id/cramfs.c b/util-linux/volume_id/cramfs.c
new file mode 100644 (file)
index 0000000..dd939e4
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ * volume_id - reads filesystem label and uuid
+ *
+ * Copyright (C) 2004 Kay Sievers <kay.sievers@vrfy.org>
+ *
+ *     This 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.
+ *
+ *     This 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 this library; if not, write to the Free Software
+ *     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "volume_id_internal.h"
+
+struct cramfs_super {
+       uint32_t        magic;
+       uint32_t        size;
+       uint32_t        flags;
+       uint32_t        future;
+       uint8_t         signature[16];
+       struct cramfs_info {
+               uint32_t        crc;
+               uint32_t        edition;
+               uint32_t        blocks;
+               uint32_t        files;
+       } __attribute__((__packed__)) info;
+       uint8_t         name[16];
+} __attribute__((__packed__));
+
+int volume_id_probe_cramfs(struct volume_id *id /*,uint64_t off*/)
+{
+#define off ((uint64_t)0)
+       struct cramfs_super *cs;
+
+       dbg("probing at offset 0x%llx", (unsigned long long) off);
+
+       cs = volume_id_get_buffer(id, off, 0x200);
+       if (cs == NULL)
+               return -1;
+
+       if (cs->magic == cpu_to_be32(0x453dcd28)) {
+//             volume_id_set_label_raw(id, cs->name, 16);
+               volume_id_set_label_string(id, cs->name, 16);
+
+//             volume_id_set_usage(id, VOLUME_ID_FILESYSTEM);
+//             id->type = "cramfs";
+               return 0;
+       }
+
+       return -1;
+}
diff --git a/util-linux/volume_id/ext.c b/util-linux/volume_id/ext.c
new file mode 100644 (file)
index 0000000..b052e04
--- /dev/null
@@ -0,0 +1,74 @@
+/*
+ * volume_id - reads filesystem label and uuid
+ *
+ * Copyright (C) 2004 Kay Sievers <kay.sievers@vrfy.org>
+ *
+ *     This 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.
+ *
+ *     This 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 this library; if not, write to the Free Software
+ *     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "volume_id_internal.h"
+
+struct ext2_super_block {
+       uint32_t        inodes_count;
+       uint32_t        blocks_count;
+       uint32_t        r_blocks_count;
+       uint32_t        free_blocks_count;
+       uint32_t        free_inodes_count;
+       uint32_t        first_data_block;
+       uint32_t        log_block_size;
+       uint32_t        dummy3[7];
+       uint8_t magic[2];
+       uint16_t        state;
+       uint32_t        dummy5[8];
+       uint32_t        feature_compat;
+       uint32_t        feature_incompat;
+       uint32_t        feature_ro_compat;
+       uint8_t uuid[16];
+       uint8_t volume_name[16];
+} __attribute__((__packed__));
+
+#define EXT3_FEATURE_COMPAT_HAS_JOURNAL                0x00000004
+#define EXT3_FEATURE_INCOMPAT_JOURNAL_DEV      0x00000008
+#define EXT_SUPERBLOCK_OFFSET                  0x400
+
+int volume_id_probe_ext(struct volume_id *id /*,uint64_t off*/)
+{
+#define off ((uint64_t)0)
+       struct ext2_super_block *es;
+
+       dbg("ext: probing at offset 0x%llx", (unsigned long long) off);
+
+       es = volume_id_get_buffer(id, off + EXT_SUPERBLOCK_OFFSET, 0x200);
+       if (es == NULL)
+               return -1;
+
+       if (es->magic[0] != 0123 || es->magic[1] != 0357) {
+               dbg("ext: no magic found");
+               return -1;
+       }
+
+//     volume_id_set_usage(id, VOLUME_ID_FILESYSTEM);
+//     volume_id_set_label_raw(id, es->volume_name, 16);
+       volume_id_set_label_string(id, es->volume_name, 16);
+       volume_id_set_uuid(id, es->uuid, UUID_DCE);
+       dbg("ext: label '%s' uuid '%s'", id->label, id->uuid);
+
+//     if ((le32_to_cpu(es->feature_compat) & EXT3_FEATURE_COMPAT_HAS_JOURNAL) != 0)
+//             id->type = "ext3";
+//     else
+//             id->type = "ext2";
+
+       return 0;
+}
diff --git a/util-linux/volume_id/fat.c b/util-linux/volume_id/fat.c
new file mode 100644 (file)
index 0000000..352040f
--- /dev/null
@@ -0,0 +1,332 @@
+/*
+ * volume_id - reads filesystem label and uuid
+ *
+ * Copyright (C) 2004 Kay Sievers <kay.sievers@vrfy.org>
+ *
+ *     This 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.
+ *
+ *     This 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 this library; if not, write to the Free Software
+ *     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "volume_id_internal.h"
+
+/* linux/msdos_fs.h says: */
+#define FAT12_MAX                      0xff4
+#define FAT16_MAX                      0xfff4
+#define FAT32_MAX                      0x0ffffff6
+
+#define FAT_ATTR_VOLUME_ID             0x08
+#define FAT_ATTR_DIR                   0x10
+#define FAT_ATTR_LONG_NAME             0x0f
+#define FAT_ATTR_MASK                  0x3f
+#define FAT_ENTRY_FREE                 0xe5
+
+struct vfat_super_block {
+       uint8_t         boot_jump[3];
+       uint8_t         sysid[8];
+       uint16_t        sector_size_bytes;
+       uint8_t         sectors_per_cluster;
+       uint16_t        reserved_sct;
+       uint8_t         fats;
+       uint16_t        dir_entries;
+       uint16_t        sectors;
+       uint8_t         media;
+       uint16_t        fat_length;
+       uint16_t        secs_track;
+       uint16_t        heads;
+       uint32_t        hidden;
+       uint32_t        total_sect;
+       union {
+               struct fat_super_block {
+                       uint8_t         unknown[3];
+                       uint8_t         serno[4];
+                       uint8_t         label[11];
+                       uint8_t         magic[8];
+                       uint8_t         dummy2[192];
+                       uint8_t         pmagic[2];
+               } __attribute__((__packed__)) fat;
+               struct fat32_super_block {
+                       uint32_t        fat32_length;
+                       uint16_t        flags;
+                       uint8_t         version[2];
+                       uint32_t        root_cluster;
+                       uint16_t        insfo_sector;
+                       uint16_t        backup_boot;
+                       uint16_t        reserved2[6];
+                       uint8_t         unknown[3];
+                       uint8_t         serno[4];
+                       uint8_t         label[11];
+                       uint8_t         magic[8];
+                       uint8_t         dummy2[164];
+                       uint8_t         pmagic[2];
+               } __attribute__((__packed__)) fat32;
+       } __attribute__((__packed__)) type;
+} __attribute__((__packed__));
+
+struct vfat_dir_entry {
+       uint8_t         name[11];
+       uint8_t         attr;
+       uint16_t        time_creat;
+       uint16_t        date_creat;
+       uint16_t        time_acc;
+       uint16_t        date_acc;
+       uint16_t        cluster_high;
+       uint16_t        time_write;
+       uint16_t        date_write;
+       uint16_t        cluster_low;
+       uint32_t        size;
+} __attribute__((__packed__));
+
+static uint8_t *get_attr_volume_id(struct vfat_dir_entry *dir, int count)
+{
+       for (;--count >= 0; dir++) {
+               /* end marker */
+               if (dir->name[0] == 0x00) {
+                       dbg("end of dir");
+                       break;
+               }
+
+               /* empty entry */
+               if (dir->name[0] == FAT_ENTRY_FREE)
+                       continue;
+
+               /* long name */
+               if ((dir->attr & FAT_ATTR_MASK) == FAT_ATTR_LONG_NAME)
+                       continue;
+
+               if ((dir->attr & (FAT_ATTR_VOLUME_ID | FAT_ATTR_DIR)) == FAT_ATTR_VOLUME_ID) {
+                       /* labels do not have file data */
+                       if (dir->cluster_high != 0 || dir->cluster_low != 0)
+                               continue;
+
+                       dbg("found ATTR_VOLUME_ID id in root dir");
+                       return dir->name;
+               }
+
+               dbg("skip dir entry");
+       }
+
+       return NULL;
+}
+
+int volume_id_probe_vfat(struct volume_id *id /*,uint64_t fat_partition_off*/)
+{
+#define fat_partition_off ((uint64_t)0)
+       struct vfat_super_block *vs;
+       struct vfat_dir_entry *dir;
+       uint16_t sector_size_bytes;
+       uint16_t dir_entries;
+       uint32_t sect_count;
+       uint16_t reserved_sct;
+       uint32_t fat_size_sct;
+       uint32_t root_cluster;
+       uint32_t dir_size_sct;
+       uint32_t cluster_count;
+       uint64_t root_start_off;
+       uint32_t start_data_sct;
+       uint8_t *buf;
+       uint32_t buf_size;
+       uint8_t *label = NULL;
+       uint32_t next_cluster;
+       int maxloop;
+
+       dbg("probing at offset 0x%llx", (unsigned long long) fat_partition_off);
+
+       vs = volume_id_get_buffer(id, fat_partition_off, 0x200);
+       if (vs == NULL)
+               return -1;
+
+       /* believe only that's fat, don't trust the version
+        * the cluster_count will tell us
+        */
+       if (memcmp(vs->sysid, "NTFS", 4) == 0)
+               return -1;
+
+       if (memcmp(vs->type.fat32.magic, "MSWIN", 5) == 0)
+               goto valid;
+
+       if (memcmp(vs->type.fat32.magic, "FAT32   ", 8) == 0)
+               goto valid;
+
+       if (memcmp(vs->type.fat.magic, "FAT16   ", 8) == 0)
+               goto valid;
+
+       if (memcmp(vs->type.fat.magic, "MSDOS", 5) == 0)
+               goto valid;
+
+       if (memcmp(vs->type.fat.magic, "FAT12   ", 8) == 0)
+               goto valid;
+
+       /*
+        * There are old floppies out there without a magic, so we check
+        * for well known values and guess if it's a fat volume
+        */
+
+       /* boot jump address check */
+       if ((vs->boot_jump[0] != 0xeb || vs->boot_jump[2] != 0x90) &&
+            vs->boot_jump[0] != 0xe9)
+               return -1;
+
+       /* heads check */
+       if (vs->heads == 0)
+               return -1;
+
+       /* cluster size check */
+       if (vs->sectors_per_cluster == 0 ||
+           (vs->sectors_per_cluster & (vs->sectors_per_cluster-1)))
+               return -1;
+
+       /* media check */
+       if (vs->media < 0xf8 && vs->media != 0xf0)
+               return -1;
+
+       /* fat count*/
+       if (vs->fats != 2)
+               return -1;
+
+ valid:
+       /* sector size check */
+       sector_size_bytes = le16_to_cpu(vs->sector_size_bytes);
+       if (sector_size_bytes != 0x200 && sector_size_bytes != 0x400 &&
+           sector_size_bytes != 0x800 && sector_size_bytes != 0x1000)
+               return -1;
+
+       dbg("sector_size_bytes 0x%x", sector_size_bytes);
+       dbg("sectors_per_cluster 0x%x", vs->sectors_per_cluster);
+
+       reserved_sct = le16_to_cpu(vs->reserved_sct);
+       dbg("reserved_sct 0x%x", reserved_sct);
+
+       sect_count = le16_to_cpu(vs->sectors);
+       if (sect_count == 0)
+               sect_count = le32_to_cpu(vs->total_sect);
+       dbg("sect_count 0x%x", sect_count);
+
+       fat_size_sct = le16_to_cpu(vs->fat_length);
+       if (fat_size_sct == 0)
+               fat_size_sct = le32_to_cpu(vs->type.fat32.fat32_length);
+       fat_size_sct *= vs->fats;
+       dbg("fat_size_sct 0x%x", fat_size_sct);
+
+       dir_entries = le16_to_cpu(vs->dir_entries);
+       dir_size_sct = ((dir_entries * sizeof(struct vfat_dir_entry)) +
+                       (sector_size_bytes-1)) / sector_size_bytes;
+       dbg("dir_size_sct 0x%x", dir_size_sct);
+
+       cluster_count = sect_count - (reserved_sct + fat_size_sct + dir_size_sct);
+       cluster_count /= vs->sectors_per_cluster;
+       dbg("cluster_count 0x%x", cluster_count);
+
+//     if (cluster_count < FAT12_MAX) {
+//             strcpy(id->type_version, "FAT12");
+//     } else if (cluster_count < FAT16_MAX) {
+//             strcpy(id->type_version, "FAT16");
+//     } else {
+//             strcpy(id->type_version, "FAT32");
+//             goto fat32;
+//     }
+       if (cluster_count >= FAT16_MAX)
+               goto fat32;
+
+       /* the label may be an attribute in the root directory */
+       root_start_off = (reserved_sct + fat_size_sct) * sector_size_bytes;
+       dbg("root dir start 0x%llx", (unsigned long long) root_start_off);
+       dbg("expected entries 0x%x", dir_entries);
+
+       buf_size = dir_entries * sizeof(struct vfat_dir_entry);
+       buf = volume_id_get_buffer(id, fat_partition_off + root_start_off, buf_size);
+       if (buf == NULL)
+               goto ret;
+
+       label = get_attr_volume_id((struct vfat_dir_entry*) buf, dir_entries);
+
+       vs = volume_id_get_buffer(id, fat_partition_off, 0x200);
+       if (vs == NULL)
+               return -1;
+
+       if (label != NULL && memcmp(label, "NO NAME    ", 11) != 0) {
+//             volume_id_set_label_raw(id, label, 11);
+               volume_id_set_label_string(id, label, 11);
+       } else if (memcmp(vs->type.fat.label, "NO NAME    ", 11) != 0) {
+//             volume_id_set_label_raw(id, vs->type.fat.label, 11);
+               volume_id_set_label_string(id, vs->type.fat.label, 11);
+       }
+       volume_id_set_uuid(id, vs->type.fat.serno, UUID_DOS);
+       goto ret;
+
+ fat32:
+       /* FAT32 root dir is a cluster chain like any other directory */
+       buf_size = vs->sectors_per_cluster * sector_size_bytes;
+       root_cluster = le32_to_cpu(vs->type.fat32.root_cluster);
+       start_data_sct = reserved_sct + fat_size_sct;
+
+       next_cluster = root_cluster;
+       maxloop = 100;
+       while (--maxloop) {
+               uint64_t next_off_sct;
+               uint64_t next_off;
+               uint64_t fat_entry_off;
+               int count;
+
+               dbg("next_cluster 0x%x", (unsigned)next_cluster);
+               next_off_sct = (uint64_t)(next_cluster - 2) * vs->sectors_per_cluster;
+               next_off = (start_data_sct + next_off_sct) * sector_size_bytes;
+               dbg("cluster offset 0x%llx", (unsigned long long) next_off);
+
+               /* get cluster */
+               buf = volume_id_get_buffer(id, fat_partition_off + next_off, buf_size);
+               if (buf == NULL)
+                       goto ret;
+
+               dir = (struct vfat_dir_entry*) buf;
+               count = buf_size / sizeof(struct vfat_dir_entry);
+               dbg("expected entries 0x%x", count);
+
+               label = get_attr_volume_id(dir, count);
+               if (label)
+                       break;
+
+               /* get FAT entry */
+               fat_entry_off = (reserved_sct * sector_size_bytes) + (next_cluster * sizeof(uint32_t));
+               dbg("fat_entry_off 0x%llx", (unsigned long long)fat_entry_off);
+               buf = volume_id_get_buffer(id, fat_partition_off + fat_entry_off, buf_size);
+               if (buf == NULL)
+                       goto ret;
+
+               /* set next cluster */
+               next_cluster = le32_to_cpu(*(uint32_t*)buf) & 0x0fffffff;
+               if (next_cluster < 2 || next_cluster > FAT32_MAX)
+                       break;
+       }
+       if (maxloop == 0)
+               dbg("reached maximum follow count of root cluster chain, give up");
+
+       vs = volume_id_get_buffer(id, fat_partition_off, 0x200);
+       if (vs == NULL)
+               return -1;
+
+       if (label != NULL && memcmp(label, "NO NAME    ", 11) != 0) {
+//             volume_id_set_label_raw(id, label, 11);
+               volume_id_set_label_string(id, label, 11);
+       } else if (memcmp(vs->type.fat32.label, "NO NAME    ", 11) != 0) {
+//             volume_id_set_label_raw(id, vs->type.fat32.label, 11);
+               volume_id_set_label_string(id, vs->type.fat32.label, 11);
+       }
+       volume_id_set_uuid(id, vs->type.fat32.serno, UUID_DOS);
+
+ ret:
+//     volume_id_set_usage(id, VOLUME_ID_FILESYSTEM);
+//     id->type = "vfat";
+
+       return 0;
+}
diff --git a/util-linux/volume_id/get_devname.c b/util-linux/volume_id/get_devname.c
new file mode 100644 (file)
index 0000000..ac69f78
--- /dev/null
@@ -0,0 +1,252 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Support functions for mounting devices by label/uuid
+ *
+ * Copyright (C) 2006 by Jason Schoon <floydpink@gmail.com>
+ * Some portions cribbed from e2fsprogs, util-linux, dosfstools
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+#include "volume_id_internal.h"
+
+//#define BLKGETSIZE64 _IOR(0x12,114,size_t)
+
+static struct uuidCache_s {
+       struct uuidCache_s *next;
+//     int major, minor;
+       char *device;
+       char *label;
+       char *uc_uuid; /* prefix makes it easier to grep for */
+} *uuidCache;
+
+/* Returns !0 on error.
+ * Otherwise, returns malloc'ed strings for label and uuid
+ * (and they can't be NULL, although they can be "").
+ * NB: closes fd. */
+static int
+get_label_uuid(int fd, char **label, char **uuid)
+{
+       int rv = 1;
+       uint64_t size;
+       struct volume_id *vid;
+
+       /* fd is owned by vid now */
+       vid = volume_id_open_node(fd);
+
+       if (ioctl(/*vid->*/fd, BLKGETSIZE64, &size) != 0)
+               size = 0;
+
+       if (volume_id_probe_all(vid, /*0,*/ size) != 0)
+               goto ret;
+
+       if (vid->label[0] != '\0' || vid->uuid[0] != '\0') {
+               *label = xstrndup(vid->label, sizeof(vid->label));
+               *uuid  = xstrndup(vid->uuid, sizeof(vid->uuid));
+               dbg("found label '%s', uuid '%s' on %s", *label, *uuid, device);
+               rv = 0;
+       }
+ ret:
+       free_volume_id(vid); /* also closes fd */
+       return rv;
+}
+
+/* NB: we take ownership of (malloc'ed) label and uuid */
+static void
+uuidcache_addentry(char *device, /*int major, int minor,*/ char *label, char *uuid)
+{
+       struct uuidCache_s *last;
+
+       if (!uuidCache) {
+               last = uuidCache = xzalloc(sizeof(*uuidCache));
+       } else {
+               for (last = uuidCache; last->next; last = last->next)
+                       continue;
+               last->next = xzalloc(sizeof(*uuidCache));
+               last = last->next;
+       }
+       /*last->next = NULL; - xzalloc did it*/
+//     last->major = major;
+//     last->minor = minor;
+       last->device = device;
+       last->label = label;
+       last->uc_uuid = uuid;
+}
+
+/* If get_label_uuid() on device_name returns success,
+ * add a cache entry for this device.
+ * If device node does not exist, it will be temporarily created. */
+static int FAST_FUNC
+uuidcache_check_device(const char *device,
+               struct stat *statbuf,
+               void *userData UNUSED_PARAM,
+               int depth UNUSED_PARAM)
+{
+       char *uuid = uuid; /* for compiler */
+       char *label = label;
+       int fd;
+
+       if (!S_ISBLK(statbuf->st_mode))
+               return TRUE;
+
+       fd = open(device, O_RDONLY);
+       if (fd < 0)
+               return TRUE;
+
+       /* get_label_uuid() closes fd in all cases (success & failure) */
+       if (get_label_uuid(fd, &label, &uuid) == 0) {
+               /* uuidcache_addentry() takes ownership of all three params */
+               uuidcache_addentry(xstrdup(device), /*ma, mi,*/ label, uuid);
+       }
+       return TRUE;
+}
+
+static void
+uuidcache_init(void)
+{
+       if (uuidCache)
+               return;
+
+       /* We were scanning /proc/partitions
+        * and /proc/sys/dev/cdrom/info here.
+        * Missed volume managers. I see that "standard" blkid uses these:
+        * /dev/mapper/control
+        * /proc/devices
+        * /proc/evms/volumes
+        * /proc/lvm/VGs
+        * This is unacceptably complex. Let's just scan /dev.
+        * (Maybe add scanning of /sys/block/XXX/dev for devices
+        * somehow not having their /dev/XXX entries created?) */
+
+       recursive_action("/dev", ACTION_RECURSE,
+               uuidcache_check_device, /* file_action */
+               NULL, /* dir_action */
+               NULL, /* userData */
+               0 /* depth */);
+}
+
+#define UUID   1
+#define VOL    2
+
+#ifdef UNUSED
+static char *
+get_spec_by_x(int n, const char *t, int *majorPtr, int *minorPtr)
+{
+       struct uuidCache_s *uc;
+
+       uuidcache_init();
+       uc = uuidCache;
+
+       while (uc) {
+               switch (n) {
+               case UUID:
+                       if (strcmp(t, uc->uc_uuid) == 0) {
+                               *majorPtr = uc->major;
+                               *minorPtr = uc->minor;
+                               return uc->device;
+                       }
+                       break;
+               case VOL:
+                       if (strcmp(t, uc->label) == 0) {
+                               *majorPtr = uc->major;
+                               *minorPtr = uc->minor;
+                               return uc->device;
+                       }
+                       break;
+               }
+               uc = uc->next;
+       }
+       return NULL;
+}
+
+static unsigned char
+fromhex(char c)
+{
+       if (isdigit(c))
+               return (c - '0');
+       return ((c|0x20) - 'a' + 10);
+}
+
+static char *
+get_spec_by_uuid(const char *s, int *major, int *minor)
+{
+       unsigned char uuid[16];
+       int i;
+
+       if (strlen(s) != 36 || s[8] != '-' || s[13] != '-'
+        || s[18] != '-' || s[23] != '-'
+       ) {
+               goto bad_uuid;
+       }
+       for (i = 0; i < 16; i++) {
+               if (*s == '-')
+                       s++;
+               if (!isxdigit(s[0]) || !isxdigit(s[1]))
+                       goto bad_uuid;
+               uuid[i] = ((fromhex(s[0]) << 4) | fromhex(s[1]));
+               s += 2;
+       }
+       return get_spec_by_x(UUID, (char *)uuid, major, minor);
+
+ bad_uuid:
+       fprintf(stderr, _("mount: bad UUID"));
+       return 0;
+}
+
+static char *
+get_spec_by_volume_label(const char *s, int *major, int *minor)
+{
+       return get_spec_by_x(VOL, s, major, minor);
+}
+#endif // UNUSED
+
+/* Used by blkid */
+void display_uuid_cache(void)
+{
+       struct uuidCache_s *u;
+
+       uuidcache_init();
+       u = uuidCache;
+       while (u) {
+               printf("%s:", u->device);
+               if (u->label[0])
+                       printf(" LABEL=\"%s\"", u->label);
+               if (u->uc_uuid[0])
+                       printf(" UUID=\"%s\"", u->uc_uuid);
+               bb_putchar('\n');
+               u = u->next;
+       }
+}
+
+/* Used by mount and findfs */
+
+char *get_devname_from_label(const char *spec)
+{
+       struct uuidCache_s *uc;
+
+       uuidcache_init();
+       uc = uuidCache;
+       while (uc) {
+               if (uc->label[0] && strcmp(spec, uc->label) == 0) {
+                       return xstrdup(uc->device);
+               }
+               uc = uc->next;
+       }
+       return NULL;
+}
+
+char *get_devname_from_uuid(const char *spec)
+{
+       struct uuidCache_s *uc;
+
+       uuidcache_init();
+       uc = uuidCache;
+       while (uc) {
+               /* case of hex numbers doesn't matter */
+               if (strcasecmp(spec, uc->uc_uuid) == 0) {
+                       return xstrdup(uc->device);
+               }
+               uc = uc->next;
+       }
+       return NULL;
+}
diff --git a/util-linux/volume_id/hfs.c b/util-linux/volume_id/hfs.c
new file mode 100644 (file)
index 0000000..f99b895
--- /dev/null
@@ -0,0 +1,292 @@
+/*
+ * volume_id - reads filesystem label and uuid
+ *
+ * Copyright (C) 2004 Kay Sievers <kay.sievers@vrfy.org>
+ *
+ *     This 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.
+ *
+ *     This 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 this library; if not, write to the Free Software
+ *     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "volume_id_internal.h"
+
+struct hfs_finder_info{
+       uint32_t        boot_folder;
+       uint32_t        start_app;
+       uint32_t        open_folder;
+       uint32_t        os9_folder;
+       uint32_t        reserved;
+       uint32_t        osx_folder;
+       uint8_t         id[8];
+} __attribute__((__packed__));
+
+struct hfs_mdb {
+       uint8_t         signature[2];
+       uint32_t        cr_date;
+       uint32_t        ls_Mod;
+       uint16_t        atrb;
+       uint16_t        nm_fls;
+       uint16_t        vbm_st;
+       uint16_t        alloc_ptr;
+       uint16_t        nm_al_blks;
+       uint32_t        al_blk_size;
+       uint32_t        clp_size;
+       uint16_t        al_bl_st;
+       uint32_t        nxt_cnid;
+       uint16_t        free_bks;
+       uint8_t         label_len;
+       uint8_t         label[27];
+       uint32_t        vol_bkup;
+       uint16_t        vol_seq_num;
+       uint32_t        wr_cnt;
+       uint32_t        xt_clump_size;
+       uint32_t        ct_clump_size;
+       uint16_t        num_root_dirs;
+       uint32_t        file_count;
+       uint32_t        dir_count;
+       struct hfs_finder_info finder_info;
+       uint8_t         embed_sig[2];
+       uint16_t        embed_startblock;
+       uint16_t        embed_blockcount;
+} __attribute__((__packed__));
+
+struct hfsplus_bnode_descriptor {
+       uint32_t        next;
+       uint32_t        prev;
+       uint8_t         type;
+       uint8_t         height;
+       uint16_t        num_recs;
+       uint16_t        reserved;
+} __attribute__((__packed__));
+
+struct hfsplus_bheader_record {
+       uint16_t        depth;
+       uint32_t        root;
+       uint32_t        leaf_count;
+       uint32_t        leaf_head;
+       uint32_t        leaf_tail;
+       uint16_t        node_size;
+} __attribute__((__packed__));
+
+struct hfsplus_catalog_key {
+       uint16_t        key_len;
+       uint32_t        parent_id;
+       uint16_t        unicode_len;
+       uint8_t         unicode[255 * 2];
+} __attribute__((__packed__));
+
+struct hfsplus_extent {
+       uint32_t        start_block;
+       uint32_t        block_count;
+} __attribute__((__packed__));
+
+#define HFSPLUS_EXTENT_COUNT           8
+struct hfsplus_fork {
+       uint64_t        total_size;
+       uint32_t        clump_size;
+       uint32_t        total_blocks;
+       struct hfsplus_extent extents[HFSPLUS_EXTENT_COUNT];
+} __attribute__((__packed__));
+
+struct hfsplus_vol_header {
+       uint8_t         signature[2];
+       uint16_t        version;
+       uint32_t        attributes;
+       uint32_t        last_mount_vers;
+       uint32_t        reserved;
+       uint32_t        create_date;
+       uint32_t        modify_date;
+       uint32_t        backup_date;
+       uint32_t        checked_date;
+       uint32_t        file_count;
+       uint32_t        folder_count;
+       uint32_t        blocksize;
+       uint32_t        total_blocks;
+       uint32_t        free_blocks;
+       uint32_t        next_alloc;
+       uint32_t        rsrc_clump_sz;
+       uint32_t        data_clump_sz;
+       uint32_t        next_cnid;
+       uint32_t        write_count;
+       uint64_t        encodings_bmp;
+       struct hfs_finder_info finder_info;
+       struct hfsplus_fork alloc_file;
+       struct hfsplus_fork ext_file;
+       struct hfsplus_fork cat_file;
+       struct hfsplus_fork attr_file;
+       struct hfsplus_fork start_file;
+} __attribute__((__packed__));
+
+#define HFS_SUPERBLOCK_OFFSET          0x400
+#define HFS_NODE_LEAF                  0xff
+#define HFSPLUS_POR_CNID               1
+
+int volume_id_probe_hfs_hfsplus(struct volume_id *id /*,uint64_t off*/)
+{
+       uint64_t off = 0;
+       unsigned blocksize;
+       unsigned cat_block;
+       unsigned ext_block_start;
+       unsigned ext_block_count;
+       int ext;
+       unsigned leaf_node_head;
+       unsigned leaf_node_count;
+       unsigned leaf_node_size;
+       unsigned leaf_block;
+       uint64_t leaf_off;
+       unsigned alloc_block_size;
+       unsigned alloc_first_block;
+       unsigned embed_first_block;
+       unsigned record_count;
+       struct hfsplus_vol_header *hfsplus;
+       struct hfsplus_bnode_descriptor *descr;
+       struct hfsplus_bheader_record *bnode;
+       struct hfsplus_catalog_key *key;
+       unsigned label_len;
+       struct hfsplus_extent extents[HFSPLUS_EXTENT_COUNT];
+       struct hfs_mdb *hfs;
+       const uint8_t *buf;
+
+       dbg("probing at offset 0x%llx", (unsigned long long) off);
+
+       buf = volume_id_get_buffer(id, off + HFS_SUPERBLOCK_OFFSET, 0x200);
+       if (buf == NULL)
+               return -1;
+
+       hfs = (struct hfs_mdb *) buf;
+       if (hfs->signature[0] != 'B' || hfs->signature[1] != 'D')
+               goto checkplus;
+
+       /* it may be just a hfs wrapper for hfs+ */
+       if (hfs->embed_sig[0] == 'H' && hfs->embed_sig[1] == '+') {
+               alloc_block_size = be32_to_cpu(hfs->al_blk_size);
+               dbg("alloc_block_size 0x%x", alloc_block_size);
+
+               alloc_first_block = be16_to_cpu(hfs->al_bl_st);
+               dbg("alloc_first_block 0x%x", alloc_first_block);
+
+               embed_first_block = be16_to_cpu(hfs->embed_startblock);
+               dbg("embed_first_block 0x%x", embed_first_block);
+
+               off += (alloc_first_block * 512) +
+                      (embed_first_block * alloc_block_size);
+               dbg("hfs wrapped hfs+ found at offset 0x%llx", (unsigned long long) off);
+
+               buf = volume_id_get_buffer(id, off + HFS_SUPERBLOCK_OFFSET, 0x200);
+               if (buf == NULL)
+                       return -1;
+               goto checkplus;
+       }
+
+       if (hfs->label_len > 0 && hfs->label_len < 28) {
+//             volume_id_set_label_raw(id, hfs->label, hfs->label_len);
+               volume_id_set_label_string(id, hfs->label, hfs->label_len) ;
+       }
+
+       volume_id_set_uuid(id, hfs->finder_info.id, UUID_HFS);
+//     volume_id_set_usage(id, VOLUME_ID_FILESYSTEM);
+//     id->type = "hfs";
+
+       return 0;
+
+ checkplus:
+       hfsplus = (struct hfsplus_vol_header *) buf;
+       if (hfs->signature[0] == 'H')
+               if (hfs->signature[1] == '+' || hfs->signature[1] == 'X')
+                       goto hfsplus;
+       return -1;
+
+ hfsplus:
+       volume_id_set_uuid(id, hfsplus->finder_info.id, UUID_HFS);
+
+       blocksize = be32_to_cpu(hfsplus->blocksize);
+       dbg("blocksize %u", blocksize);
+
+       memcpy(extents, hfsplus->cat_file.extents, sizeof(extents));
+       cat_block = be32_to_cpu(extents[0].start_block);
+       dbg("catalog start block 0x%x", cat_block);
+
+       buf = volume_id_get_buffer(id, off + (cat_block * blocksize), 0x2000);
+       if (buf == NULL)
+               goto found;
+
+       bnode = (struct hfsplus_bheader_record *)
+               &buf[sizeof(struct hfsplus_bnode_descriptor)];
+
+       leaf_node_head = be32_to_cpu(bnode->leaf_head);
+       dbg("catalog leaf node 0x%x", leaf_node_head);
+
+       leaf_node_size = be16_to_cpu(bnode->node_size);
+       dbg("leaf node size 0x%x", leaf_node_size);
+
+       leaf_node_count = be32_to_cpu(bnode->leaf_count);
+       dbg("leaf node count 0x%x", leaf_node_count);
+       if (leaf_node_count == 0)
+               goto found;
+
+       leaf_block = (leaf_node_head * leaf_node_size) / blocksize;
+
+       /* get physical location */
+       for (ext = 0; ext < HFSPLUS_EXTENT_COUNT; ext++) {
+               ext_block_start = be32_to_cpu(extents[ext].start_block);
+               ext_block_count = be32_to_cpu(extents[ext].block_count);
+               dbg("extent start block 0x%x, count 0x%x", ext_block_start, ext_block_count);
+
+               if (ext_block_count == 0)
+                       goto found;
+
+               /* this is our extent */
+               if (leaf_block < ext_block_count)
+                       break;
+
+               leaf_block -= ext_block_count;
+       }
+       if (ext == HFSPLUS_EXTENT_COUNT)
+               goto found;
+       dbg("found block in extent %i", ext);
+
+       leaf_off = (ext_block_start + leaf_block) * blocksize;
+
+       buf = volume_id_get_buffer(id, off + leaf_off, leaf_node_size);
+       if (buf == NULL)
+               goto found;
+
+       descr = (struct hfsplus_bnode_descriptor *) buf;
+       dbg("descriptor type 0x%x", descr->type);
+
+       record_count = be16_to_cpu(descr->num_recs);
+       dbg("number of records %u", record_count);
+       if (record_count == 0)
+               goto found;
+
+       if (descr->type != HFS_NODE_LEAF)
+               goto found;
+
+       key = (struct hfsplus_catalog_key *)
+               &buf[sizeof(struct hfsplus_bnode_descriptor)];
+
+       dbg("parent id 0x%x", be32_to_cpu(key->parent_id));
+       if (key->parent_id != cpu_to_be32(HFSPLUS_POR_CNID))
+               goto found;
+
+       label_len = be16_to_cpu(key->unicode_len) * 2;
+       dbg("label unicode16 len %i", label_len);
+//     volume_id_set_label_raw(id, key->unicode, label_len);
+       volume_id_set_label_unicode16(id, key->unicode, BE, label_len);
+
+ found:
+//     volume_id_set_usage(id, VOLUME_ID_FILESYSTEM);
+//     id->type = "hfsplus";
+
+       return 0;
+}
diff --git a/util-linux/volume_id/iso9660.c b/util-linux/volume_id/iso9660.c
new file mode 100644 (file)
index 0000000..82f5e48
--- /dev/null
@@ -0,0 +1,120 @@
+/*
+ * volume_id - reads filesystem label and uuid
+ *
+ * Copyright (C) 2004 Kay Sievers <kay.sievers@vrfy.org>
+ *
+ *     This 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.
+ *
+ *     This 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 this library; if not, write to the Free Software
+ *     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "volume_id_internal.h"
+
+#define ISO_SUPERBLOCK_OFFSET          0x8000
+#define ISO_SECTOR_SIZE                        0x800
+#define ISO_VD_OFFSET                  (ISO_SUPERBLOCK_OFFSET + ISO_SECTOR_SIZE)
+#define ISO_VD_PRIMARY                 0x1
+#define ISO_VD_SUPPLEMENTARY           0x2
+#define ISO_VD_END                     0xff
+#define ISO_VD_MAX                     16
+
+struct iso_volume_descriptor {
+       uint8_t         vd_type;
+       uint8_t         vd_id[5];
+       uint8_t         vd_version;
+       uint8_t         flags;
+       uint8_t         system_id[32];
+       uint8_t         volume_id[32];
+       uint8_t         unused[8];
+       uint8_t         space_size[8];
+       uint8_t         escape_sequences[8];
+} __attribute__((__packed__));
+
+struct high_sierra_volume_descriptor {
+       uint8_t         foo[8];
+       uint8_t         type;
+       uint8_t         id[4];
+       uint8_t         version;
+} __attribute__((__packed__));
+
+int volume_id_probe_iso9660(struct volume_id *id /*,uint64_t off*/)
+{
+#define off ((uint64_t)0)
+       uint8_t *buf;
+       struct iso_volume_descriptor *is;
+       struct high_sierra_volume_descriptor *hs;
+
+       dbg("probing at offset 0x%llx", (unsigned long long) off);
+
+       buf = volume_id_get_buffer(id, off + ISO_SUPERBLOCK_OFFSET, 0x200);
+       if (buf == NULL)
+               return -1;
+
+       is = (struct iso_volume_descriptor *) buf;
+
+       if (memcmp(is->vd_id, "CD001", 5) == 0) {
+               int vd_offset;
+               int i;
+
+               dbg("read label from PVD");
+//             volume_id_set_label_raw(id, is->volume_id, 32);
+               volume_id_set_label_string(id, is->volume_id, 32);
+
+               dbg("looking for SVDs");
+               vd_offset = ISO_VD_OFFSET;
+               for (i = 0; i < ISO_VD_MAX; i++) {
+                       uint8_t svd_label[64];
+
+                       is = volume_id_get_buffer(id, off + vd_offset, 0x200);
+                       if (is == NULL || is->vd_type == ISO_VD_END)
+                               break;
+                       if (is->vd_type != ISO_VD_SUPPLEMENTARY)
+                               continue;
+
+                       dbg("found SVD at offset 0x%llx", (unsigned long long) (off + vd_offset));
+                       if (memcmp(is->escape_sequences, "%/@", 3) == 0
+                        || memcmp(is->escape_sequences, "%/C", 3) == 0
+                        || memcmp(is->escape_sequences, "%/E", 3) == 0
+                       ) {
+                               dbg("Joliet extension found");
+                               volume_id_set_unicode16((char *)svd_label, sizeof(svd_label), is->volume_id, BE, 32);
+                               if (memcmp(id->label, svd_label, 16) == 0) {
+                                       dbg("SVD label is identical, use the possibly longer PVD one");
+                                       break;
+                               }
+
+//                             volume_id_set_label_raw(id, is->volume_id, 32);
+                               volume_id_set_label_string(id, svd_label, 32);
+//                             strcpy(id->type_version, "Joliet Extension");
+                               goto found;
+                       }
+                       vd_offset += ISO_SECTOR_SIZE;
+               }
+               goto found;
+       }
+
+       hs = (struct high_sierra_volume_descriptor *) buf;
+
+       if (memcmp(hs->id, "CDROM", 5) == 0) {
+//             strcpy(id->type_version, "High Sierra");
+               goto found;
+       }
+
+       return -1;
+
+ found:
+//     volume_id_set_usage(id, VOLUME_ID_FILESYSTEM);
+//     id->type = "iso9660";
+
+       return 0;
+}
diff --git a/util-linux/volume_id/jfs.c b/util-linux/volume_id/jfs.c
new file mode 100644 (file)
index 0000000..4c39e53
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+ * volume_id - reads filesystem label and uuid
+ *
+ * Copyright (C) 2004 Kay Sievers <kay.sievers@vrfy.org>
+ *
+ *     This 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.
+ *
+ *     This 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 this library; if not, write to the Free Software
+ *     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "volume_id_internal.h"
+
+struct jfs_super_block {
+       uint8_t         magic[4];
+       uint32_t        version;
+       uint64_t        size;
+       uint32_t        bsize;
+       uint32_t        dummy1;
+       uint32_t        pbsize;
+       uint32_t        dummy2[27];
+       uint8_t         uuid[16];
+       uint8_t         label[16];
+       uint8_t         loguuid[16];
+} __attribute__((__packed__));
+
+#define JFS_SUPERBLOCK_OFFSET                  0x8000
+
+int volume_id_probe_jfs(struct volume_id *id /*,uint64_t off*/)
+{
+#define off ((uint64_t)0)
+       struct jfs_super_block *js;
+
+       dbg("probing at offset 0x%llx", (unsigned long long) off);
+
+       js = volume_id_get_buffer(id, off + JFS_SUPERBLOCK_OFFSET, 0x200);
+       if (js == NULL)
+               return -1;
+
+       if (memcmp(js->magic, "JFS1", 4) != 0)
+               return -1;
+
+//     volume_id_set_label_raw(id, js->label, 16);
+       volume_id_set_label_string(id, js->label, 16);
+       volume_id_set_uuid(id, js->uuid, UUID_DCE);
+
+//     volume_id_set_usage(id, VOLUME_ID_FILESYSTEM);
+//     id->type = "jfs";
+
+       return 0;
+}
diff --git a/util-linux/volume_id/linux_raid.c b/util-linux/volume_id/linux_raid.c
new file mode 100644 (file)
index 0000000..cc02469
--- /dev/null
@@ -0,0 +1,80 @@
+/*
+ * volume_id - reads filesystem label and uuid
+ *
+ * Copyright (C) 2004 Kay Sievers <kay.sievers@vrfy.org>
+ *
+ *     This 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.
+ *
+ *     This 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 this library; if not, write to the Free Software
+ *     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "volume_id_internal.h"
+
+struct mdp_super_block {
+       uint32_t        md_magic;
+       uint32_t        major_version;
+       uint32_t        minor_version;
+       uint32_t        patch_version;
+       uint32_t        gvalid_words;
+       uint32_t        set_uuid0;
+       uint32_t        ctime;
+       uint32_t        level;
+       uint32_t        size;
+       uint32_t        nr_disks;
+       uint32_t        raid_disks;
+       uint32_t        md_minor;
+       uint32_t        not_persistent;
+       uint32_t        set_uuid1;
+       uint32_t        set_uuid2;
+       uint32_t        set_uuid3;
+} __attribute__((packed));
+
+#define MD_RESERVED_BYTES              0x10000
+#define MD_MAGIC                       0xa92b4efc
+
+int volume_id_probe_linux_raid(struct volume_id *id /*,uint64_t off*/, uint64_t size)
+{
+#define off ((uint64_t)0)
+       uint64_t sboff;
+       uint8_t uuid[16];
+       struct mdp_super_block *mdp;
+
+       dbg("probing at offset 0x%llx, size 0x%llx",
+           (unsigned long long) off, (unsigned long long) size);
+
+       if (size < 0x10000)
+               return -1;
+
+       sboff = (size & ~(MD_RESERVED_BYTES - 1)) - MD_RESERVED_BYTES;
+       mdp = volume_id_get_buffer(id, off + sboff, 0x800);
+       if (mdp == NULL)
+               return -1;
+
+       if (mdp->md_magic != cpu_to_le32(MD_MAGIC))
+               return -1;
+
+       *(uint32_t*)uuid = mdp->set_uuid0;
+       memcpy(&uuid[4], &mdp->set_uuid1, 12);
+       volume_id_set_uuid(id, uuid, UUID_DCE);
+
+//     snprintf(id->type_version, sizeof(id->type_version)-1, "%u.%u.%u",
+//              le32_to_cpu(mdp->major_version),
+//              le32_to_cpu(mdp->minor_version),
+//              le32_to_cpu(mdp->patch_version));
+
+       dbg("found raid signature");
+//     volume_id_set_usage(id, VOLUME_ID_RAID);
+//     id->type = "linux_raid_member";
+
+       return 0;
+}
diff --git a/util-linux/volume_id/linux_swap.c b/util-linux/volume_id/linux_swap.c
new file mode 100644 (file)
index 0000000..c9b62e9
--- /dev/null
@@ -0,0 +1,74 @@
+/*
+ * volume_id - reads filesystem label and uuid
+ *
+ * Copyright (C) 2004 Kay Sievers <kay.sievers@vrfy.org>
+ *
+ *     This 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.
+ *
+ *     This 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 this library; if not, write to the Free Software
+ *     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "volume_id_internal.h"
+
+struct swap_header_v1_2 {
+       uint8_t         bootbits[1024];
+       uint32_t        version;
+       uint32_t        last_page;
+       uint32_t        nr_badpages;
+       uint8_t         uuid[16];
+       uint8_t         volume_name[16];
+} __attribute__((__packed__));
+
+#define LARGEST_PAGESIZE                       0x4000
+
+int volume_id_probe_linux_swap(struct volume_id *id /*,uint64_t off*/)
+{
+#define off ((uint64_t)0)
+       struct swap_header_v1_2 *sw;
+       const uint8_t *buf;
+       unsigned page;
+
+       dbg("probing at offset 0x%llx", (unsigned long long) off);
+
+       /* the swap signature is at the end of the PAGE_SIZE */
+       for (page = 0x1000; page <= LARGEST_PAGESIZE; page <<= 1) {
+                       buf = volume_id_get_buffer(id, off + page-10, 10);
+                       if (buf == NULL)
+                               return -1;
+
+                       if (memcmp(buf, "SWAP-SPACE", 10) == 0) {
+//                             id->type_version[0] = '1';
+//                             id->type_version[1] = '\0';
+                               goto found;
+                       }
+
+                       if (memcmp(buf, "SWAPSPACE2", 10) == 0) {
+                               sw = volume_id_get_buffer(id, off, sizeof(struct swap_header_v1_2));
+                               if (sw == NULL)
+                                       return -1;
+//                             id->type_version[0] = '2';
+//                             id->type_version[1] = '\0';
+//                             volume_id_set_label_raw(id, sw->volume_name, 16);
+                               volume_id_set_label_string(id, sw->volume_name, 16);
+                               volume_id_set_uuid(id, sw->uuid, UUID_DCE);
+                               goto found;
+                       }
+       }
+       return -1;
+
+found:
+//     volume_id_set_usage(id, VOLUME_ID_OTHER);
+//     id->type = "swap";
+
+       return 0;
+}
diff --git a/util-linux/volume_id/luks.c b/util-linux/volume_id/luks.c
new file mode 100644 (file)
index 0000000..ebc7d16
--- /dev/null
@@ -0,0 +1,100 @@
+/*
+ * volume_id - reads filesystem label and uuid
+ *
+ * Copyright (C) 2005 W. Michael Petullo <mike@flyn.org>
+ *
+ *     This 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.
+ *
+ *     This 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 this library; if not, write to the Free Software
+ *     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "volume_id_internal.h"
+
+#define LUKS_MAGIC_L             6
+#define UUID_STRING_L           40
+#define LUKS_CIPHERNAME_L       32
+#define LUKS_CIPHERMODE_L       32
+#define LUKS_HASHSPEC_L         32
+#define LUKS_DIGESTSIZE         20
+#define LUKS_SALTSIZE           32
+#define LUKS_NUMKEYS             8
+
+static const uint8_t LUKS_MAGIC[] = { 'L','U','K','S', 0xba, 0xbe };
+
+struct luks_phdr {
+       uint8_t         magic[LUKS_MAGIC_L];
+       uint16_t        version;
+       uint8_t         cipherName[LUKS_CIPHERNAME_L];
+       uint8_t         cipherMode[LUKS_CIPHERMODE_L];
+       uint8_t         hashSpec[LUKS_HASHSPEC_L];
+       uint32_t        payloadOffset;
+       uint32_t        keyBytes;
+       uint8_t         mkDigest[LUKS_DIGESTSIZE];
+       uint8_t         mkDigestSalt[LUKS_SALTSIZE];
+       uint32_t        mkDigestIterations;
+       uint8_t         uuid[UUID_STRING_L];
+       struct {
+               uint32_t        active;
+               uint32_t        passwordIterations;
+               uint8_t         passwordSalt[LUKS_SALTSIZE];
+               uint32_t        keyMaterialOffset;
+               uint32_t        stripes;
+       } keyblock[LUKS_NUMKEYS];
+};
+
+enum {
+       EXPECTED_SIZE_luks_phdr = 0
+               + 1 * LUKS_MAGIC_L
+               + 2
+               + 1 * LUKS_CIPHERNAME_L
+               + 1 * LUKS_CIPHERMODE_L
+               + 1 * LUKS_HASHSPEC_L
+               + 4
+               + 4
+               + 1 * LUKS_DIGESTSIZE
+               + 1 * LUKS_SALTSIZE
+               + 4
+               + 1 * UUID_STRING_L
+               + LUKS_NUMKEYS * (0
+                 + 4
+                 + 4
+                 + 1 * LUKS_SALTSIZE
+                 + 4
+                 + 4
+                 )
+};
+
+struct BUG_bad_size_luks_phdr {
+       char BUG_bad_size_luks_phdr[
+               sizeof(struct luks_phdr) == EXPECTED_SIZE_luks_phdr ?
+               1 : -1];
+};
+
+int volume_id_probe_luks(struct volume_id *id /*,uint64_t off*/)
+{
+#define off ((uint64_t)0)
+       struct luks_phdr *header;
+
+       header = volume_id_get_buffer(id, off, sizeof(*header));
+       if (header == NULL)
+               return -1;
+
+       if (memcmp(header->magic, LUKS_MAGIC, LUKS_MAGIC_L))
+               return -1;
+
+//     volume_id_set_usage(id, VOLUME_ID_CRYPTO);
+       volume_id_set_uuid(id, header->uuid, UUID_DCE_STRING);
+//     id->type = "crypto_LUKS";
+
+       return 0;
+}
diff --git a/util-linux/volume_id/ntfs.c b/util-linux/volume_id/ntfs.c
new file mode 100644 (file)
index 0000000..6e8f1dd
--- /dev/null
@@ -0,0 +1,194 @@
+/*
+ * volume_id - reads filesystem label and uuid
+ *
+ * Copyright (C) 2004 Kay Sievers <kay.sievers@vrfy.org>
+ *
+ *     This 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.
+ *
+ *     This 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 this library; if not, write to the Free Software
+ *     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "volume_id_internal.h"
+
+struct ntfs_super_block {
+       uint8_t         jump[3];
+       uint8_t         oem_id[8];
+       uint16_t        bytes_per_sector;
+       uint8_t         sectors_per_cluster;
+       uint16_t        reserved_sectors;
+       uint8_t         fats;
+       uint16_t        root_entries;
+       uint16_t        sectors;
+       uint8_t         media_type;
+       uint16_t        sectors_per_fat;
+       uint16_t        sectors_per_track;
+       uint16_t        heads;
+       uint32_t        hidden_sectors;
+       uint32_t        large_sectors;
+       uint16_t        unused[2];
+       uint64_t        number_of_sectors;
+       uint64_t        mft_cluster_location;
+       uint64_t        mft_mirror_cluster_location;
+       int8_t          cluster_per_mft_record;
+       uint8_t         reserved1[3];
+       int8_t          cluster_per_index_record;
+       uint8_t         reserved2[3];
+       uint8_t         volume_serial[8];
+       uint16_t        checksum;
+} __attribute__((__packed__));
+
+struct master_file_table_record {
+       uint8_t         magic[4];
+       uint16_t        usa_ofs;
+       uint16_t        usa_count;
+       uint64_t        lsn;
+       uint16_t        sequence_number;
+       uint16_t        link_count;
+       uint16_t        attrs_offset;
+       uint16_t        flags;
+       uint32_t        bytes_in_use;
+       uint32_t        bytes_allocated;
+} __attribute__((__packed__));
+
+struct file_attribute {
+       uint32_t        type;
+       uint32_t        len;
+       uint8_t         non_resident;
+       uint8_t         name_len;
+       uint16_t        name_offset;
+       uint16_t        flags;
+       uint16_t        instance;
+       uint32_t        value_len;
+       uint16_t        value_offset;
+} __attribute__((__packed__));
+
+struct volume_info {
+       uint64_t        reserved;
+       uint8_t         major_ver;
+       uint8_t         minor_ver;
+} __attribute__((__packed__));
+
+#define MFT_RECORD_VOLUME                      3
+#define MFT_RECORD_ATTR_VOLUME_NAME            0x60
+#define MFT_RECORD_ATTR_VOLUME_INFO            0x70
+#define MFT_RECORD_ATTR_OBJECT_ID              0x40
+#define MFT_RECORD_ATTR_END                    0xffffffffu
+
+int volume_id_probe_ntfs(struct volume_id *id /*,uint64_t off*/)
+{
+#define off ((uint64_t)0)
+       unsigned sector_size;
+       unsigned cluster_size;
+       uint64_t mft_cluster;
+       uint64_t mft_off;
+       unsigned mft_record_size;
+       unsigned attr_type;
+       unsigned attr_off;
+       unsigned attr_len;
+       unsigned val_off;
+       unsigned val_len;
+       struct master_file_table_record *mftr;
+       struct ntfs_super_block *ns;
+       const uint8_t *buf;
+       const uint8_t *val;
+
+       dbg("probing at offset 0x%llx", (unsigned long long) off);
+
+       ns = volume_id_get_buffer(id, off, 0x200);
+       if (ns == NULL)
+               return -1;
+
+       if (memcmp(ns->oem_id, "NTFS", 4) != 0)
+               return -1;
+
+       volume_id_set_uuid(id, ns->volume_serial, UUID_NTFS);
+
+       sector_size = le16_to_cpu(ns->bytes_per_sector);
+       cluster_size = ns->sectors_per_cluster * sector_size;
+       mft_cluster = le64_to_cpu(ns->mft_cluster_location);
+       mft_off = mft_cluster * cluster_size;
+
+       if (ns->cluster_per_mft_record < 0)
+               /* size = -log2(mft_record_size); normally 1024 Bytes */
+               mft_record_size = 1 << -ns->cluster_per_mft_record;
+       else
+               mft_record_size = ns->cluster_per_mft_record * cluster_size;
+
+       dbg("sectorsize  0x%x", sector_size);
+       dbg("clustersize 0x%x", cluster_size);
+       dbg("mftcluster  %llu", (unsigned long long) mft_cluster);
+       dbg("mftoffset  0x%llx", (unsigned long long) mft_off);
+       dbg("cluster per mft_record  %i", ns->cluster_per_mft_record);
+       dbg("mft record size  %i", mft_record_size);
+
+       buf = volume_id_get_buffer(id, off + mft_off + (MFT_RECORD_VOLUME * mft_record_size),
+                        mft_record_size);
+       if (buf == NULL)
+               goto found;
+
+       mftr = (struct master_file_table_record*) buf;
+
+       dbg("mftr->magic '%c%c%c%c'", mftr->magic[0], mftr->magic[1], mftr->magic[2], mftr->magic[3]);
+       if (memcmp(mftr->magic, "FILE", 4) != 0)
+               goto found;
+
+       attr_off = le16_to_cpu(mftr->attrs_offset);
+       dbg("file $Volume's attributes are at offset %i", attr_off);
+
+       while (1) {
+               struct file_attribute *attr;
+
+               attr = (struct file_attribute*) &buf[attr_off];
+               attr_type = le32_to_cpu(attr->type);
+               attr_len = le16_to_cpu(attr->len);
+               val_off = le16_to_cpu(attr->value_offset);
+               val_len = le32_to_cpu(attr->value_len);
+               attr_off += attr_len;
+
+               if (attr_len == 0)
+                       break;
+
+               if (attr_off >= mft_record_size)
+                       break;
+
+               if (attr_type == MFT_RECORD_ATTR_END)
+                       break;
+
+               dbg("found attribute type 0x%x, len %i, at offset %i",
+                   attr_type, attr_len, attr_off);
+
+//             if (attr_type == MFT_RECORD_ATTR_VOLUME_INFO) {
+//                     struct volume_info *info;
+//                     dbg("found info, len %i", val_len);
+//                     info = (struct volume_info*) (((uint8_t *) attr) + val_off);
+//                     snprintf(id->type_version, sizeof(id->type_version)-1,
+//                              "%u.%u", info->major_ver, info->minor_ver);
+//             }
+
+               if (attr_type == MFT_RECORD_ATTR_VOLUME_NAME) {
+                       dbg("found label, len %i", val_len);
+                       if (val_len > VOLUME_ID_LABEL_SIZE)
+                               val_len = VOLUME_ID_LABEL_SIZE;
+
+                       val = ((uint8_t *) attr) + val_off;
+//                     volume_id_set_label_raw(id, val, val_len);
+                       volume_id_set_label_unicode16(id, val, LE, val_len);
+               }
+       }
+
+ found:
+//     volume_id_set_usage(id, VOLUME_ID_FILESYSTEM);
+//     id->type = "ntfs";
+
+       return 0;
+}
diff --git a/util-linux/volume_id/ocfs2.c b/util-linux/volume_id/ocfs2.c
new file mode 100644 (file)
index 0000000..8417d91
--- /dev/null
@@ -0,0 +1,106 @@
+/*
+ * volume_id - reads filesystem label and uuid
+ *
+ * Copyright (C) Andre Masella <andre@masella.no-ip.org>
+ *
+ *     This 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.
+ *
+ *     This 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 this library; if not, write to the Free Software
+ *     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "volume_id_internal.h"
+
+/* All these values are taken from ocfs2-tools's ocfs2_fs.h */
+#define OCFS2_VOL_UUID_LEN                     16
+#define OCFS2_MAX_VOL_LABEL_LEN                        64
+#define OCFS2_SUPERBLOCK_OFFSET                        0x2000
+
+
+/* This is the superblock. The OCFS2 header files have structs in structs.
+This is one has been simplified since we only care about the superblock.
+*/
+
+struct ocfs2_super_block {
+       uint8_t         i_signature[8];                 /* Signature for validation */
+       uint32_t        i_generation;                   /* Generation number */
+       int16_t         i_suballoc_slot;                        /* Slot suballocator this inode belongs to */
+       uint16_t        i_suballoc_bit;                 /* Bit offset in suballocator block group */
+       uint32_t        i_reserved0;
+       uint32_t        i_clusters;                     /* Cluster count */
+       uint32_t        i_uid;                          /* Owner UID */
+       uint32_t        i_gid;                          /* Owning GID */
+       uint64_t        i_size;                         /* Size in bytes */
+       uint16_t        i_mode;                         /* File mode */
+       uint16_t        i_links_count;                  /* Links count */
+       uint32_t        i_flags;                                /* File flags */
+       uint64_t        i_atime;                                /* Access time */
+       uint64_t        i_ctime;                                /* Creation time */
+       uint64_t        i_mtime;                                /* Modification time */
+       uint64_t        i_dtime;                                /* Deletion time */
+       uint64_t        i_blkno;                                /* Offset on disk, in blocks */
+       uint64_t        i_last_eb_blk;                  /* Pointer to last extent block */
+       uint32_t        i_fs_generation;                        /* Generation per fs-instance */
+       uint32_t        i_atime_nsec;
+       uint32_t        i_ctime_nsec;
+       uint32_t        i_mtime_nsec;
+       uint64_t        i_reserved1[9];
+       uint64_t        i_pad1;                         /* Generic way to refer to this 64bit union */
+       /* Normally there is a union of the different block types, but we only care about the superblock. */
+       uint16_t        s_major_rev_level;
+       uint16_t        s_minor_rev_level;
+       uint16_t        s_mnt_count;
+       int16_t         s_max_mnt_count;
+       uint16_t        s_state;                                /* File system state */
+       uint16_t        s_errors;                               /* Behaviour when detecting errors */
+       uint32_t        s_checkinterval;                        /* Max time between checks */
+       uint64_t        s_lastcheck;                    /* Time of last check */
+       uint32_t        s_creator_os;                   /* OS */
+       uint32_t        s_feature_compat;                       /* Compatible feature set */
+       uint32_t        s_feature_incompat;             /* Incompatible feature set */
+       uint32_t        s_feature_ro_compat;            /* Readonly-compatible feature set */
+       uint64_t        s_root_blkno;                   /* Offset, in blocks, of root directory dinode */
+       uint64_t        s_system_dir_blkno;             /* Offset, in blocks, of system directory dinode */
+       uint32_t        s_blocksize_bits;                       /* Blocksize for this fs */
+       uint32_t        s_clustersize_bits;             /* Clustersize for this fs */
+       uint16_t        s_max_slots;                    /* Max number of simultaneous mounts before tunefs required */
+       uint16_t        s_reserved1;
+       uint32_t        s_reserved2;
+       uint64_t        s_first_cluster_group;          /* Block offset of 1st cluster group header */
+       uint8_t         s_label[OCFS2_MAX_VOL_LABEL_LEN];       /* Label for mounting, etc. */
+       uint8_t         s_uuid[OCFS2_VOL_UUID_LEN];     /* 128-bit uuid */
+} __attribute__((__packed__));
+
+int volume_id_probe_ocfs2(struct volume_id *id /*,uint64_t off*/)
+{
+#define off ((uint64_t)0)
+       struct ocfs2_super_block *os;
+
+       dbg("probing at offset 0x%llx", (unsigned long long) off);
+
+       os = volume_id_get_buffer(id, off + OCFS2_SUPERBLOCK_OFFSET, 0x200);
+       if (os == NULL)
+               return -1;
+
+       if (memcmp(os->i_signature, "OCFSV2", 6) != 0) {
+               return -1;
+       }
+
+//     volume_id_set_usage(id, VOLUME_ID_FILESYSTEM);
+//     volume_id_set_label_raw(id, os->s_label, OCFS2_MAX_VOL_LABEL_LEN < VOLUME_ID_LABEL_SIZE ?
+//                                     OCFS2_MAX_VOL_LABEL_LEN : VOLUME_ID_LABEL_SIZE);
+       volume_id_set_label_string(id, os->s_label, OCFS2_MAX_VOL_LABEL_LEN < VOLUME_ID_LABEL_SIZE ?
+                                       OCFS2_MAX_VOL_LABEL_LEN : VOLUME_ID_LABEL_SIZE);
+       volume_id_set_uuid(id, os->s_uuid, UUID_DCE);
+//     id->type = "ocfs2";
+       return 0;
+}
diff --git a/util-linux/volume_id/reiserfs.c b/util-linux/volume_id/reiserfs.c
new file mode 100644 (file)
index 0000000..b8cdc98
--- /dev/null
@@ -0,0 +1,113 @@
+/*
+ * volume_id - reads filesystem label and uuid
+ *
+ * Copyright (C) 2004 Kay Sievers <kay.sievers@vrfy.org>
+ * Copyright (C) 2005 Tobias Klauser <tklauser@access.unizh.ch>
+ *
+ *     This 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.
+ *
+ *     This 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 this library; if not, write to the Free Software
+ *     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "volume_id_internal.h"
+
+struct reiserfs_super_block {
+       uint32_t        blocks_count;
+       uint32_t        free_blocks;
+       uint32_t        root_block;
+       uint32_t        journal_block;
+       uint32_t        journal_dev;
+       uint32_t        orig_journal_size;
+       uint32_t        dummy2[5];
+       uint16_t        blocksize;
+       uint16_t        dummy3[3];
+       uint8_t         magic[12];
+       uint32_t        dummy4[5];
+       uint8_t         uuid[16];
+       uint8_t         label[16];
+} __attribute__((__packed__));
+
+struct reiser4_super_block {
+       uint8_t         magic[16];
+       uint16_t        dummy[2];
+       uint8_t         uuid[16];
+       uint8_t         label[16];
+       uint64_t        dummy2;
+} __attribute__((__packed__));
+
+#define REISERFS1_SUPERBLOCK_OFFSET            0x2000
+#define REISERFS_SUPERBLOCK_OFFSET             0x10000
+
+int volume_id_probe_reiserfs(struct volume_id *id /*,uint64_t off*/)
+{
+#define off ((uint64_t)0)
+       struct reiserfs_super_block *rs;
+       struct reiser4_super_block *rs4;
+
+       dbg("reiserfs: probing at offset 0x%llx", (unsigned long long) off);
+
+       rs = volume_id_get_buffer(id, off + REISERFS_SUPERBLOCK_OFFSET, 0x200);
+       if (rs == NULL)
+               return -1;
+
+       if (memcmp(rs->magic, "ReIsErFs", 8) == 0) {
+               dbg("reiserfs: ReIsErFs, no label");
+//             strcpy(id->type_version, "3.5");
+               goto found;
+       }
+       if (memcmp(rs->magic, "ReIsEr2Fs", 9) == 0) {
+               dbg("reiserfs: ReIsEr2Fs");
+//             strcpy(id->type_version, "3.6");
+               goto found_label;
+       }
+       if (memcmp(rs->magic, "ReIsEr3Fs", 9) == 0) {
+               dbg("reiserfs: ReIsEr3Fs");
+//             strcpy(id->type_version, "JR");
+               goto found_label;
+       }
+
+       rs4 = (struct reiser4_super_block *) rs;
+       if (memcmp(rs4->magic, "ReIsEr4", 7) == 0) {
+//             strcpy(id->type_version, "4");
+//             volume_id_set_label_raw(id, rs4->label, 16);
+               volume_id_set_label_string(id, rs4->label, 16);
+               volume_id_set_uuid(id, rs4->uuid, UUID_DCE);
+               dbg("reiserfs: ReIsEr4, label '%s' uuid '%s'", id->label, id->uuid);
+               goto found;
+       }
+
+       rs = volume_id_get_buffer(id, off + REISERFS1_SUPERBLOCK_OFFSET, 0x200);
+       if (rs == NULL)
+               return -1;
+
+       if (memcmp(rs->magic, "ReIsErFs", 8) == 0) {
+               dbg("reiserfs: ReIsErFs, no label");
+//             strcpy(id->type_version, "3.5");
+               goto found;
+       }
+
+       dbg("reiserfs: no signature found");
+       return -1;
+
+ found_label:
+//     volume_id_set_label_raw(id, rs->label, 16);
+       volume_id_set_label_string(id, rs->label, 16);
+       volume_id_set_uuid(id, rs->uuid, UUID_DCE);
+       dbg("reiserfs: label '%s' uuid '%s'", id->label, id->uuid);
+
+ found:
+//     volume_id_set_usage(id, VOLUME_ID_FILESYSTEM);
+//     id->type = "reiserfs";
+
+       return 0;
+}
diff --git a/util-linux/volume_id/romfs.c b/util-linux/volume_id/romfs.c
new file mode 100644 (file)
index 0000000..2c061bd
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ * volume_id - reads filesystem label and uuid
+ *
+ * Copyright (C) 2004 Kay Sievers <kay.sievers@vrfy.org>
+ *
+ *     This 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.
+ *
+ *     This 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 this library; if not, write to the Free Software
+ *     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "volume_id_internal.h"
+
+struct romfs_super {
+       uint8_t magic[8];
+       uint32_t size;
+       uint32_t checksum;
+       uint8_t name[0];
+} __attribute__((__packed__));
+
+int volume_id_probe_romfs(struct volume_id *id /*,uint64_t off*/)
+{
+#define off ((uint64_t)0)
+       struct romfs_super *rfs;
+
+       dbg("probing at offset 0x%llx", (unsigned long long) off);
+
+       rfs = volume_id_get_buffer(id, off, 0x200);
+       if (rfs == NULL)
+               return -1;
+
+       if (memcmp(rfs->magic, "-rom1fs-", 4) == 0) {
+               size_t len = strlen((char *)rfs->name);
+
+               if (len) {
+//                     volume_id_set_label_raw(id, rfs->name, len);
+                       volume_id_set_label_string(id, rfs->name, len);
+               }
+
+//             volume_id_set_usage(id, VOLUME_ID_FILESYSTEM);
+//             id->type = "romfs";
+               return 0;
+       }
+
+       return -1;
+}
diff --git a/util-linux/volume_id/sysv.c b/util-linux/volume_id/sysv.c
new file mode 100644 (file)
index 0000000..1650332
--- /dev/null
@@ -0,0 +1,126 @@
+/*
+ * volume_id - reads filesystem label and uuid
+ *
+ * Copyright (C) 2005 Kay Sievers <kay.sievers@vrfy.org>
+ *
+ *     This 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.
+ *
+ *     This 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 this library; if not, write to the Free Software
+ *     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "volume_id_internal.h"
+
+#define SYSV_NICINOD                   100
+#define SYSV_NICFREE                   50
+
+struct sysv_super
+{
+       uint16_t        s_isize;
+       uint16_t        s_pad0;
+       uint32_t        s_fsize;
+       uint16_t        s_nfree;
+       uint16_t        s_pad1;
+       uint32_t        s_free[SYSV_NICFREE];
+       uint16_t        s_ninode;
+       uint16_t        s_pad2;
+       uint16_t        s_inode[SYSV_NICINOD];
+       uint8_t         s_flock;
+       uint8_t         s_ilock;
+       uint8_t         s_fmod;
+       uint8_t         s_ronly;
+       uint32_t        s_time;
+       uint16_t        s_dinfo[4];
+       uint32_t        s_tfree;
+       uint16_t        s_tinode;
+       uint16_t        s_pad3;
+       uint8_t         s_fname[6];
+       uint8_t         s_fpack[6];
+       uint32_t        s_fill[12];
+       uint32_t        s_state;
+       uint32_t        s_magic;
+       uint32_t        s_type;
+} __attribute__((__packed__));
+
+#define XENIX_NICINOD                          100
+#define XENIX_NICFREE                          100
+
+struct xenix_super {
+       uint16_t        s_isize;
+       uint32_t        s_fsize;
+       uint16_t        s_nfree;
+       uint32_t        s_free[XENIX_NICFREE];
+       uint16_t        s_ninode;
+       uint16_t        s_inode[XENIX_NICINOD];
+       uint8_t         s_flock;
+       uint8_t         s_ilock;
+       uint8_t         s_fmod;
+       uint8_t         s_ronly;
+       uint32_t        s_time;
+       uint32_t        s_tfree;
+       uint16_t        s_tinode;
+       uint16_t        s_dinfo[4];
+       uint8_t         s_fname[6];
+       uint8_t         s_fpack[6];
+       uint8_t         s_clean;
+       uint8_t         s_fill[371];
+       uint32_t        s_magic;
+       uint32_t        s_type;
+} __attribute__((__packed__));
+
+#define SYSV_SUPERBLOCK_BLOCK                  0x01
+#define SYSV_MAGIC                             0xfd187e20
+#define XENIX_SUPERBLOCK_BLOCK                 0x18
+#define XENIX_MAGIC                            0x2b5544
+#define SYSV_MAX_BLOCKSIZE                     0x800
+
+int volume_id_probe_sysv(struct volume_id *id /*,uint64_t off*/)
+{
+#define off ((uint64_t)0)
+       struct sysv_super *vs;
+       struct xenix_super *xs;
+       unsigned boff;
+
+       dbg("probing at offset 0x%llx", (unsigned long long) off);
+
+       for (boff = 0x200; boff <= SYSV_MAX_BLOCKSIZE; boff <<= 1) {
+               vs = volume_id_get_buffer(id, off + (boff * SYSV_SUPERBLOCK_BLOCK), 0x200);
+               if (vs == NULL)
+                       return -1;
+
+               if (vs->s_magic == cpu_to_le32(SYSV_MAGIC) || vs->s_magic == cpu_to_be32(SYSV_MAGIC)) {
+//                     volume_id_set_label_raw(id, vs->s_fname, 6);
+                       volume_id_set_label_string(id, vs->s_fname, 6);
+//                     id->type = "sysv";
+                       goto found;
+               }
+       }
+
+       for (boff = 0x200; boff <= SYSV_MAX_BLOCKSIZE; boff <<= 1) {
+               xs = volume_id_get_buffer(id, off + (boff + XENIX_SUPERBLOCK_BLOCK), 0x200);
+               if (xs == NULL)
+                       return -1;
+
+               if (xs->s_magic == cpu_to_le32(XENIX_MAGIC) || xs->s_magic == cpu_to_be32(XENIX_MAGIC)) {
+//                     volume_id_set_label_raw(id, xs->s_fname, 6);
+                       volume_id_set_label_string(id, xs->s_fname, 6);
+//                     id->type = "xenix";
+                       goto found;
+               }
+       }
+
+       return -1;
+
+ found:
+//     volume_id_set_usage(id, VOLUME_ID_FILESYSTEM);
+       return 0;
+}
diff --git a/util-linux/volume_id/udf.c b/util-linux/volume_id/udf.c
new file mode 100644 (file)
index 0000000..e272e19
--- /dev/null
@@ -0,0 +1,173 @@
+/*
+ * volume_id - reads filesystem label and uuid
+ *
+ * Copyright (C) 2004 Kay Sievers <kay.sievers@vrfy.org>
+ *
+ *     This 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.
+ *
+ *     This 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 this library; if not, write to the Free Software
+ *     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "volume_id_internal.h"
+
+struct volume_descriptor {
+       struct descriptor_tag {
+               uint16_t        id;
+               uint16_t        version;
+               uint8_t         checksum;
+               uint8_t         reserved;
+               uint16_t        serial;
+               uint16_t        crc;
+               uint16_t        crc_len;
+               uint32_t        location;
+       } __attribute__((__packed__)) tag;
+       union {
+               struct anchor_descriptor {
+                       uint32_t        length;
+                       uint32_t        location;
+               } __attribute__((__packed__)) anchor;
+               struct primary_descriptor {
+                       uint32_t        seq_num;
+                       uint32_t        desc_num;
+                       struct dstring {
+                               uint8_t clen;
+                               uint8_t c[31];
+                       } __attribute__((__packed__)) ident;
+               } __attribute__((__packed__)) primary;
+       } __attribute__((__packed__)) type;
+} __attribute__((__packed__));
+
+struct volume_structure_descriptor {
+       uint8_t         type;
+       uint8_t         id[5];
+       uint8_t         version;
+} __attribute__((__packed__));
+
+#define UDF_VSD_OFFSET                 0x8000
+
+int volume_id_probe_udf(struct volume_id *id /*,uint64_t off*/)
+{
+#define off ((uint64_t)0)
+       struct volume_descriptor *vd;
+       struct volume_structure_descriptor *vsd;
+       unsigned bs;
+       unsigned b;
+       unsigned type;
+       unsigned count;
+       unsigned loc;
+       unsigned clen;
+
+       dbg("probing at offset 0x%llx", (unsigned long long) off);
+
+       vsd = volume_id_get_buffer(id, off + UDF_VSD_OFFSET, 0x200);
+       if (vsd == NULL)
+               return -1;
+
+       if (memcmp(vsd->id, "NSR02", 5) == 0)
+               goto blocksize;
+       if (memcmp(vsd->id, "NSR03", 5) == 0)
+               goto blocksize;
+       if (memcmp(vsd->id, "BEA01", 5) == 0)
+               goto blocksize;
+       if (memcmp(vsd->id, "BOOT2", 5) == 0)
+               goto blocksize;
+       if (memcmp(vsd->id, "CD001", 5) == 0)
+               goto blocksize;
+       if (memcmp(vsd->id, "CDW02", 5) == 0)
+               goto blocksize;
+       if (memcmp(vsd->id, "TEA03", 5) == 0)
+               goto blocksize;
+       return -1;
+
+blocksize:
+       /* search the next VSD to get the logical block size of the volume */
+       for (bs = 0x800; bs < 0x8000; bs += 0x800) {
+               vsd = volume_id_get_buffer(id, off + UDF_VSD_OFFSET + bs, 0x800);
+               if (vsd == NULL)
+                       return -1;
+               dbg("test for blocksize: 0x%x", bs);
+               if (vsd->id[0] != '\0')
+                       goto nsr;
+       }
+       return -1;
+
+nsr:
+       /* search the list of VSDs for a NSR descriptor */
+       for (b = 0; b < 64; b++) {
+               vsd = volume_id_get_buffer(id, off + UDF_VSD_OFFSET + (b * bs), 0x800);
+               if (vsd == NULL)
+                       return -1;
+
+               dbg("vsd: %c%c%c%c%c",
+                   vsd->id[0], vsd->id[1], vsd->id[2], vsd->id[3], vsd->id[4]);
+
+               if (vsd->id[0] == '\0')
+                       return -1;
+               if (memcmp(vsd->id, "NSR02", 5) == 0)
+                       goto anchor;
+               if (memcmp(vsd->id, "NSR03", 5) == 0)
+                       goto anchor;
+       }
+       return -1;
+
+anchor:
+       /* read anchor volume descriptor */
+       vd = volume_id_get_buffer(id, off + (256 * bs), 0x200);
+       if (vd == NULL)
+               return -1;
+
+       type = le16_to_cpu(vd->tag.id);
+       if (type != 2) /* TAG_ID_AVDP */
+               goto found;
+
+       /* get desriptor list address and block count */
+       count = le32_to_cpu(vd->type.anchor.length) / bs;
+       loc = le32_to_cpu(vd->type.anchor.location);
+       dbg("0x%x descriptors starting at logical secor 0x%x", count, loc);
+
+       /* pick the primary descriptor from the list */
+       for (b = 0; b < count; b++) {
+               vd = volume_id_get_buffer(id, off + ((loc + b) * bs), 0x200);
+               if (vd == NULL)
+                       return -1;
+
+               type = le16_to_cpu(vd->tag.id);
+               dbg("descriptor type %i", type);
+
+               /* check validity */
+               if (type == 0)
+                       goto found;
+               if (le32_to_cpu(vd->tag.location) != loc + b)
+                       goto found;
+
+               if (type == 1) /* TAG_ID_PVD */
+                       goto pvd;
+       }
+       goto found;
+
+ pvd:
+//     volume_id_set_label_raw(id, &(vd->type.primary.ident.clen), 32);
+
+       clen = vd->type.primary.ident.clen;
+       dbg("label string charsize=%i bit", clen);
+       if (clen == 8)
+               volume_id_set_label_string(id, vd->type.primary.ident.c, 31);
+       else if (clen == 16)
+               volume_id_set_label_unicode16(id, vd->type.primary.ident.c, BE, 31);
+
+ found:
+//     volume_id_set_usage(id, VOLUME_ID_FILESYSTEM);
+//     id->type = "udf";
+
+       return 0;
+}
diff --git a/util-linux/volume_id/unused_highpoint.c b/util-linux/volume_id/unused_highpoint.c
new file mode 100644 (file)
index 0000000..57c5cad
--- /dev/null
@@ -0,0 +1,86 @@
+/*
+ * volume_id - reads filesystem label and uuid
+ *
+ * Copyright (C) 2004 Kay Sievers <kay.sievers@vrfy.org>
+ *
+ *     This 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.
+ *
+ *     This 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 this library; if not, write to the Free Software
+ *     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "volume_id_internal.h"
+
+struct hpt37x_meta {
+       uint8_t         filler1[32];
+       uint32_t        magic;
+} __attribute__((packed));
+
+struct hpt45x_meta {
+       uint32_t        magic;
+} __attribute__((packed));
+
+#define HPT37X_CONFIG_OFF              0x1200
+#define HPT37X_MAGIC_OK                        0x5a7816f0
+#define HPT37X_MAGIC_BAD               0x5a7816fd
+
+#define HPT45X_MAGIC_OK                        0x5a7816f3
+#define HPT45X_MAGIC_BAD               0x5a7816fd
+
+
+int volume_id_probe_highpoint_37x_raid(struct volume_id *id, uint64_t off)
+{
+       struct hpt37x_meta *hpt;
+       uint32_t magic;
+
+       dbg("probing at offset 0x%llx", (unsigned long long) off);
+
+       hpt = volume_id_get_buffer(id, off + HPT37X_CONFIG_OFF, 0x200);
+       if (hpt == NULL)
+               return -1;
+
+       magic = hpt->magic;
+       if (magic != cpu_to_le32(HPT37X_MAGIC_OK) && magic != cpu_to_le32(HPT37X_MAGIC_BAD))
+               return -1;
+
+//     volume_id_set_usage(id, VOLUME_ID_RAID);
+//     id->type = "highpoint_raid_member";
+
+       return 0;
+}
+
+int volume_id_probe_highpoint_45x_raid(struct volume_id *id, uint64_t off, uint64_t size)
+{
+       struct hpt45x_meta *hpt;
+       uint64_t meta_off;
+       uint32_t magic;
+
+       dbg("probing at offset 0x%llx, size 0x%llx",
+           (unsigned long long) off, (unsigned long long) size);
+
+       if (size < 0x10000)
+               return -1;
+
+       meta_off = ((size / 0x200)-11) * 0x200;
+       hpt = volume_id_get_buffer(id, off + meta_off, 0x200);
+       if (hpt == NULL)
+               return -1;
+
+       magic = hpt->magic;
+       if (magic != cpu_to_le32(HPT45X_MAGIC_OK) && magic != cpu_to_le32(HPT45X_MAGIC_BAD))
+               return -1;
+
+//     volume_id_set_usage(id, VOLUME_ID_RAID);
+//     id->type = "highpoint_raid_member";
+
+       return 0;
+}
diff --git a/util-linux/volume_id/unused_hpfs.c b/util-linux/volume_id/unused_hpfs.c
new file mode 100644 (file)
index 0000000..8b51756
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ * volume_id - reads filesystem label and uuid
+ *
+ * Copyright (C) 2005 Kay Sievers <kay.sievers@vrfy.org>
+ *
+ *     This 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.
+ *
+ *     This 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 this library; if not, write to the Free Software
+ *     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "volume_id_internal.h"
+
+struct hpfs_super
+{
+       uint8_t         magic[4];
+       uint8_t         version;
+} __attribute__((__packed__));
+
+#define HPFS_SUPERBLOCK_OFFSET                 0x2000
+
+int volume_id_probe_hpfs(struct volume_id *id, uint64_t off)
+{
+       struct hpfs_super *hs;
+
+       dbg("probing at offset 0x%llx", (unsigned long long) off);
+
+       hs = volume_id_get_buffer(id, off + HPFS_SUPERBLOCK_OFFSET, 0x200);
+       if (hs == NULL)
+               return -1;
+
+       if (memcmp(hs->magic, "\x49\xe8\x95\xf9", 4) == 0) {
+//             sprintf(id->type_version, "%u", hs->version);
+//             volume_id_set_usage(id, VOLUME_ID_FILESYSTEM);
+//             id->type = "hpfs";
+               return 0;
+       }
+
+       return -1;
+}
diff --git a/util-linux/volume_id/unused_isw_raid.c b/util-linux/volume_id/unused_isw_raid.c
new file mode 100644 (file)
index 0000000..d928245
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+ * volume_id - reads filesystem label and uuid
+ *
+ * Copyright (C) 2005 Kay Sievers <kay.sievers@vrfy.org>
+ *
+ *     This 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.
+ *
+ *     This 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 this library; if not, write to the Free Software
+ *     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "volume_id_internal.h"
+
+struct isw_meta {
+       uint8_t         sig[32];
+       uint32_t        check_sum;
+       uint32_t        mpb_size;
+       uint32_t        family_num;
+       uint32_t        generation_num;
+} __attribute__((packed));
+
+#define ISW_SIGNATURE          "Intel Raid ISM Cfg Sig. "
+
+
+int volume_id_probe_intel_software_raid(struct volume_id *id, uint64_t off, uint64_t size)
+{
+       uint64_t meta_off;
+       struct isw_meta *isw;
+
+       dbg("probing at offset 0x%llx, size 0x%llx",
+           (unsigned long long) off, (unsigned long long) size);
+
+       if (size < 0x10000)
+               return -1;
+
+       meta_off = ((size / 0x200)-2) * 0x200;
+       isw = volume_id_get_buffer(id, off + meta_off, 0x200);
+       if (isw == NULL)
+               return -1;
+
+       if (memcmp(isw->sig, ISW_SIGNATURE, sizeof(ISW_SIGNATURE)-1) != 0)
+               return -1;
+
+//     volume_id_set_usage(id, VOLUME_ID_RAID);
+//     memcpy(id->type_version, &isw->sig[sizeof(ISW_SIGNATURE)-1], 6);
+//     id->type = "isw_raid_member";
+
+       return 0;
+}
diff --git a/util-linux/volume_id/unused_lsi_raid.c b/util-linux/volume_id/unused_lsi_raid.c
new file mode 100644 (file)
index 0000000..730a313
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ * volume_id - reads filesystem label and uuid
+ *
+ * Copyright (C) 2005 Kay Sievers <kay.sievers@vrfy.org>
+ *
+ *     This 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.
+ *
+ *     This 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 this library; if not, write to the Free Software
+ *     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "volume_id_internal.h"
+
+struct lsi_meta {
+       uint8_t         sig[6];
+} __attribute__((packed));
+
+#define LSI_SIGNATURE          "$XIDE$"
+
+int volume_id_probe_lsi_mega_raid(struct volume_id *id, uint64_t off, uint64_t size)
+{
+       uint64_t meta_off;
+       struct lsi_meta *lsi;
+
+       dbg("probing at offset 0x%llx, size 0x%llx",
+           (unsigned long long) off, (unsigned long long) size);
+
+       if (size < 0x10000)
+               return -1;
+
+       meta_off = ((size / 0x200)-1) * 0x200;
+       lsi = volume_id_get_buffer(id, off + meta_off, 0x200);
+       if (lsi == NULL)
+               return -1;
+
+       if (memcmp(lsi->sig, LSI_SIGNATURE, sizeof(LSI_SIGNATURE)-1) != 0)
+               return -1;
+
+//     volume_id_set_usage(id, VOLUME_ID_RAID);
+//     id->type = "lsi_mega_raid_member";
+
+       return 0;
+}
diff --git a/util-linux/volume_id/unused_lvm.c b/util-linux/volume_id/unused_lvm.c
new file mode 100644 (file)
index 0000000..caee04b
--- /dev/null
@@ -0,0 +1,87 @@
+/*
+ * volume_id - reads filesystem label and uuid
+ *
+ * Copyright (C) 2004 Kay Sievers <kay.sievers@vrfy.org>
+ *
+ *     This 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.
+ *
+ *     This 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 this library; if not, write to the Free Software
+ *     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "volume_id_internal.h"
+
+struct lvm1_super_block {
+       uint8_t id[2];
+} __attribute__((packed));
+
+struct lvm2_super_block {
+       uint8_t         id[8];
+       uint64_t        sector_xl;
+       uint32_t        crc_xl;
+       uint32_t        offset_xl;
+       uint8_t         type[8];
+} __attribute__((packed));
+
+#define LVM1_SB_OFF                    0x400
+
+int volume_id_probe_lvm1(struct volume_id *id, uint64_t off)
+{
+       struct lvm1_super_block *lvm;
+
+       dbg("probing at offset 0x%llx", (unsigned long long) off);
+
+       lvm = volume_id_get_buffer(id, off + LVM1_SB_OFF, 0x800);
+       if (lvm == NULL)
+               return -1;
+
+       if (lvm->id[0] != 'H' || lvm->id[1] != 'M')
+               return -1;
+
+//     volume_id_set_usage(id, VOLUME_ID_RAID);
+//     id->type = "LVM1_member";
+
+       return 0;
+}
+
+#define LVM2_LABEL_ID                  "LABELONE"
+#define LVM2LABEL_SCAN_SECTORS         4
+
+int volume_id_probe_lvm2(struct volume_id *id, uint64_t off)
+{
+       const uint8_t *buf;
+       unsigned soff;
+       struct lvm2_super_block *lvm;
+
+       dbg("probing at offset 0x%llx", (unsigned long long) off);
+
+       buf = volume_id_get_buffer(id, off, LVM2LABEL_SCAN_SECTORS * 0x200);
+       if (buf == NULL)
+               return -1;
+
+
+       for (soff = 0; soff < LVM2LABEL_SCAN_SECTORS * 0x200; soff += 0x200) {
+               lvm = (struct lvm2_super_block *) &buf[soff];
+
+               if (memcmp(lvm->id, LVM2_LABEL_ID, 8) == 0)
+                       goto found;
+       }
+
+       return -1;
+
+ found:
+//     memcpy(id->type_version, lvm->type, 8);
+//     volume_id_set_usage(id, VOLUME_ID_RAID);
+//     id->type = "LVM2_member";
+
+       return 0;
+}
diff --git a/util-linux/volume_id/unused_mac.c b/util-linux/volume_id/unused_mac.c
new file mode 100644 (file)
index 0000000..8eaa173
--- /dev/null
@@ -0,0 +1,123 @@
+/*
+ * volume_id - reads filesystem label and uuid
+ *
+ * Copyright (C) 2004 Kay Sievers <kay.sievers@vrfy.org>
+ *
+ *     This 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.
+ *
+ *     This 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 this library; if not, write to the Free Software
+ *     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "volume_id_internal.h"
+
+struct mac_driver_desc {
+       uint8_t         signature[2];
+       uint16_t        block_size;
+       uint32_t        block_count;
+} __attribute__((__packed__));
+
+struct mac_partition {
+       uint8_t         signature[2];
+       uint16_t        res1;
+       uint32_t        map_count;
+       uint32_t        start_block;
+       uint32_t        block_count;
+       uint8_t         name[32];
+       uint8_t         type[32];
+} __attribute__((__packed__));
+
+int volume_id_probe_mac_partition_map(struct volume_id *id, uint64_t off)
+{
+       const uint8_t *buf;
+       struct mac_driver_desc *driver;
+       struct mac_partition *part;
+
+       dbg("probing at offset 0x%llx", (unsigned long long) off);
+
+       buf = volume_id_get_buffer(id, off, 0x200);
+       if (buf == NULL)
+               return -1;
+
+       part = (struct mac_partition *) buf;
+       if (part->signature[0] == 'P' && part->signature[1] == 'M' /* "PM" */
+        && (memcmp(part->type, "Apple_partition_map", 19) == 0)
+       ) {
+               /* linux creates an own subdevice for the map
+                * just return the type if the drive header is missing */
+//             volume_id_set_usage(id, VOLUME_ID_PARTITIONTABLE);
+//             id->type = "mac_partition_map";
+               return 0;
+       }
+
+       driver = (struct mac_driver_desc *) buf;
+       if (driver->signature[0] == 'E' && driver->signature[1] == 'R') { /* "ER" */
+               /* we are on a main device, like a CD
+                * just try to probe the first partition from the map */
+               unsigned bsize = be16_to_cpu(driver->block_size);
+               int part_count;
+               int i;
+
+               /* get first entry of partition table */
+               buf = volume_id_get_buffer(id, off +  bsize, 0x200);
+               if (buf == NULL)
+                       return -1;
+
+               part = (struct mac_partition *) buf;
+               if (part->signature[0] != 'P' || part->signature[1] != 'M') /* not "PM" */
+                       return -1;
+
+               part_count = be32_to_cpu(part->map_count);
+               dbg("expecting %d partition entries", part_count);
+
+               if (id->partitions != NULL)
+                       free(id->partitions);
+               id->partitions = xzalloc(part_count * sizeof(struct volume_id_partition));
+
+               id->partition_count = part_count;
+
+               for (i = 0; i < part_count; i++) {
+                       uint64_t poff;
+                       uint64_t plen;
+
+                       buf = volume_id_get_buffer(id, off + ((i+1) * bsize), 0x200);
+                       if (buf == NULL)
+                               return -1;
+
+                       part = (struct mac_partition *) buf;
+                       if (part->signature[0] != 'P' || part->signature[1] != 'M') /* not "PM" */
+                               return -1;
+
+                       poff = be32_to_cpu(part->start_block) * bsize;
+                       plen = be32_to_cpu(part->block_count) * bsize;
+                       dbg("found '%s' partition entry at 0x%llx, len 0x%llx",
+                                       part->type, (unsigned long long) poff,
+                                       (unsigned long long) plen);
+
+//                     id->partitions[i].pt_off = poff;
+//                     id->partitions[i].pt_len = plen;
+
+//                     if (memcmp(part->type, "Apple_Free", 10) == 0) {
+//                             volume_id_set_usage_part(&id->partitions[i], VOLUME_ID_UNUSED);
+//                     } else if (memcmp(part->type, "Apple_partition_map", 19) == 0) {
+//                             volume_id_set_usage_part(&id->partitions[i], VOLUME_ID_PARTITIONTABLE);
+//                     } else {
+//                             volume_id_set_usage_part(&id->partitions[i], VOLUME_ID_UNPROBED);
+//                     }
+               }
+//             volume_id_set_usage(id, VOLUME_ID_PARTITIONTABLE);
+//             id->type = "mac_partition_map";
+               return 0;
+       }
+
+       return -1;
+}
diff --git a/util-linux/volume_id/unused_minix.c b/util-linux/volume_id/unused_minix.c
new file mode 100644 (file)
index 0000000..2f52093
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+ * volume_id - reads filesystem label and uuid
+ *
+ * Copyright (C) 2005 Kay Sievers <kay.sievers@vrfy.org>
+ *
+ *     This 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.
+ *
+ *     This 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 this library; if not, write to the Free Software
+ *     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "volume_id_internal.h"
+
+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;
+} __attribute__((__packed__));
+
+#define MINIX_SUPERBLOCK_OFFSET                        0x400
+
+int volume_id_probe_minix(struct volume_id *id, uint64_t off)
+{
+       struct minix_super_block *ms;
+
+       dbg("probing at offset 0x%llx", (unsigned long long) off);
+
+       ms = volume_id_get_buffer(id, off + MINIX_SUPERBLOCK_OFFSET, 0x200);
+       if (ms == NULL)
+               return -1;
+
+       if (ms->s_magic == cpu_to_le16(0x137f)) {
+//             id->type_version[0] = '1';
+               goto found;
+       }
+
+       if (ms->s_magic == cpu_to_le16(0x1387)) {
+//             id->type_version[0] = '1';
+               goto found;
+       }
+
+       if (ms->s_magic == cpu_to_le16(0x2468)) {
+//             id->type_version[0] = '2';
+               goto found;
+       }
+
+       if (ms->s_magic == cpu_to_le16(0x2478)) {
+//             id->type_version[0] = '2';
+               goto found;
+       }
+
+       return -1;
+
+ found:
+//     id->type_version[1] = '\0';
+//     volume_id_set_usage(id, VOLUME_ID_FILESYSTEM);
+//     id->type = "minix";
+       return 0;
+}
diff --git a/util-linux/volume_id/unused_msdos.c b/util-linux/volume_id/unused_msdos.c
new file mode 100644 (file)
index 0000000..097ee67
--- /dev/null
@@ -0,0 +1,193 @@
+/*
+ * volume_id - reads filesystem label and uuid
+ *
+ * Copyright (C) 2004 Kay Sievers <kay.sievers@vrfy.org>
+ *
+ *     This 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.
+ *
+ *     This 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 this library; if not, write to the Free Software
+ *     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "volume_id_internal.h"
+
+struct msdos_partition_entry {
+       uint8_t         boot_ind;
+       uint8_t         head;
+       uint8_t         sector;
+       uint8_t         cyl;
+       uint8_t         sys_ind;
+       uint8_t         end_head;
+       uint8_t         end_sector;
+       uint8_t         end_cyl;
+       uint32_t        start_sect;
+       uint32_t        nr_sects;
+} __attribute__((packed));
+
+#define MSDOS_PARTTABLE_OFFSET         0x1be
+#define MSDOS_SIG_OFF                  0x1fe
+#define BSIZE                          0x200
+#define DOS_EXTENDED_PARTITION         0x05
+#define LINUX_EXTENDED_PARTITION       0x85
+#define WIN98_EXTENDED_PARTITION       0x0f
+#define LINUX_RAID_PARTITION           0xfd
+#define is_extended(type) \
+       (type == DOS_EXTENDED_PARTITION ||      \
+        type == WIN98_EXTENDED_PARTITION ||    \
+        type == LINUX_EXTENDED_PARTITION)
+#define is_raid(type) \
+       (type == LINUX_RAID_PARTITION)
+
+int volume_id_probe_msdos_part_table(struct volume_id *id, uint64_t off)
+{
+       const uint8_t *buf;
+       int i;
+       uint64_t poff;
+       uint64_t plen;
+       uint64_t extended = 0;
+       uint64_t current;
+       uint64_t next;
+       int limit;
+       int empty = 1;
+       struct msdos_partition_entry *part;
+       struct volume_id_partition *p;
+
+       dbg("probing at offset 0x%llx", (unsigned long long) off);
+
+       buf = volume_id_get_buffer(id, off, 0x200);
+       if (buf == NULL)
+               return -1;
+
+       if (buf[MSDOS_SIG_OFF] != 0x55 || buf[MSDOS_SIG_OFF + 1] != 0xaa)
+               return -1;
+
+       /* check flags on all entries for a valid partition table */
+       part = (struct msdos_partition_entry*) &buf[MSDOS_PARTTABLE_OFFSET];
+       for (i = 0; i < 4; i++) {
+               if (part[i].boot_ind != 0 &&
+                   part[i].boot_ind != 0x80)
+                       return -1;
+
+               if (part[i].nr_sects != 0)
+                       empty = 0;
+       }
+       if (empty == 1)
+               return -1;
+
+       if (id->partitions != NULL)
+               free(id->partitions);
+       id->partitions = xzalloc(VOLUME_ID_PARTITIONS_MAX *
+                               sizeof(struct volume_id_partition));
+
+       for (i = 0; i < 4; i++) {
+               poff = (uint64_t) le32_to_cpu(part[i].start_sect) * BSIZE;
+               plen = (uint64_t) le32_to_cpu(part[i].nr_sects) * BSIZE;
+
+               if (plen == 0)
+                       continue;
+
+               p = &id->partitions[i];
+
+//             p->pt_type_raw = part[i].sys_ind;
+
+               if (is_extended(part[i].sys_ind)) {
+                       dbg("found extended partition at 0x%llx", (unsigned long long) poff);
+//                     volume_id_set_usage_part(p, VOLUME_ID_PARTITIONTABLE);
+//                     p->type = "msdos_extended_partition";
+                       if (extended == 0)
+                               extended = off + poff;
+               } else {
+                       dbg("found 0x%x data partition at 0x%llx, len 0x%llx",
+                           part[i].sys_ind, (unsigned long long) poff, (unsigned long long) plen);
+
+//                     if (is_raid(part[i].sys_ind))
+//                             volume_id_set_usage_part(p, VOLUME_ID_RAID);
+//                     else
+//                             volume_id_set_usage_part(p, VOLUME_ID_UNPROBED);
+               }
+
+//             p->pt_off = off + poff;
+//             p->pt_len = plen;
+               id->partition_count = i+1;
+       }
+
+       next = extended;
+       current = extended;
+       limit = 50;
+
+       /* follow extended partition chain and add data partitions */
+       while (next != 0) {
+               if (limit-- == 0) {
+                       dbg("extended chain limit reached");
+                       break;
+               }
+
+               buf = volume_id_get_buffer(id, current, 0x200);
+               if (buf == NULL)
+                       break;
+
+               part = (struct msdos_partition_entry*) &buf[MSDOS_PARTTABLE_OFFSET];
+
+               if (buf[MSDOS_SIG_OFF] != 0x55 || buf[MSDOS_SIG_OFF + 1] != 0xaa)
+                       break;
+
+               next = 0;
+
+               for (i = 0; i < 4; i++) {
+                       poff = (uint64_t) le32_to_cpu(part[i].start_sect) * BSIZE;
+                       plen = (uint64_t) le32_to_cpu(part[i].nr_sects) * BSIZE;
+
+                       if (plen == 0)
+                               continue;
+
+                       if (is_extended(part[i].sys_ind)) {
+                               dbg("found extended partition at 0x%llx", (unsigned long long) poff);
+                               if (next == 0)
+                                       next = extended + poff;
+                       } else {
+                               dbg("found 0x%x data partition at 0x%llx, len 0x%llx",
+                                       part[i].sys_ind, (unsigned long long) poff, (unsigned long long) plen);
+
+                               /* we always start at the 5th entry */
+//                             while (id->partition_count < 4)
+//                                     volume_id_set_usage_part(&id->partitions[id->partition_count++], VOLUME_ID_UNUSED);
+                               if (id->partition_count < 4)
+                                       id->partition_count = 4;
+
+                               p = &id->partitions[id->partition_count];
+
+//                             if (is_raid(part[i].sys_ind))
+//                                     volume_id_set_usage_part(p, VOLUME_ID_RAID);
+//                             else
+//                                     volume_id_set_usage_part(p, VOLUME_ID_UNPROBED);
+
+//                             p->pt_off = current + poff;
+//                             p->pt_len = plen;
+                               id->partition_count++;
+
+//                             p->pt_type_raw = part[i].sys_ind;
+
+                               if (id->partition_count >= VOLUME_ID_PARTITIONS_MAX) {
+                                       dbg("too many partitions");
+                                       next = 0;
+                               }
+                       }
+               }
+
+               current = next;
+       }
+
+//     volume_id_set_usage(id, VOLUME_ID_PARTITIONTABLE);
+//     id->type = "msdos_partition_table";
+
+       return 0;
+}
diff --git a/util-linux/volume_id/unused_nvidia_raid.c b/util-linux/volume_id/unused_nvidia_raid.c
new file mode 100644 (file)
index 0000000..692aad1
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ * volume_id - reads filesystem label and uuid
+ *
+ * Copyright (C) 2005 Kay Sievers <kay.sievers@vrfy.org>
+ *
+ *     This 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.
+ *
+ *     This 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 this library; if not, write to the Free Software
+ *     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "volume_id_internal.h"
+
+struct nvidia_meta {
+       uint8_t         vendor[8];
+       uint32_t        size;
+       uint32_t        chksum;
+       uint16_t        version;
+} __attribute__((packed));
+
+#define NVIDIA_SIGNATURE               "NVIDIA"
+
+int volume_id_probe_nvidia_raid(struct volume_id *id, uint64_t off, uint64_t size)
+{
+       uint64_t meta_off;
+       struct nvidia_meta *nv;
+
+       dbg("probing at offset 0x%llx, size 0x%llx",
+           (unsigned long long) off, (unsigned long long) size);
+
+       if (size < 0x10000)
+               return -1;
+
+       meta_off = ((size / 0x200)-2) * 0x200;
+       nv = volume_id_get_buffer(id, off + meta_off, 0x200);
+       if (nv == NULL)
+               return -1;
+
+       if (memcmp(nv->vendor, NVIDIA_SIGNATURE, sizeof(NVIDIA_SIGNATURE)-1) != 0)
+               return -1;
+
+//     volume_id_set_usage(id, VOLUME_ID_RAID);
+//     snprintf(id->type_version, sizeof(id->type_version)-1, "%u", le16_to_cpu(nv->version));
+//     id->type = "nvidia_raid_member";
+
+       return 0;
+}
diff --git a/util-linux/volume_id/unused_promise_raid.c b/util-linux/volume_id/unused_promise_raid.c
new file mode 100644 (file)
index 0000000..75c6f89
--- /dev/null
@@ -0,0 +1,63 @@
+/*
+ * volume_id - reads filesystem label and uuid
+ *
+ * Copyright (C) 2005 Kay Sievers <kay.sievers@vrfy.org>
+ *
+ *     This 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.
+ *
+ *     This 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 this library; if not, write to the Free Software
+ *     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "volume_id_internal.h"
+
+struct promise_meta {
+       uint8_t sig[24];
+} __attribute__((packed));
+
+#define PDC_CONFIG_OFF         0x1200
+#define PDC_SIGNATURE          "Promise Technology, Inc."
+
+int volume_id_probe_promise_fasttrack_raid(struct volume_id *id, uint64_t off, uint64_t size)
+{
+       static const unsigned short sectors[] = {
+               63, 255, 256, 16, 399
+       };
+
+       struct promise_meta *pdc;
+       unsigned i;
+
+       dbg("probing at offset 0x%llx, size 0x%llx",
+           (unsigned long long) off, (unsigned long long) size);
+
+       if (size < 0x40000)
+               return -1;
+
+       for (i = 0; i < ARRAY_SIZE(sectors); i++) {
+               uint64_t meta_off;
+
+               meta_off = ((size / 0x200) - sectors[i]) * 0x200;
+               pdc = volume_id_get_buffer(id, off + meta_off, 0x200);
+               if (pdc == NULL)
+                       return -1;
+
+               if (memcmp(pdc->sig, PDC_SIGNATURE, sizeof(PDC_SIGNATURE)-1) == 0)
+                       goto found;
+       }
+       return -1;
+
+ found:
+//     volume_id_set_usage(id, VOLUME_ID_RAID);
+//     id->type = "promise_fasttrack_raid_member";
+
+       return 0;
+}
diff --git a/util-linux/volume_id/unused_silicon_raid.c b/util-linux/volume_id/unused_silicon_raid.c
new file mode 100644 (file)
index 0000000..5143112
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+ * volume_id - reads filesystem label and uuid
+ *
+ * Copyright (C) 2005 Kay Sievers <kay.sievers@vrfy.org>
+ *
+ *     This 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.
+ *
+ *     This 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 this library; if not, write to the Free Software
+ *     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "volume_id_internal.h"
+
+struct silicon_meta {
+       uint8_t         unknown0[0x2E];
+       uint8_t         ascii_version[0x36 - 0x2E];
+       uint8_t         diskname[0x56 - 0x36];
+       uint8_t         unknown1[0x60 - 0x56];
+       uint32_t        magic;
+       uint32_t        unknown1a[0x6C - 0x64];
+       uint32_t        array_sectors_low;
+       uint32_t        array_sectors_high;
+       uint8_t         unknown2[0x78 - 0x74];
+       uint32_t        thisdisk_sectors;
+       uint8_t         unknown3[0x100 - 0x7C];
+       uint8_t         unknown4[0x104 - 0x100];
+       uint16_t        product_id;
+       uint16_t        vendor_id;
+       uint16_t        minor_ver;
+       uint16_t        major_ver;
+} __attribute__((packed));
+
+#define SILICON_MAGIC          0x2F000000
+
+int volume_id_probe_silicon_medley_raid(struct volume_id *id, uint64_t off, uint64_t size)
+{
+       uint64_t meta_off;
+       struct silicon_meta *sil;
+
+       dbg("probing at offset 0x%llx, size 0x%llx",
+           (unsigned long long) off, (unsigned long long) size);
+
+       if (size < 0x10000)
+               return -1;
+
+       meta_off = ((size / 0x200)-1) * 0x200;
+       sil = volume_id_get_buffer(id, off + meta_off, 0x200);
+       if (sil == NULL)
+               return -1;
+
+       if (sil->magic != cpu_to_le32(SILICON_MAGIC))
+               return -1;
+
+//     volume_id_set_usage(id, VOLUME_ID_RAID);
+//     snprintf(id->type_version, sizeof(id->type_version)-1, "%u.%u",
+//              le16_to_cpu(sil->major_ver), le16_to_cpu(sil->minor_ver));
+//     id->type = "silicon_medley_raid_member";
+
+       return 0;
+}
diff --git a/util-linux/volume_id/unused_ufs.c b/util-linux/volume_id/unused_ufs.c
new file mode 100644 (file)
index 0000000..8693758
--- /dev/null
@@ -0,0 +1,206 @@
+/*
+ * volume_id - reads filesystem label and uuid
+ *
+ * Copyright (C) 2004 Kay Sievers <kay.sievers@vrfy.org>
+ *
+ *     This 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.
+ *
+ *     This 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 this library; if not, write to the Free Software
+ *     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "volume_id_internal.h"
+
+struct ufs_super_block {
+       uint32_t        fs_link;
+       uint32_t        fs_rlink;
+       uint32_t        fs_sblkno;
+       uint32_t        fs_cblkno;
+       uint32_t        fs_iblkno;
+       uint32_t        fs_dblkno;
+       uint32_t        fs_cgoffset;
+       uint32_t        fs_cgmask;
+       uint32_t        fs_time;
+       uint32_t        fs_size;
+       uint32_t        fs_dsize;
+       uint32_t        fs_ncg;
+       uint32_t        fs_bsize;
+       uint32_t        fs_fsize;
+       uint32_t        fs_frag;
+       uint32_t        fs_minfree;
+       uint32_t        fs_rotdelay;
+       uint32_t        fs_rps;
+       uint32_t        fs_bmask;
+       uint32_t        fs_fmask;
+       uint32_t        fs_bshift;
+       uint32_t        fs_fshift;
+       uint32_t        fs_maxcontig;
+       uint32_t        fs_maxbpg;
+       uint32_t        fs_fragshift;
+       uint32_t        fs_fsbtodb;
+       uint32_t        fs_sbsize;
+       uint32_t        fs_csmask;
+       uint32_t        fs_csshift;
+       uint32_t        fs_nindir;
+       uint32_t        fs_inopb;
+       uint32_t        fs_nspf;
+       uint32_t        fs_optim;
+       uint32_t        fs_npsect_state;
+       uint32_t        fs_interleave;
+       uint32_t        fs_trackskew;
+       uint32_t        fs_id[2];
+       uint32_t        fs_csaddr;
+       uint32_t        fs_cssize;
+       uint32_t        fs_cgsize;
+       uint32_t        fs_ntrak;
+       uint32_t        fs_nsect;
+       uint32_t        fs_spc;
+       uint32_t        fs_ncyl;
+       uint32_t        fs_cpg;
+       uint32_t        fs_ipg;
+       uint32_t        fs_fpg;
+       struct ufs_csum {
+               uint32_t        cs_ndir;
+               uint32_t        cs_nbfree;
+               uint32_t        cs_nifree;
+               uint32_t        cs_nffree;
+       } __attribute__((__packed__)) fs_cstotal;
+       int8_t          fs_fmod;
+       int8_t          fs_clean;
+       int8_t          fs_ronly;
+       int8_t          fs_flags;
+       union {
+               struct {
+                       int8_t  fs_fsmnt[512];
+                       uint32_t        fs_cgrotor;
+                       uint32_t        fs_csp[31];
+                       uint32_t        fs_maxcluster;
+                       uint32_t        fs_cpc;
+                       uint16_t        fs_opostbl[16][8];
+               } __attribute__((__packed__)) fs_u1;
+               struct {
+                       int8_t          fs_fsmnt[468];
+                       uint8_t         fs_volname[32];
+                       uint64_t        fs_swuid;
+                       int32_t         fs_pad;
+                       uint32_t        fs_cgrotor;
+                       uint32_t        fs_ocsp[28];
+                       uint32_t        fs_contigdirs;
+                       uint32_t        fs_csp;
+                       uint32_t        fs_maxcluster;
+                       uint32_t        fs_active;
+                       int32_t         fs_old_cpc;
+                       int32_t         fs_maxbsize;
+                       int64_t         fs_sparecon64[17];
+                       int64_t         fs_sblockloc;
+                       struct ufs2_csum_total {
+                               uint64_t        cs_ndir;
+                               uint64_t        cs_nbfree;
+                               uint64_t        cs_nifree;
+                               uint64_t        cs_nffree;
+                               uint64_t        cs_numclusters;
+                               uint64_t        cs_spare[3];
+                       } __attribute__((__packed__)) fs_cstotal;
+                       struct ufs_timeval {
+                               int32_t         tv_sec;
+                               int32_t         tv_usec;
+                       } __attribute__((__packed__)) fs_time;
+                       int64_t         fs_size;
+                       int64_t         fs_dsize;
+                       uint64_t        fs_csaddr;
+                       int64_t         fs_pendingblocks;
+                       int32_t         fs_pendinginodes;
+               } __attribute__((__packed__)) fs_u2;
+       }  fs_u11;
+       union {
+               struct {
+                       int32_t         fs_sparecon[53];
+                       int32_t         fs_reclaim;
+                       int32_t         fs_sparecon2[1];
+                       int32_t         fs_state;
+                       uint32_t        fs_qbmask[2];
+                       uint32_t        fs_qfmask[2];
+               } __attribute__((__packed__)) fs_sun;
+               struct {
+                       int32_t         fs_sparecon[53];
+                       int32_t         fs_reclaim;
+                       int32_t         fs_sparecon2[1];
+                       uint32_t        fs_npsect;
+                       uint32_t        fs_qbmask[2];
+                       uint32_t        fs_qfmask[2];
+               } __attribute__((__packed__)) fs_sunx86;
+               struct {
+                       int32_t         fs_sparecon[50];
+                       int32_t         fs_contigsumsize;
+                       int32_t         fs_maxsymlinklen;
+                       int32_t         fs_inodefmt;
+                       uint32_t        fs_maxfilesize[2];
+                       uint32_t        fs_qbmask[2];
+                       uint32_t        fs_qfmask[2];
+                       int32_t         fs_state;
+               } __attribute__((__packed__)) fs_44;
+       } fs_u2;
+       int32_t         fs_postblformat;
+       int32_t         fs_nrpos;
+       int32_t         fs_postbloff;
+       int32_t         fs_rotbloff;
+       uint32_t        fs_magic;
+       uint8_t         fs_space[1];
+} __attribute__((__packed__));
+
+#define UFS_MAGIC                      0x00011954
+#define UFS2_MAGIC                     0x19540119
+#define UFS_MAGIC_FEA                  0x00195612
+#define UFS_MAGIC_LFN                  0x00095014
+
+int volume_id_probe_ufs(struct volume_id *id, uint64_t off)
+{
+       static const short offsets[] = { 0, 8, 64, 256 };
+
+       uint32_t magic;
+       unsigned i;
+       struct ufs_super_block *ufs;
+
+       dbg("probing at offset 0x%llx", (unsigned long long) off);
+
+       for (i = 0; i < ARRAY_SIZE(offsets); i++) {
+               ufs = volume_id_get_buffer(id, off + (offsets[i] * 0x400), 0x800);
+               if (ufs == NULL)
+                       return -1;
+
+               dbg("offset 0x%x", offsets[i] * 0x400);
+               magic = ufs->fs_magic;
+               if ((magic == cpu_to_be32(UFS_MAGIC))
+                || (magic == cpu_to_be32(UFS2_MAGIC))
+                || (magic == cpu_to_be32(UFS_MAGIC_FEA))
+                || (magic == cpu_to_be32(UFS_MAGIC_LFN))
+               ) {
+                       dbg("magic 0x%08x(be)", magic);
+                       goto found;
+               }
+               if ((magic == cpu_to_le32(UFS_MAGIC))
+                || (magic == cpu_to_le32(UFS2_MAGIC))
+                || (magic == cpu_to_le32(UFS_MAGIC_FEA))
+                || (magic == cpu_to_le32(UFS_MAGIC_LFN))
+               ) {
+                       dbg("magic 0x%08x(le)", magic);
+                       goto found;
+               }
+       }
+       return -1;
+
+ found:
+//     volume_id_set_usage(id, VOLUME_ID_FILESYSTEM);
+//     id->type = "ufs";
+
+       return 0;
+}
diff --git a/util-linux/volume_id/unused_via_raid.c b/util-linux/volume_id/unused_via_raid.c
new file mode 100644 (file)
index 0000000..4332946
--- /dev/null
@@ -0,0 +1,68 @@
+/*
+ * volume_id - reads filesystem label and uuid
+ *
+ * Copyright (C) 2005 Kay Sievers <kay.sievers@vrfy.org>
+ *
+ *     This 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.
+ *
+ *     This 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 this library; if not, write to the Free Software
+ *     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "volume_id_internal.h"
+
+struct via_meta {
+       uint16_t        signature;
+       uint8_t         version_number;
+       struct via_array {
+               uint16_t        disk_bits;
+               uint8_t         disk_array_ex;
+               uint32_t        capacity_low;
+               uint32_t        capacity_high;
+               uint32_t        serial_checksum;
+       } __attribute((packed)) array;
+       uint32_t        serial_checksum[8];
+       uint8_t         checksum;
+} __attribute__((packed));
+
+#define VIA_SIGNATURE          0xAA55
+
+int volume_id_probe_via_raid(struct volume_id *id, uint64_t off, uint64_t size)
+{
+       uint64_t meta_off;
+       struct via_meta *via;
+
+       dbg("probing at offset 0x%llx, size 0x%llx",
+           (unsigned long long) off, (unsigned long long) size);
+
+       if (size < 0x10000)
+               return -1;
+
+       meta_off = ((size / 0x200)-1) * 0x200;
+
+       via = volume_id_get_buffer(id, off + meta_off, 0x200);
+       if (via == NULL)
+               return -1;
+
+       if (via->signature != cpu_to_le16(VIA_SIGNATURE))
+               return -1;
+
+       if (via->version_number > 1)
+               return -1;
+
+//     volume_id_set_usage(id, VOLUME_ID_RAID);
+//     id->type_version[0] = '0' + via->version_number;
+//     id->type_version[1] = '\0';
+//     id->type = "via_raid_member";
+
+       return 0;
+}
diff --git a/util-linux/volume_id/util.c b/util-linux/volume_id/util.c
new file mode 100644 (file)
index 0000000..dd75c7b
--- /dev/null
@@ -0,0 +1,282 @@
+/*
+ * volume_id - reads filesystem label and uuid
+ *
+ * Copyright (C) 2005 Kay Sievers <kay.sievers@vrfy.org>
+ *
+ *     This 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.
+ *
+ *     This 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 this library; if not, write to the Free Software
+ *     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "volume_id_internal.h"
+
+void volume_id_set_unicode16(char *str, size_t len, const uint8_t *buf, enum endian endianess, size_t count)
+{
+       unsigned i, j;
+       unsigned c;
+
+       j = 0;
+       for (i = 0; i + 2 <= count; i += 2) {
+               if (endianess == LE)
+                       c = (buf[i+1] << 8) | buf[i];
+               else
+                       c = (buf[i] << 8) | buf[i+1];
+               if (c == 0) {
+                       str[j] = '\0';
+                       break;
+               } else if (c < 0x80) {
+                       if (j+1 >= len)
+                               break;
+                       str[j++] = (uint8_t) c;
+               } else if (c < 0x800) {
+                       if (j+2 >= len)
+                               break;
+                       str[j++] = (uint8_t) (0xc0 | (c >> 6));
+                       str[j++] = (uint8_t) (0x80 | (c & 0x3f));
+               } else {
+                       if (j+3 >= len)
+                               break;
+                       str[j++] = (uint8_t) (0xe0 | (c >> 12));
+                       str[j++] = (uint8_t) (0x80 | ((c >> 6) & 0x3f));
+                       str[j++] = (uint8_t) (0x80 | (c & 0x3f));
+               }
+       }
+       str[j] = '\0';
+}
+
+#ifdef UNUSED
+static const char *usage_to_string(enum volume_id_usage usage_id)
+{
+       switch (usage_id) {
+       case VOLUME_ID_FILESYSTEM:
+               return "filesystem";
+       case VOLUME_ID_PARTITIONTABLE:
+               return "partitiontable";
+       case VOLUME_ID_OTHER:
+               return "other";
+       case VOLUME_ID_RAID:
+               return "raid";
+       case VOLUME_ID_DISKLABEL:
+               return "disklabel";
+       case VOLUME_ID_CRYPTO:
+               return "crypto";
+       case VOLUME_ID_UNPROBED:
+               return "unprobed";
+       case VOLUME_ID_UNUSED:
+               return "unused";
+       }
+       return NULL;
+}
+
+void volume_id_set_usage_part(struct volume_id_partition *part, enum volume_id_usage usage_id)
+{
+       part->usage_id = usage_id;
+       part->usage = usage_to_string(usage_id);
+}
+
+void volume_id_set_usage(struct volume_id *id, enum volume_id_usage usage_id)
+{
+       id->usage_id = usage_id;
+       id->usage = usage_to_string(usage_id);
+}
+
+void volume_id_set_label_raw(struct volume_id *id, const uint8_t *buf, size_t count)
+{
+       memcpy(id->label_raw, buf, count);
+       id->label_raw_len = count;
+}
+#endif
+
+#ifdef NOT_NEEDED
+static size_t strnlen(const char *s, size_t maxlen)
+{
+       size_t i;
+       if (!maxlen) return 0;
+       if (!s) return 0;
+       for (i = 0; *s && i < maxlen; ++s) ++i;
+       return i;
+}
+#endif
+
+void volume_id_set_label_string(struct volume_id *id, const uint8_t *buf, size_t count)
+{
+       unsigned i;
+
+       memcpy(id->label, buf, count);
+
+       /* remove trailing whitespace */
+       i = strnlen(id->label, count);
+       while (i--) {
+               if (!isspace(id->label[i]))
+                       break;
+       }
+       id->label[i+1] = '\0';
+}
+
+void volume_id_set_label_unicode16(struct volume_id *id, const uint8_t *buf, enum endian endianess, size_t count)
+{
+        volume_id_set_unicode16(id->label, sizeof(id->label), buf, endianess, count);
+}
+
+void volume_id_set_uuid(struct volume_id *id, const uint8_t *buf, enum uuid_format format)
+{
+       unsigned i;
+       unsigned count = 0;
+
+       switch (format) {
+       case UUID_DOS:
+               count = 4;
+               break;
+       case UUID_NTFS:
+       case UUID_HFS:
+               count = 8;
+               break;
+       case UUID_DCE:
+               count = 16;
+               break;
+       case UUID_DCE_STRING:
+               /* 36 is ok, id->uuid has one extra byte for NUL */
+               count = VOLUME_ID_UUID_SIZE;
+               break;
+       }
+//     memcpy(id->uuid_raw, buf, count);
+//     id->uuid_raw_len = count;
+
+       /* if set, create string in the same format, the native platform uses */
+       for (i = 0; i < count; i++)
+               if (buf[i] != 0)
+                       goto set;
+       return; /* all bytes are zero, leave it empty ("") */
+
+set:
+       switch (format) {
+       case UUID_DOS:
+               sprintf(id->uuid, "%02X%02X-%02X%02X",
+                       buf[3], buf[2], buf[1], buf[0]);
+               break;
+       case UUID_NTFS:
+               sprintf(id->uuid, "%02X%02X%02X%02X%02X%02X%02X%02X",
+                       buf[7], buf[6], buf[5], buf[4],
+                       buf[3], buf[2], buf[1], buf[0]);
+               break;
+       case UUID_HFS:
+               sprintf(id->uuid, "%02X%02X%02X%02X%02X%02X%02X%02X",
+                       buf[0], buf[1], buf[2], buf[3],
+                       buf[4], buf[5], buf[6], buf[7]);
+               break;
+       case UUID_DCE:
+               sprintf(id->uuid,
+                       "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x",
+                       buf[0], buf[1], buf[2], buf[3],
+                       buf[4], buf[5],
+                       buf[6], buf[7],
+                       buf[8], buf[9],
+                       buf[10], buf[11], buf[12], buf[13], buf[14], buf[15]);
+               break;
+       case UUID_DCE_STRING:
+               memcpy(id->uuid, buf, count);
+               id->uuid[count] = '\0';
+               break;
+       }
+}
+
+/* Do not use xlseek here. With it, single corrupted filesystem
+ * may result in attempt to seek past device -> exit.
+ * It's better to ignore such fs and continue.  */
+void *volume_id_get_buffer(struct volume_id *id, uint64_t off, size_t len)
+{
+       uint8_t *dst;
+       unsigned small_off;
+       ssize_t read_len;
+
+       dbg("get buffer off 0x%llx(%llu), len 0x%zx",
+               (unsigned long long) off, (unsigned long long) off, len);
+
+       /* check if requested area fits in superblock buffer */
+       if (off + len <= SB_BUFFER_SIZE
+        /* && off <= SB_BUFFER_SIZE - want this paranoid overflow check? */
+       ) {
+               if (id->sbbuf == NULL) {
+                       id->sbbuf = xmalloc(SB_BUFFER_SIZE);
+               }
+               small_off = off;
+               dst = id->sbbuf;
+
+               /* check if we need to read */
+               len += off;
+               if (len <= id->sbbuf_len)
+                       goto ret; /* we already have it */
+
+               dbg("read sbbuf len:0x%x", (unsigned) len);
+               id->sbbuf_len = len;
+               off = 0;
+               goto do_read;
+       }
+
+       if (len > SEEK_BUFFER_SIZE) {
+               dbg("seek buffer too small %d", SEEK_BUFFER_SIZE);
+               return NULL;
+       }
+       dst = id->seekbuf;
+
+       /* check if we need to read */
+       if ((off >= id->seekbuf_off)
+        && ((off + len) <= (id->seekbuf_off + id->seekbuf_len))
+       ) {
+               small_off = off - id->seekbuf_off; /* can't overflow */
+               goto ret; /* we already have it */
+       }
+
+       id->seekbuf_off = off;
+       id->seekbuf_len = len;
+       id->seekbuf = xrealloc(id->seekbuf, len);
+       small_off = 0;
+       dst = id->seekbuf;
+       dbg("read seekbuf off:0x%llx len:0x%zx",
+                               (unsigned long long) off, len);
+ do_read:
+       if (lseek(id->fd, off, SEEK_SET) != off) {
+               dbg("seek(0x%llx) failed", (unsigned long long) off);
+               goto err;
+       }
+       read_len = full_read(id->fd, dst, len);
+       if (read_len != len) {
+               dbg("requested 0x%x bytes, got 0x%x bytes",
+                               (unsigned) len, (unsigned) read_len);
+ err:
+               /* No filesystem can be this tiny. It's most likely
+                * non-associated loop device, empty drive and so on.
+                * Flag it, making it possible to short circuit future
+                * accesses. Rationale:
+                * users complained of slow blkid due to empty floppy drives.
+                */
+               if (off < 64*1024)
+                       id->error = 1;
+               /* id->seekbuf_len or id->sbbuf_len is wrong now! Fixing. */
+               volume_id_free_buffer(id);
+               return NULL;
+       }
+ ret:
+       return dst + small_off;
+}
+
+void volume_id_free_buffer(struct volume_id *id)
+{
+       free(id->sbbuf);
+       id->sbbuf = NULL;
+       id->sbbuf_len = 0;
+       free(id->seekbuf);
+       id->seekbuf = NULL;
+       id->seekbuf_len = 0;
+       id->seekbuf_off = 0; /* paranoia */
+}
diff --git a/util-linux/volume_id/volume_id.c b/util-linux/volume_id/volume_id.c
new file mode 100644 (file)
index 0000000..1acd905
--- /dev/null
@@ -0,0 +1,244 @@
+/*
+ * volume_id - reads filesystem label and uuid
+ *
+ * Copyright (C) 2005 Kay Sievers <kay.sievers@vrfy.org>
+ *
+ *     This 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.
+ *
+ *     This 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 this library; if not, write to the Free Software
+ *     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "volume_id_internal.h"
+
+
+/* Some detection routines do not set label or uuid anyway,
+ * so they are disabled. */
+
+/* Looks for partitions, we don't use it: */
+#define ENABLE_FEATURE_VOLUMEID_MAC           0
+/* #define ENABLE_FEATURE_VOLUMEID_MSDOS      0 - NB: this one
+ * was not properly added to probe table anyway - ??! */
+
+/* None of RAIDs have label or uuid, except LinuxRAID: */
+#define ENABLE_FEATURE_VOLUMEID_HIGHPOINTRAID 0
+#define ENABLE_FEATURE_VOLUMEID_ISWRAID       0
+#define ENABLE_FEATURE_VOLUMEID_LSIRAID       0
+#define ENABLE_FEATURE_VOLUMEID_LVM           0
+#define ENABLE_FEATURE_VOLUMEID_NVIDIARAID    0
+#define ENABLE_FEATURE_VOLUMEID_PROMISERAID   0
+#define ENABLE_FEATURE_VOLUMEID_SILICONRAID   0
+#define ENABLE_FEATURE_VOLUMEID_VIARAID       0
+
+/* These filesystems also have no label or uuid: */
+#define ENABLE_FEATURE_VOLUMEID_MINIX         0
+#define ENABLE_FEATURE_VOLUMEID_HPFS          0
+#define ENABLE_FEATURE_VOLUMEID_UFS           0
+
+
+typedef int (*raid_probe_fptr)(struct volume_id *id, /*uint64_t off,*/ uint64_t size);
+typedef int (*probe_fptr)(struct volume_id *id /*, uint64_t off*/);
+
+static const raid_probe_fptr raid1[] = {
+#if ENABLE_FEATURE_VOLUMEID_LINUXRAID
+       volume_id_probe_linux_raid,
+#endif
+#if ENABLE_FEATURE_VOLUMEID_ISWRAID
+       volume_id_probe_intel_software_raid,
+#endif
+#if ENABLE_FEATURE_VOLUMEID_LSIRAID
+       volume_id_probe_lsi_mega_raid,
+#endif
+#if ENABLE_FEATURE_VOLUMEID_VIARAID
+       volume_id_probe_via_raid,
+#endif
+#if ENABLE_FEATURE_VOLUMEID_SILICONRAID
+       volume_id_probe_silicon_medley_raid,
+#endif
+#if ENABLE_FEATURE_VOLUMEID_NVIDIARAID
+       volume_id_probe_nvidia_raid,
+#endif
+#if ENABLE_FEATURE_VOLUMEID_PROMISERAID
+       volume_id_probe_promise_fasttrack_raid,
+#endif
+#if ENABLE_FEATURE_VOLUMEID_HIGHPOINTRAID
+       volume_id_probe_highpoint_45x_raid,
+#endif
+};
+
+static const probe_fptr raid2[] = {
+#if ENABLE_FEATURE_VOLUMEID_LVM
+       volume_id_probe_lvm1,
+       volume_id_probe_lvm2,
+#endif
+#if ENABLE_FEATURE_VOLUMEID_HIGHPOINTRAID
+       volume_id_probe_highpoint_37x_raid,
+#endif
+#if ENABLE_FEATURE_VOLUMEID_LUKS
+       volume_id_probe_luks,
+#endif
+};
+
+/* signature in the first block, only small buffer needed */
+static const probe_fptr fs1[] = {
+#if ENABLE_FEATURE_VOLUMEID_FAT
+       volume_id_probe_vfat,
+#endif
+#if ENABLE_FEATURE_VOLUMEID_MAC
+       volume_id_probe_mac_partition_map,
+#endif
+#if ENABLE_FEATURE_VOLUMEID_XFS
+       volume_id_probe_xfs,
+#endif
+};
+
+/* fill buffer with maximum */
+static const probe_fptr fs2[] = {
+#if ENABLE_FEATURE_VOLUMEID_LINUXSWAP
+       volume_id_probe_linux_swap,
+#endif
+#if ENABLE_FEATURE_VOLUMEID_EXT
+       volume_id_probe_ext,
+#endif
+#if ENABLE_FEATURE_VOLUMEID_REISERFS
+       volume_id_probe_reiserfs,
+#endif
+#if ENABLE_FEATURE_VOLUMEID_JFS
+       volume_id_probe_jfs,
+#endif
+#if ENABLE_FEATURE_VOLUMEID_UDF
+       volume_id_probe_udf,
+#endif
+#if ENABLE_FEATURE_VOLUMEID_ISO9660
+       volume_id_probe_iso9660,
+#endif
+#if ENABLE_FEATURE_VOLUMEID_HFS
+       volume_id_probe_hfs_hfsplus,
+#endif
+#if ENABLE_FEATURE_VOLUMEID_UFS
+       volume_id_probe_ufs,
+#endif
+#if ENABLE_FEATURE_VOLUMEID_NTFS
+       volume_id_probe_ntfs,
+#endif
+#if ENABLE_FEATURE_VOLUMEID_CRAMFS
+       volume_id_probe_cramfs,
+#endif
+#if ENABLE_FEATURE_VOLUMEID_ROMFS
+       volume_id_probe_romfs,
+#endif
+#if ENABLE_FEATURE_VOLUMEID_HPFS
+       volume_id_probe_hpfs,
+#endif
+#if ENABLE_FEATURE_VOLUMEID_SYSV
+       volume_id_probe_sysv,
+#endif
+#if ENABLE_FEATURE_VOLUMEID_MINIX
+       volume_id_probe_minix,
+#endif
+#if ENABLE_FEATURE_VOLUMEID_OCFS2
+       volume_id_probe_ocfs2,
+#endif
+};
+
+int volume_id_probe_all(struct volume_id *id, /*uint64_t off,*/ uint64_t size)
+{
+       unsigned i;
+
+       /* probe for raid first, cause fs probes may be successful on raid members */
+       if (size) {
+               for (i = 0; i < ARRAY_SIZE(raid1); i++) {
+                       if (raid1[i](id, /*off,*/ size) == 0)
+                               goto ret;
+                       if (id->error)
+                               goto ret;
+               }
+       }
+
+       for (i = 0; i < ARRAY_SIZE(raid2); i++) {
+               if (raid2[i](id /*,off*/) == 0)
+                       goto ret;
+               if (id->error)
+                       goto ret;
+       }
+
+       /* signature in the first block, only small buffer needed */
+       for (i = 0; i < ARRAY_SIZE(fs1); i++) {
+               if (fs1[i](id /*,off*/) == 0)
+                       goto ret;
+               if (id->error)
+                       goto ret;
+       }
+
+       /* fill buffer with maximum */
+       volume_id_get_buffer(id, 0, SB_BUFFER_SIZE);
+
+       for (i = 0; i < ARRAY_SIZE(fs2); i++) {
+               if (fs2[i](id /*,off*/) == 0)
+                       goto ret;
+               if (id->error)
+                       goto ret;
+       }
+
+ ret:
+       volume_id_free_buffer(id);
+       return (- id->error); /* 0 or -1 */
+
+}
+
+/* open volume by device node */
+struct volume_id *volume_id_open_node(int fd)
+{
+       struct volume_id *id;
+
+       id = xzalloc(sizeof(struct volume_id));
+       id->fd = fd;
+       ///* close fd on device close */
+       //id->fd_close = 1;
+       return id;
+}
+
+#ifdef UNUSED
+/* open volume by major/minor */
+struct volume_id *volume_id_open_dev_t(dev_t devt)
+{
+       struct volume_id *id;
+       char *tmp_node[VOLUME_ID_PATH_MAX];
+
+       tmp_node = xasprintf("/dev/.volume_id-%u-%u-%u",
+               (unsigned)getpid(), (unsigned)major(devt), (unsigned)minor(devt));
+
+       /* create temporary node to open block device */
+       unlink(tmp_node);
+       if (mknod(tmp_node, (S_IFBLK | 0600), devt) != 0)
+               bb_perror_msg_and_die("cannot mknod(%s)", tmp_node);
+
+       id = volume_id_open_node(tmp_node);
+       unlink(tmp_node);
+       free(tmp_node);
+       return id;
+}
+#endif
+
+void free_volume_id(struct volume_id *id)
+{
+       if (id == NULL)
+               return;
+
+       //if (id->fd_close != 0) - always true
+               close(id->fd);
+       volume_id_free_buffer(id);
+#ifdef UNUSED_PARTITION_CODE
+       free(id->partitions);
+#endif
+       free(id);
+}
diff --git a/util-linux/volume_id/volume_id_internal.h b/util-linux/volume_id/volume_id_internal.h
new file mode 100644 (file)
index 0000000..af58883
--- /dev/null
@@ -0,0 +1,227 @@
+/*
+ * volume_id - reads filesystem label and uuid
+ *
+ * Copyright (C) 2005 Kay Sievers <kay.sievers@vrfy.org>
+ *
+ *     This 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.
+ *
+ *     This 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 this library; if not, write to the Free Software
+ *     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "libbb.h"
+#include "volume_id.h"
+
+PUSH_AND_SET_FUNCTION_VISIBILITY_TO_HIDDEN
+
+#define dbg(...) ((void)0)
+/* #define dbg(...) bb_error_msg(__VA_ARGS__) */
+
+/* volume_id.h */
+
+#define VOLUME_ID_VERSION              48
+
+#define VOLUME_ID_LABEL_SIZE           64
+#define VOLUME_ID_UUID_SIZE            36
+#define VOLUME_ID_FORMAT_SIZE          32
+#define VOLUME_ID_PARTITIONS_MAX       256
+
+enum volume_id_usage {
+       VOLUME_ID_UNUSED,
+       VOLUME_ID_UNPROBED,
+       VOLUME_ID_OTHER,
+       VOLUME_ID_FILESYSTEM,
+       VOLUME_ID_PARTITIONTABLE,
+       VOLUME_ID_RAID,
+       VOLUME_ID_DISKLABEL,
+       VOLUME_ID_CRYPTO,
+};
+
+#ifdef UNUSED_PARTITION_CODE
+struct volume_id_partition {
+//     const char      *type;
+//     const char      *usage;
+//     smallint        usage_id;
+//     uint8_t         pt_type_raw;
+//     uint64_t        pt_off;
+//     uint64_t        pt_len;
+};
+#endif
+
+struct volume_id {
+       int             fd;
+//     int             fd_close:1;
+       int             error;
+       size_t          sbbuf_len;
+       size_t          seekbuf_len;
+       uint8_t         *sbbuf;
+       uint8_t         *seekbuf;
+       uint64_t        seekbuf_off;
+#ifdef UNUSED_PARTITION_CODE
+       struct volume_id_partition *partitions;
+       size_t          partition_count;
+#endif
+//     uint8_t         label_raw[VOLUME_ID_LABEL_SIZE];
+//     size_t          label_raw_len;
+       char            label[VOLUME_ID_LABEL_SIZE+1];
+//     uint8_t         uuid_raw[VOLUME_ID_UUID_SIZE];
+//     size_t          uuid_raw_len;
+       /* uuid is stored in ASCII (not binary) form here: */
+       char            uuid[VOLUME_ID_UUID_SIZE+1];
+//     char            type_version[VOLUME_ID_FORMAT_SIZE];
+//     smallint        usage_id;
+//     const char      *usage;
+//     const char      *type;
+};
+
+struct volume_id *volume_id_open_node(int fd);
+int volume_id_probe_all(struct volume_id *id, /*uint64_t off,*/ uint64_t size);
+void free_volume_id(struct volume_id *id);
+
+/* util.h */
+
+/* size of superblock buffer, reiserfs block is at 64k */
+#define SB_BUFFER_SIZE                         0x11000
+/* size of seek buffer, FAT cluster is 32k max */
+#define SEEK_BUFFER_SIZE                       0x10000
+
+#define bswap16(x) (uint16_t)  ( \
+                               (((uint16_t)(x) & 0x00ffu) << 8) | \
+                               (((uint16_t)(x) & 0xff00u) >> 8))
+
+#define bswap32(x) (uint32_t)  ( \
+                               (((uint32_t)(x) & 0xff000000u) >> 24) | \
+                               (((uint32_t)(x) & 0x00ff0000u) >>  8) | \
+                               (((uint32_t)(x) & 0x0000ff00u) <<  8) | \
+                               (((uint32_t)(x) & 0x000000ffu) << 24))
+
+#define bswap64(x) (uint64_t)  ( \
+                               (((uint64_t)(x) & 0xff00000000000000ull) >> 56) | \
+                               (((uint64_t)(x) & 0x00ff000000000000ull) >> 40) | \
+                               (((uint64_t)(x) & 0x0000ff0000000000ull) >> 24) | \
+                               (((uint64_t)(x) & 0x000000ff00000000ull) >>  8) | \
+                               (((uint64_t)(x) & 0x00000000ff000000ull) <<  8) | \
+                               (((uint64_t)(x) & 0x0000000000ff0000ull) << 24) | \
+                               (((uint64_t)(x) & 0x000000000000ff00ull) << 40) | \
+                               (((uint64_t)(x) & 0x00000000000000ffull) << 56))
+
+#if BB_LITTLE_ENDIAN
+#define le16_to_cpu(x) (x)
+#define le32_to_cpu(x) (x)
+#define le64_to_cpu(x) (x)
+#define be16_to_cpu(x) bswap16(x)
+#define be32_to_cpu(x) bswap32(x)
+#define cpu_to_le16(x) (x)
+#define cpu_to_le32(x) (x)
+#define cpu_to_be32(x) bswap32(x)
+#else
+#define le16_to_cpu(x) bswap16(x)
+#define le32_to_cpu(x) bswap32(x)
+#define le64_to_cpu(x) bswap64(x)
+#define be16_to_cpu(x) (x)
+#define be32_to_cpu(x) (x)
+#define cpu_to_le16(x) bswap16(x)
+#define cpu_to_le32(x) bswap32(x)
+#define cpu_to_be32(x) (x)
+#endif
+
+enum uuid_format {
+       UUID_DCE_STRING,
+       UUID_DCE,
+       UUID_DOS,
+       UUID_NTFS,
+       UUID_HFS,
+};
+
+enum endian {
+       LE = 0,
+       BE = 1
+};
+
+void volume_id_set_unicode16(char *str, size_t len, const uint8_t *buf, enum endian endianess, size_t count);
+//void volume_id_set_usage(struct volume_id *id, enum volume_id_usage usage_id);
+//void volume_id_set_usage_part(struct volume_id_partition *part, enum volume_id_usage usage_id);
+//void volume_id_set_label_raw(struct volume_id *id, const uint8_t *buf, size_t count);
+void volume_id_set_label_string(struct volume_id *id, const uint8_t *buf, size_t count);
+void volume_id_set_label_unicode16(struct volume_id *id, const uint8_t *buf, enum endian endianess, size_t count);
+void volume_id_set_uuid(struct volume_id *id, const uint8_t *buf, enum uuid_format format);
+void *volume_id_get_buffer(struct volume_id *id, uint64_t off, size_t len);
+void volume_id_free_buffer(struct volume_id *id);
+
+
+/* Probe routines */
+
+/* RAID */
+
+//int volume_id_probe_highpoint_37x_raid(struct volume_id *id /*,uint64_t off*/);
+//int volume_id_probe_highpoint_45x_raid(struct volume_id *id /*,uint64_t off*/, uint64_t size);
+
+//int volume_id_probe_intel_software_raid(struct volume_id *id /*,uint64_t off*/, uint64_t size);
+
+int volume_id_probe_linux_raid(struct volume_id *id /*,uint64_t off*/, uint64_t size);
+
+//int volume_id_probe_lsi_mega_raid(struct volume_id *id /*,uint64_t off*/, uint64_t size);
+
+//int volume_id_probe_nvidia_raid(struct volume_id *id /*,uint64_t off*/, uint64_t size);
+
+//int volume_id_probe_promise_fasttrack_raid(struct volume_id *id /*,uint64_t off*/, uint64_t size);
+
+//int volume_id_probe_silicon_medley_raid(struct volume_id *id /*,uint64_t off*/, uint64_t size);
+
+//int volume_id_probe_via_raid(struct volume_id *id /*,uint64_t off*/, uint64_t size);
+
+//int volume_id_probe_lvm1(struct volume_id *id /*,uint64_t off*/);
+//int volume_id_probe_lvm2(struct volume_id *id /*,uint64_t off*/);
+
+/* FS */
+
+int volume_id_probe_cramfs(struct volume_id *id /*,uint64_t off*/);
+
+int volume_id_probe_ext(struct volume_id *id /*,uint64_t off*/);
+
+int volume_id_probe_vfat(struct volume_id *id /*,uint64_t off*/);
+
+int volume_id_probe_hfs_hfsplus(struct volume_id *id /*,uint64_t off*/);
+
+//int volume_id_probe_hpfs(struct volume_id *id /*,uint64_t off*/);
+
+int volume_id_probe_iso9660(struct volume_id *id /*,uint64_t off*/);
+
+int volume_id_probe_jfs(struct volume_id *id /*,uint64_t off*/);
+
+int volume_id_probe_linux_swap(struct volume_id *id /*,uint64_t off*/);
+
+int volume_id_probe_luks(struct volume_id *id /*,uint64_t off*/);
+
+//int volume_id_probe_mac_partition_map(struct volume_id *id /*,uint64_t off*/);
+
+//int volume_id_probe_minix(struct volume_id *id /*,uint64_t off*/);
+
+//int volume_id_probe_msdos_part_table(struct volume_id *id /*,uint64_t off*/);
+
+int volume_id_probe_ntfs(struct volume_id *id /*,uint64_t off*/);
+
+int volume_id_probe_ocfs2(struct volume_id *id /*,uint64_t off*/);
+
+int volume_id_probe_reiserfs(struct volume_id *id /*,uint64_t off*/);
+
+int volume_id_probe_romfs(struct volume_id *id /*,uint64_t off*/);
+
+int volume_id_probe_sysv(struct volume_id *id /*,uint64_t off*/);
+
+int volume_id_probe_udf(struct volume_id *id /*,uint64_t off*/);
+
+//int volume_id_probe_ufs(struct volume_id *id /*,uint64_t off*/);
+
+int volume_id_probe_xfs(struct volume_id *id /*,uint64_t off*/);
+
+POP_SAVED_FUNCTION_VISIBILITY
diff --git a/util-linux/volume_id/xfs.c b/util-linux/volume_id/xfs.c
new file mode 100644 (file)
index 0000000..646c81d
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+ * volume_id - reads filesystem label and uuid
+ *
+ * Copyright (C) 2004 Kay Sievers <kay.sievers@vrfy.org>
+ *
+ *     This 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.
+ *
+ *     This 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 this library; if not, write to the Free Software
+ *     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "volume_id_internal.h"
+
+struct xfs_super_block {
+       uint8_t magic[4];
+       uint32_t        blocksize;
+       uint64_t        dblocks;
+       uint64_t        rblocks;
+       uint32_t        dummy1[2];
+       uint8_t uuid[16];
+       uint32_t        dummy2[15];
+       uint8_t fname[12];
+       uint32_t        dummy3[2];
+       uint64_t        icount;
+       uint64_t        ifree;
+       uint64_t        fdblocks;
+} __attribute__((__packed__));
+
+int volume_id_probe_xfs(struct volume_id *id /*,uint64_t off*/)
+{
+#define off ((uint64_t)0)
+       struct xfs_super_block *xs;
+
+       dbg("probing at offset 0x%llx", (unsigned long long) off);
+
+       xs = volume_id_get_buffer(id, off, 0x200);
+       if (xs == NULL)
+               return -1;
+
+       if (memcmp(xs->magic, "XFSB", 4) != 0)
+               return -1;
+
+//     volume_id_set_label_raw(id, xs->fname, 12);
+       volume_id_set_label_string(id, xs->fname, 12);
+       volume_id_set_uuid(id, xs->uuid, UUID_DCE);
+
+//     volume_id_set_usage(id, VOLUME_ID_FILESYSTEM);
+//     id->type = "xfs";
+
+       return 0;
+}